Bu bölüm otomatik olarak yaratılan CRUD sayfalarını inceleyip, üç katmanın birbirini nasıl desteklediğini üzerinedir.Öncelikle model ile başlayalım. Razor Pages bölümde anlatılan doğrulama işlemi bu bölüm içinde geçerlidir. Örneğin movie.cs dosyasın aşağıdaki değişiklikleri yapalım.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
using System; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace MvcMovie.Models { public class Movie { public int Id { get; set; } [required] public string Title { get; set; } [Display(Name = "Release Date")] [DataType(DataType.Date)] public DateTime ReleaseDate { get; set; } public string Genre { get; set; } [Column(TypeName = "decimal(18, 2)")] public decimal Price { get; set; } } } |
Bu kod, DRY ilkesi aklımıza geldi. DataAnnotations özelliği sayesinde […] arasına eklenen ifadeler anlam kazanmaktadır. [required] ifadesi bu alanın kesinlikle doldurulması gerektiğini belirtir. Bu basit kullanım ile hem istemci hem de sunucu tarafı için doğrulama işlemi olur.
Display(Name = “Release Date”)] özelliği kullanılacak başlık bilgisi için kullanılır. Burada yazılımı metni View bölümden çağırmak için birden fazla HTML Helper kullanılabilir.
@Html.DisplayNameFor(model => model.ReleaseDate)
<label asp-for=”ReleaseDate” class=”control-label”></label>
Her iki yöntemde Display(Name =”…”) içindeki değeri elde etmek için kullanılır. Eğer bu değer tanımlanmaz ise direkt direkt değişken değeri görüntülenir.
[DataType(DataType.Date)] ifadesi ile veri tipi otomatik tayin edilmiş olur. Create ve Edit sayfalarından tarih seçme işlemi otomatik olarak devreye girer. Ekstra bir javascript koduna ihtiyaç yoktur, DRY felsefesi gereği kodu bizim için arka planda üretti.
[Column(TypeName = “decimal(18, 2)”)] ifadesi verinin tam ve ondalık kısımları hakkında bilgi verir. Bunun dışında bir kullanıcı girişi olduğunda doğrulama işlemi hem istemci hem de sunucu tarafında kontrolü yapılır.
Şimdi oluşan Views\Movies\Index.cshtml view dosyasına bakalım.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
@model IEnumerable<MVC3_Model.Models.Movie> @{ ViewData["Title"] = "Index"; } <h1>Index</h1> <p> <a asp-action="Create">Create New</a> </p> <table class="table"> <thead> <tr> <th> @Html.DisplayNameFor(model => model.Title) </th> <th> @Html.DisplayNameFor(model => model.ReleaseDate) </th> <th> @Html.DisplayNameFor(model => model.Genre) </th> <th> @Html.DisplayNameFor(model => model.Price) </th> <th></th> </tr> </thead> <tbody> @foreach (var item in Model) { <tr> <td> @Html.DisplayFor(modelItem => item.Title) </td> <td> @Html.DisplayFor(modelItem => item.ReleaseDate) </td> <td> @Html.DisplayFor(modelItem => item.Genre) </td> <td> @Html.DisplayFor(modelItem => item.Price) </td> <td> <a asp-action="Edit" asp-route-id="@item.ID">Edit</a> | <a asp-action="Details" asp-route-id="@item.ID">Details</a> | <a asp-action="Delete" asp-route-id="@item.ID">Delete</a> </td> </tr> } </tbody> </table> |
Model controller tarafında hazırlanır. Views katmanında model’den gelen isimler @Html.DisplayNameFor ile ve Controller üzerinden veritabanından çekilmiş kayıtlar @Html.DisplayFor(modelItem => …) HTML yardımcı ile elde edilir. Kayıtlar birden fazla olacağı için bir Foreach döngüsü ile kayıt kalmayıncaya kadar işlem tekrarlanmıştır.
Ayrıca tekrarlayan Edit, Details, ve Delete a etiketi içindeki satırları vardır. Bu bölümde o kayıt için bir link oluşturulur. Örneğin ID değeri bir olan kayıt için oluşturulan linkler,
1 2 3 4 5 |
<td> <a href="/Movies/Edit/4"> Edit </a> | <a href="/Movies/Details/4"> Details </a> | <a href="/Movies/Delete/4"> Delete </a> </td> |
asp-action=”…” ve asp-route-id=”…” tarayıcıya gönderilmeden önce nasıl bir değişime uğradığına dikkat edelim. Aynı şekilde bir adet Create linki yaratılmıştır. Ancak bu link asp-route-id HTML Helper’ına ihtiyaç duymaz. Burada oluşan linklerin URL routing işleminden geçtiğini Razor Pages konusunda anlatmıştık. Hatırlamak için Startup.cs dosyasında aşağıdaki satırları bulup inceleyin.
1 2 3 4 5 6 |
app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); |
Şimdi Index Controller’ına bakalım. Bu kontroller Views bölümdeki klasör ismi ile aynı isme ve Controller.cs ekine sahiptir. Örneğin Movies için MoviesController.cs dosyası yaratılmalıdır. Bu dosyada tüm view’ler için metotlar vardır. Örneğin, tablo’daki verileri doldurmak için:
1 2 3 4 |
public async Task<IActionResult> Index() { return View(await _context.Movie.ToListAsync()); } |
kodu kullanılır. Bu kod _context üzerinden Movie kayıtları alır ve listeye ekler. await ile sonuçların tamamen gelmesi beklenir. View metodu Index.cshtml’e sonuçları gönderir. Aslında View metodu Index.cshtml’e Model gönderir. Bu örnek üç katmanında birlikte nasıl çalıştığını göstermektedir.
Şimdi Views/Movies/Edit.cshtml dosyasını açalım.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
@model MVC3_Model.Models.Movie @{ ViewData["Title"] = "Edit"; } <h1>Edit</h1> <h4>Movie</h4> <hr /> <div class="row"> <div class="col-md-4"> <form asp-action="Edit"> <div asp-validation-summary="ModelOnly" class="text-danger"></div> <input type="hidden" asp-for="ID" /> <div class="form-group"> <label asp-for="Title" class="control-label"></label> <input asp-for="Title" class="form-control" /> <span asp-validation-for="Title" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="ReleaseDate" class="control-label"></label> <input asp-for="ReleaseDate" class="form-control" /> <span asp-validation-for="ReleaseDate" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="Genre" class="control-label"></label> <input asp-for="Genre" class="form-control" /> <span asp-validation-for="Genre" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="Price" class="control-label"></label> <input asp-for="Price" class="form-control" /> <span asp-validation-for="Price" class="text-danger"></span> </div> <div class="form-group"> <input type="submit" value="Save" class="btn btn-primary" /> </div> </form> </div> </div> <div> <a asp-action="Index">Back to List</a> </div> @section Scripts { @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} } |
Bu Razor sayfası Create.cshtml sayfasına çok benzer. Sadece submit işleminde çağrılan metotlar farklıdır. Bu sayfa yüklenirken önce mevcut kayıtın bilgileri oluşturulur. Bunun için controller bölümde aşağıdaki satırlar vardır.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public async Task<IActionResult> Edit(int? id) { if (id == null) { return NotFound(); } var movie = await _context.Movie.FindAsync(id); if (movie == null) { return NotFound(); } return View(movie); } |
Tarayıcıdan /Movies/Edit/4 uzantılı bir get işlemi yapıldığında yukarıdaki metot devreye girer. Eğer id parametresi geldiği ise işleme devam eder, eğer yok ise NotFound metodunu döndürür. FindAsync metodu ile arama işlemini yapar. Eğer kayıt bulunursa model sonucu olarak View(…) içinde görevi Edit.cshtml dosyasına bırakır. Bu dosyada bu verileri alıp HTML çıktıyı üretir. Bu arada model kısmındaki doğrulama işlemlerinin de hem istemci için aktive edildiğini unutmayalım. Eğer bu kodla oluşan HTML form kullanıcı tarafından doğru şekilde doldurulduysa submit buttonu üzerinden bir POST işlemi gelir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
// POST: Movies/Edit/5 [HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> Edit(int id, [Bind("ID,Title,ReleaseDate,Genre,Price")] Movie movie) { if (id != movie.ID) { return NotFound(); } if (ModelState.IsValid) { try { _context.Update(movie); await _context.SaveChangesAsync(); } catch (DbUpdateConcurrencyException) { if (!MovieExists(movie.ID)) { return NotFound(); } else { throw; } } return RedirectToAction(nameof(Index)); } return View(movie); } |
Bu metotta yukarıdaki metot ismi ile aynı isme (Edit) sahiptir. Ancak bu metotun üzerinde [HttpPost] ve [ValidateAntiForgeryToken] satırları vardır. Burada [HttpPost] POST işlemi ait bir metot olduğunu gösterir. Bir anlamda diğer metottan ayrılır. Diğer metot ise GET işlemine aittir. [ValidateAntiForgeryToken] değeri güvenlik amaçlıdır. ValidateAntiForgeryToken özniteliği, bir isteğin sahtesini önlemek için kullanılır ve düzenleme görünümü dosyasında oluşturulan bir sahtekarlık token’ı ile eşleştirilir. Bu token için oluşan HTML sayfasının kaynak kodunda “__RequestVerificationToken” ismini arayın. Bu güvenlik amacı ile form Helper’ı tarafından oluşturulmuş bir etikettir.
Diğer bir güvenlik işlemi ise [Bind] özelliğidir. [Bind] özelliği, aşırı gönderime (over-posting) karşı korunmanın bir yoludur. Daha fazla bilgi tıklayın.
id değeri Submit edilen sayfadaki URL içindeki değerdir. Örneğin, /Movies/Edit/4 için bu değer 4’tür. Eğer gelen id ile Movie.ID aynı değilse NotFound metodu döndürülür. Bu hiç beklenmeyen bir durumdur hatta saldırı olduğu gösterir. ModelState.IsValid ile sunucu tarafında kontrol yapılır (model tarafındaki niteliklere göre), eğer doğrulama tamam ise kaydetme işlemine geçilir. Update metodu _context üzerindeki düzenlemeyi yapar ve SaveChangesAsync metodu ile fiziksel kaydetme işlemi başlatılır. Bir hata oluşmaz ise RedirectToAction metodu ile Index sayfasına yönlendirme yapılır. Eğer sayfada bir hata var ise View(movie) metodu ile aynı değerler mevcut sayfa üzerinde tekrar geri gönderilir.
Bir Delete işleminde Details sayfasına benzer bir sayfa yüklenir, ancak bu sayfada Delete’i onaylama işlemi vardır. Delete onaylandığında controller aşağıdaki kodu çalıştırır.
1 2 3 4 5 6 7 8 9 10 |
// POST: Movies/Delete/5 [HttpPost, ActionName("Delete")] [ValidateAntiForgeryToken] public async Task<IActionResult> DeleteConfirmed(int id) { var movie = await _context.Movie.FindAsync(id); _context.Movie.Remove(movie); await _context.SaveChangesAsync(); return RedirectToAction(nameof(Index)); } |
Delete işlemi de bir POST işlemidir, aynı zamanda Linkinde Delete işlemi yazar. ActionName ile Delete işlemi kontrolü yapılır. Delete işlemi de URL üzerindeki ID üzerinden olur. Silme işleminde öncelikle arama işlemi (FindAsync) yapılır. Aranan kayıt bulunca _Context’ten Remove metodu ile çıkarılır. SaveChangesAsync ile kaydetme işlemi başlar. Kaydetme işlemi aslında _context üzerindeki işlemin fiziksel olarak gerçekleştirme işlemidir. SaveChangesAsync metodu içinde Update, Delete, Insert olabilir.