Bagikan melalui


Menerapkan Fungsionalitas CRUD Dasar dengan Kerangka Kerja Entitas di Aplikasi MVC ASP.NET (2 dari 10)

oleh Tom Dykstra

Aplikasi web sampel Contoso University menunjukkan cara membuat aplikasi ASP.NET MVC 4 menggunakan Entity Framework 5 Code First dan Visual Studio 2012. Untuk informasi tentang seri tutorial, lihat tutorial pertama dalam seri ini.

Catatan

Jika Anda mengalami masalah yang tidak dapat Anda atasi, unduh bab yang telah selesai dan coba reprodurasi masalah Anda. Anda umumnya dapat menemukan solusi untuk masalah dengan membandingkan kode Anda dengan kode yang telah selesai. Untuk beberapa kesalahan umum dan cara mengatasinya, lihat Kesalahan dan Solusi.

Dalam tutorial sebelumnya Anda membuat aplikasi MVC yang menyimpan dan menampilkan data menggunakan Kerangka Kerja Entitas dan SQL Server LocalDB. Dalam tutorial ini Anda akan meninjau dan menyesuaikan kode CRUD (buat, baca, perbarui, hapus) yang dibuat perancah MVC secara otomatis untuk Anda dalam pengontrol dan tampilan.

Catatan

Ini adalah praktik umum untuk menerapkan pola repositori untuk membuat lapisan abstraksi antara pengontrol Anda dan lapisan akses data. Agar tutorial ini tetap sederhana, Anda tidak akan menerapkan repositori hingga tutorial selanjutnya dalam seri ini.

Dalam tutorial ini, Anda akan membuat halaman web berikut:

Cuplikan layar memperlihatkan halaman Detail Mahasiswa Contoso University.

Cuplikan layar memperlihatkan halaman Edit Mahasiswa Contoso University.

Cuplikan layar memperlihatkan halaman Buat Mahasiswa Contoso University.

Cuplikan layar yang memperlihatkan halaman Hapus Siswa.

Membuat Halaman Detail

Kode perancah untuk halaman Siswa Index meninggalkan Enrollments properti, karena properti tersebut menyimpan koleksi. Details Di halaman Anda akan menampilkan konten koleksi dalam tabel HTML.

Di Controllers\StudentController.cs, metode tindakan untuk Details tampilan menggunakan Find metode untuk mengambil satu Student entitas.

public ActionResult Details(int id = 0)
{
    Student student = db.Students.Find(id);
    if (student == null)
    {
        return HttpNotFound();
    }
    return View(student);
}

Nilai kunci diteruskan ke metode sebagai id parameter dan berasal dari data rute dalam hyperlink Detail di halaman Indeks.

  1. Buka Views\Student\Details.cshtml. Setiap bidang ditampilkan menggunakan pembantu, seperti yang DisplayFor diperlihatkan dalam contoh berikut:

    <div class="display-label">
             @Html.DisplayNameFor(model => model.LastName)
        </div>
        <div class="display-field">
            @Html.DisplayFor(model => model.LastName)
        </div>
    
  2. EnrollmentDate Setelah bidang dan segera sebelum tag penutupfieldset, tambahkan kode untuk menampilkan daftar pendaftaran, seperti yang ditunjukkan dalam contoh berikut:

    <div class="display-label">
            @Html.LabelFor(model => model.Enrollments)
        </div>
        <div class="display-field">
            <table>
                <tr>
                    <th>Course Title</th>
                    <th>Grade</th>
                </tr>
                @foreach (var item in Model.Enrollments)
                {
                    <tr>
                        <td>
                            @Html.DisplayFor(modelItem => item.Course.Title)
                        </td>
                        <td>
                            @Html.DisplayFor(modelItem => item.Grade)
                        </td>
                    </tr>
                }
            </table>
        </div>
    </fieldset>
    <p>
        @Html.ActionLink("Edit", "Edit", new { id=Model.StudentID }) |
        @Html.ActionLink("Back to List", "Index")
    </p>
    

    Kode ini mengulangi entitas di Enrollments properti navigasi. Untuk setiap Enrollment entitas dalam properti, entitas menampilkan judul kursus dan nilainya. Judul kursus diambil dari Course entitas yang disimpan di Course properti Enrollments navigasi entitas. Semua data ini diambil dari database secara otomatis saat diperlukan. (Dengan kata lain, Anda menggunakan pemuatan malas di sini. Anda tidak menentukan pemuatan bersemangat untuk Courses properti navigasi, jadi pertama kali Anda mencoba mengakses properti tersebut, kueri dikirim ke database untuk mengambil data. Anda dapat membaca lebih lanjut tentang pemuatan malas dan pemuatan yang bersemangat dalam tutorial Membaca Data Terkait nanti dalam seri ini.)

  3. Jalankan halaman dengan memilih tab Siswa dan mengklik tautan Detail untuk Alexander Carson. Anda melihat daftar kursus dan nilai untuk siswa yang dipilih:

    Student_Details_page

Memperbarui Halaman Buat

  1. Di Controllers\StudentController.cs, ganti HttpPost``Create metode tindakan dengan kode berikut untuk menambahkan try-catch blok dan atribut Bind ke metode scaffolded:

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Create(
       [Bind(Include = "LastName, FirstMidName, EnrollmentDate")]
       Student student)
    {
       try
       {
          if (ModelState.IsValid)
          {
             db.Students.Add(student);
             db.SaveChanges();
             return RedirectToAction("Index");
          }
       }
       catch (DataException /* dex */)
       {
          //Log the error (uncomment dex variable name after DataException and add a line here to write a log.
          ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists see your system administrator.");
       }
       return View(student);
    }
    

    Kode ini menambahkan entitas yang Student dibuat oleh pengikat model MVC ASP.NET ke Students kumpulan entitas lalu menyimpan perubahan ke database. (Pengikat model mengacu pada fungsionalitas MVC ASP.NET yang memudahkan Anda untuk bekerja dengan data yang dikirimkan oleh formulir; pengikat model mengonversi nilai formulir yang diposting ke jenis CLR dan meneruskannya ke metode tindakan dalam parameter. Dalam hal ini, pengikat model membuat instans Student entitas untuk Anda menggunakan nilai properti dari Form koleksi.)

    Atribut ini ValidateAntiForgeryToken membantu mencegah serangan pemalsuan permintaan lintas situs.

> [!WARNING]
    > Security - The `Bind` attribute is added to protect against *over-posting*. For example, suppose the `Student` entity includes a `Secret` property that you don't want this web page to update.
    > 
    > [!code-csharp[Main](implementing-basic-crud-functionality-with-the-entity-framework-in-asp-net-mvc-application/samples/sample5.cs?highlight=7)]
    > 
    > Even if you don't have a `Secret` field on the web page, a hacker could use a tool such as [fiddler](http://fiddler2.com/home), or write some JavaScript, to post a `Secret` form value. Without the [Bind](https://msdn.microsoft.com/library/system.web.mvc.bindattribute(v=vs.108).aspx) attribute limiting the fields that the model binder uses when it creates a `Student` instance*,* the model binder would pick up that `Secret` form value and use it to update the `Student` entity instance. Then whatever value the hacker specified for the `Secret` form field would be updated in your database. The following image shows the fiddler tool adding the `Secret` field (with the value "OverPost") to the posted form values.
    > 
    > ![](implementing-basic-crud-functionality-with-the-entity-framework-in-asp-net-mvc-application/_static/image6.png)  
    > 
    > The value "OverPost" would then be successfully added to the `Secret` property of the inserted row, although you never intended that the web page be able to update that property.
    > 
    > It's a security best practice to use the `Include` parameter with the `Bind` attribute to *allowed attributes* fields. It's also possible to use the `Exclude` parameter to *blocked attributes* fields you want to exclude. The reason `Include` is more secure is that when you add a new property to the entity, the new field is not automatically protected by an `Exclude` list.
    > 
    > Another alternative approach, and one preferred by many, is to use only view models with model binding. The view model contains only the properties you want to bind. Once the MVC model binder has finished, you copy the view model properties to the entity instance.

    Other than the `Bind` attribute, the `try-catch` block is the only change you've made to the scaffolded code. If an exception that derives from [DataException](https://msdn.microsoft.com/library/system.data.dataexception.aspx) is caught while the changes are being saved, a generic error message is displayed. [DataException](https://msdn.microsoft.com/library/system.data.dataexception.aspx) exceptions are sometimes caused by something external to the application rather than a programming error, so the user is advised to try again. Although not implemented in this sample, a production quality application would log the exception (and non-null inner exceptions ) with a logging mechanism such as [ELMAH](https://code.google.com/p/elmah/).

    The code in *Views\Student\Create.cshtml* is similar to what you saw in *Details.cshtml*, except that `EditorFor` and `ValidationMessageFor` helpers are used for each field instead of `DisplayFor`. The following example shows the relevant code:

    [!code-cshtml[Main](implementing-basic-crud-functionality-with-the-entity-framework-in-asp-net-mvc-application/samples/sample6.cshtml)]

    *Create.cshtml* also includes `@Html.AntiForgeryToken()`, which works with the `ValidateAntiForgeryToken` attribute in the controller to help prevent [cross-site request forgery](../../security/xsrfcsrf-prevention-in-aspnet-mvc-and-web-pages.md) attacks.

    No changes are required in *Create.cshtml*.
  1. Jalankan halaman dengan memilih tab Siswa dan klik Buat Baru.

    Student_Create_page

    Beberapa validasi data berfungsi secara default. Masukkan nama dan tanggal yang tidak valid dan klik Buat untuk melihat pesan kesalahan.

    Students_Create_page_error_message

    Kode yang disorot berikut menunjukkan pemeriksaan validasi model.

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Create(Student student)
    {
        if (ModelState.IsValid)
        {
            db.Students.Add(student);
            db.SaveChanges();
            return RedirectToAction("Index");
        }
    
        return View(student);
    }
    

    Ubah tanggal menjadi nilai yang valid seperti 1/9/2005 dan klik Buat untuk melihat siswa baru muncul di halaman Indeks .

    Students_Index_page_with_new_student

Memperbarui Halaman Edit POST

Di Controllers\StudentController.cs, HttpGet Edit metode (yang tanpa HttpPost atribut) menggunakan Find metode untuk mengambil entitas yang dipilih, seperti yang Student Anda lihat dalam Details metode . Anda tidak perlu mengubah metode ini.

Namun, ganti HttpPost Edit metode tindakan dengan kode berikut untuk menambahkan try-catch blok dan atribut Bind:

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(
   [Bind(Include = "StudentID, LastName, FirstMidName, EnrollmentDate")]
   Student student)
{
   try
   {
      if (ModelState.IsValid)
      {
         db.Entry(student).State = EntityState.Modified;
         db.SaveChanges();
         return RedirectToAction("Index");
      }
   }
   catch (DataException /* dex */)
   {
      //Log the error (uncomment dex variable name after DataException and add a line here to write a log.
      ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists see your system administrator.");
   }
   return View(student);
}

Kode ini mirip dengan apa yang Anda lihat dalam HttpPost Create metode . Namun, alih-alih menambahkan entitas yang dibuat oleh pengikat model ke kumpulan entitas, kode ini menetapkan bendera pada entitas yang menunjukkan bahwa entitas telah diubah. Ketika metode SaveChanges dipanggil, bendera Diubah menyebabkan Kerangka Kerja Entitas membuat pernyataan SQL untuk memperbarui baris database. Semua kolom baris database akan diperbarui, termasuk kolom yang tidak diubah pengguna, dan konflik konkurensi diabaikan. (Anda akan mempelajari cara menangani konkurensi dalam tutorial selanjutnya dalam seri ini.)

Status Entitas dan Metode Lampirkan dan SaveChanges

Konteks database melacak apakah entitas dalam memori sinkron dengan baris yang sesuai dalam database, dan informasi ini menentukan apa yang terjadi saat Anda memanggil SaveChanges metode . Misalnya, saat Anda meneruskan entitas baru ke metode Tambahkan , status entitas tersebut diatur ke Added. Kemudian saat Anda memanggil metode SaveChanges , konteks database mengeluarkan perintah SQL INSERT .

Entitas mungkin berada di salah satu status berikut:

  • Added. Entitas belum ada di database. Metode SaveChanges harus mengeluarkan INSERT pernyataan.
  • Unchanged. Tidak ada yang perlu dilakukan dengan entitas ini dengan SaveChanges metode . Saat Anda membaca entitas dari database, entitas dimulai dengan status ini.
  • Modified. Beberapa atau semua nilai properti entitas telah dimodifikasi. Metode SaveChanges harus mengeluarkan UPDATE pernyataan.
  • Deleted. Entitas telah ditandai untuk dihapus. Metode SaveChanges harus mengeluarkan DELETE pernyataan.
  • Detached. Entitas tidak dilacak oleh konteks database.

Dalam aplikasi desktop, perubahan status biasanya diatur secara otomatis. Dalam jenis aplikasi desktop, Anda membaca entitas dan membuat perubahan pada beberapa nilai propertinya. Ini menyebabkan status entitasnya secara otomatis diubah menjadi Modified. Kemudian saat Anda memanggil SaveChanges, Kerangka Kerja Entitas menghasilkan pernyataan SQL UPDATE yang hanya memperbarui properti aktual yang Anda ubah.

Sifat aplikasi web yang terputus tidak memungkinkan urutan berkelanjutan ini. DbContext yang membaca entitas dibuang setelah halaman dirender. HttpPost Edit Ketika metode tindakan dipanggil, permintaan baru dibuat dan Anda memiliki instans baru DbContext, jadi Anda harus mengatur status entitas secara manual ke Modified. Kemudian ketika Anda memanggil SaveChanges, Kerangka Kerja Entitas memperbarui semua kolom baris database, karena konteks tidak memiliki cara untuk mengetahui properti mana yang Anda ubah.

Jika Anda ingin pernyataan SQL Update hanya memperbarui bidang yang benar-benar diubah pengguna, Anda dapat menyimpan nilai asli dalam beberapa cara (seperti bidang tersembunyi) sehingga tersedia saat HttpPost Edit metode dipanggil. Kemudian Anda dapat membuat Student entitas menggunakan nilai asli, memanggil Attach metode dengan versi asli entitas tersebut, memperbarui nilai entitas ke nilai baru, lalu memanggil SaveChanges. Untuk informasi selengkapnya, lihat Status entitas dan SaveChanges dan Data Lokal di Pusat Pengembang Data MSDN.

Kode dalam Views\Student\Edit.cshtml mirip dengan apa yang Anda lihat di Create.cshtml, dan tidak ada perubahan yang diperlukan.

Jalankan halaman dengan memilih tab Siswa lalu klik Edit hyperlink.

Student_Edit_page

Ubah beberapa data dan klik Simpan. Anda melihat data yang diubah di halaman Indeks.

Students_Index_page_after_edit

Memperbarui Halaman Hapus

Di Controllers\StudentController.cs, kode templat untuk HttpGet Delete metode menggunakan Find metode untuk mengambil entitas yang dipilih, seperti yang Student Anda lihat dalam Details metode dan Edit . Namun, untuk menerapkan pesan kesalahan kustom saat panggilan gagal SaveChanges , Anda akan menambahkan beberapa fungsionalitas ke metode ini dan tampilan yang sesuai.

Seperti yang Anda lihat untuk memperbarui dan membuat operasi, operasi penghapusan memerlukan dua metode tindakan. Metode yang dipanggil sebagai respons terhadap permintaan GET menampilkan tampilan yang memberi pengguna kesempatan untuk menyetujui atau membatalkan operasi penghapusan. Jika pengguna menyetujuinya, permintaan POST dibuat. Ketika itu terjadi, metode dipanggil HttpPost Delete dan kemudian metode itu benar-benar melakukan operasi penghapusan.

Anda akan menambahkan try-catch blok ke HttpPost Delete metode untuk menangani kesalahan apa pun yang mungkin terjadi saat database diperbarui. Jika terjadi kesalahan, HttpPost Delete metode memanggil HttpGet Delete metode , meneruskannya parameter yang menunjukkan bahwa kesalahan telah terjadi. Metode kemudian HttpGet Delete memutar ulang halaman konfirmasi bersama dengan pesan kesalahan, memberi pengguna kesempatan untuk membatalkan atau mencoba lagi.

  1. HttpGet Delete Ganti metode tindakan dengan kode berikut, yang mengelola pelaporan kesalahan:

    public ActionResult Delete(bool? saveChangesError=false, int id = 0)
    {
        if (saveChangesError.GetValueOrDefault())
        {
            ViewBag.ErrorMessage = "Delete failed. Try again, and if the problem persists see your system administrator.";
        }
        Student student = db.Students.Find(id);
        if (student == null)
        {
            return HttpNotFound();
        }
        return View(student);
    }
    

    Kode ini menerima parameter Boolean opsional yang menunjukkan apakah itu dipanggil setelah kegagalan untuk menyimpan perubahan. Parameter ini adalah false ketika metode dipanggil HttpGet Delete tanpa kegagalan sebelumnya. Ketika dipanggil oleh HttpPost Delete metode sebagai respons terhadap kesalahan pembaruan database, parameternya adalah true dan pesan kesalahan diteruskan ke tampilan.

  2. HttpPost Delete Ganti metode tindakan (bernama DeleteConfirmed) dengan kode berikut, yang melakukan operasi penghapusan aktual dan menangkap kesalahan pembaruan database apa pun.

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Delete(int id)
    {
        try
        {
            Student student = db.Students.Find(id);
            db.Students.Remove(student);
            db.SaveChanges();
        }
        catch (DataException/* dex */)
        {
            // uncomment dex and log error. 
            return RedirectToAction("Delete", new { id = id, saveChangesError = true });
        }
        return RedirectToAction("Index");
    }
    

    Kode ini mengambil entitas yang dipilih, lalu memanggil metode Hapus untuk mengatur status entitas ke Deleted. Ketika SaveChanges dipanggil, perintah SQL DELETE dihasilkan. Anda juga telah mengubah nama metode tindakan dari DeleteConfirmed menjadi Delete. Kode perancah bernama HttpPost Delete metode DeleteConfirmed untuk memberikan HttpPost metode tanda tangan unik. ( CLR memerlukan metode kelebihan beban untuk memiliki parameter metode yang berbeda.) Sekarang setelah tanda tangan unik, Anda dapat tetap dengan konvensi MVC dan menggunakan nama yang sama untuk HttpPost metode dan HttpGet hapus.

    Jika meningkatkan performa dalam aplikasi volume tinggi adalah prioritas, Anda dapat menghindari kueri SQL yang tidak perlu untuk mengambil baris dengan mengganti baris kode yang memanggil Find metode dan Remove dengan kode berikut seperti yang ditunjukkan dalam sorotan kuning:

    Student studentToDelete = new Student() { StudentID = id };
    db.Entry(studentToDelete).State = EntityState.Deleted;
    

    Kode ini membuat Student instans entitas hanya menggunakan nilai kunci utama lalu mengatur status entitas ke Deleted. Itu saja yang dibutuhkan Entity Framework untuk menghapus entitas.

    Seperti yang disebutkan, HttpGet Delete metode tidak menghapus data. Melakukan operasi penghapusan sebagai respons terhadap permintaan GET (atau untuk hal itu, melakukan operasi edit apa pun, membuat operasi, atau operasi lain yang mengubah data) membuat risiko keamanan. Untuk informasi selengkapnya, lihat ASP.NET Tip MVC #46 — Jangan gunakan Hapus Tautan karena mereka membuat Lubang Keamanan di blog Stephen Walther.

  3. Di Views\Student\Delete.cshtml, tambahkan pesan kesalahan antara h2 judul dan h3 judul, seperti yang ditunjukkan dalam contoh berikut:

    <h2>Delete</h2>
    <p class="error">@ViewBag.ErrorMessage</p>
    <h3>Are you sure you want to delete this?</h3>
    

    Jalankan halaman dengan memilih tab Siswa dan mengklik Hapus hyperlink:

    Student_Delete_page

  4. Klik Hapus. Halaman Indeks ditampilkan tanpa siswa yang dihapus. (Anda akan melihat contoh kode penanganan kesalahan dalam tindakan di Menangani tutorial Konkurensi nanti dalam seri ini.)

Memastikan Bahwa Koneksi Database Tidak Dibiarkan Terbuka

Untuk memastikan bahwa koneksi database ditutup dengan benar dan sumber daya yang dibeberkan, Anda akan melihatnya bahwa instans konteks dibuang. Itulah sebabnya kode perancah menyediakan metode Buang di akhir StudentController kelas di StudentController.cs, seperti yang ditunjukkan dalam contoh berikut:

protected override void Dispose(bool disposing)
{
    db.Dispose();
    base.Dispose(disposing);
}

Kelas dasar Controller sudah mengimplementasikan IDisposable antarmuka, sehingga kode ini hanya menambahkan penimpaan ke Dispose(bool) metode untuk secara eksplisit membuang instans konteks.

Ringkasan

Anda sekarang memiliki sekumpulan halaman lengkap yang melakukan operasi CRUD sederhana untuk Student entitas. Anda menggunakan pembantu MVC untuk menghasilkan elemen UI untuk bidang data. Untuk informasi selengkapnya tentang pembantu MVC, lihat Merender Formulir Menggunakan Pembantu HTML (halaman tersebut untuk MVC 3 tetapi masih relevan untuk MVC 4).

Dalam tutorial berikutnya, Anda akan memperluas fungsionalitas halaman Indeks dengan menambahkan pengurutan dan penomoran halaman.

Tautan ke sumber daya Entity Framework lainnya dapat ditemukan di Peta Konten Akses Data ASP.NET.