28 Temmuz 2014 Pazartesi

MVC Film Uygulaması - Edit Metodlarının İncelenmesi (Part VI)

Bu yazımızda film controller sınıfımızın otomatik olarak oluşturulmuş Edit action metodlarını inceleyeceğiz. Ama öncelikle çıkış tarihinin görünümünü daha iyi hale getirelim. Models\Film.cs dosyasını açalım ve aşağıdaki işaretli satırları ekleyelim:

using System;
using System.ComponentModel.DataAnnotations;
using System.Data.Entity;

namespace MVCFilm.Models
{
    public class Film
    {
        public int ID { get; set; }
        public string Baslik { get; set; }

        [Display(Name = "Çıkış Tarihi")]
        [DataType(DataType.Date)]
        [DisplayFormat(DataFormatString = "{0:dd-MM-yyyy}", ApplyFormatInEditMode = true)]
        public DateTime CikisTarihi { get; set; }
        public string Tipi { get; set; }
        public decimal Fiyati { get; set; }
    }

    public class FilmDBContext : DbContext
    {
        public DbSet<Film> Filmler { get; set; }
    }
}
Yukarıda köşeli parantezlerle tanımladığımız verilere DataAnnotations denir. Buna sonraki yazımızda değineceğiz. Tanımlanan annotation'lara bakarsak:

  • Display özelliği, değişkenin isminin nasıl görüneceğini belirler (CikisTarihi yerine Çıkış Tarihi şeklinde).
  • DataType özelliği verinin hangi tür olduğunu belirler. Bizim örneğimizde tarih tipinde oluyor. Yani saniye, dakika gibi bir zaman bilgisi gözükmeyecek.
  • DisplayFormat özelliği ise, tarayıcıda tarih formatının yanlış işlenmemesi için gereklidir.


Uygulamamızı çalıştıralım ve Filmler controller'ına göz atalım. Fare imleci ike Edit linkinin üzerine gelelim ve hangi URL'e bağlı olduğuna bakalım.


Edit linki, Views\Filmler\Index.cshtml view'ı içerisinde yer alan Html.ActionLink metodu tarafından oluşturulmuştur.
@Html.ActionLink("Edit", "Edit", new { id=item.ID }) 

Buradaki Html nesnesi System.Web.Mvc.WebView temel sınıfının bir özelliğidir. ActionLink metodu ise controller'daki action metodlarına bağlanan HTML linklerinin dinamik olarak oluşturulmasını sağlar. Actionlink metodunun:

  • İlk argümanı link'in yazısıdır. (Örneğin <a>Edit Me</a> şeklinde).
  • İkinci argüman ise çalıştırılacak action metodudur (Edit metodu). 
  • Son argüman ise yönlendirme verisini oluşturan anonim nesnedir (ID = 4)
Önceki resimde altta görünen link http://localhost:61049/Filmler/Edit/3'tü. RouteConfig.cs dosyasında bulunan varsayılan rota, {controller}/{action}/{id} örüntüsünü tanır. Bu nedenle, ASP.NET yukarıdaki URL isteğini Filmler controller'ı içerisindeki Edit metoduna ID parametresi 4 olacak şekilde  iletir.

App_start\RouteConfig.cs dosyasında bulunan aşağıdaki kodu inceleyelim. MapRoute metodu, HTTP isteklerini uygun controller'daki metod içerisine, ID parametresi ile birlikte iletmek için kullanılır. Ayrıca MapRoute metodu, ActionLink gibi HtmlHelper'lar tarafından verilen controller, action metodu ve rota verisi için URL oluşturmada kullanılır:
public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

    routes.MapRoute(
        name: "Default",
        url: "{controller}/{action}/{id}",
        defaults: new { controller = "Home", action = "Index", 
            id = UrlParameter.Optional }
    );
}
Action metod parametrelerini sorgu olarak da gönderebiliriz. Örneğin http://localhost:61049/Filmler/Edit?ID=3 URL'i de aynı şekilde Filmler controller'ı içerisindeki Edit metoduna 3 olan ID parametresini iletir.


FilmlerController.cs'i açalım. İçerisinde aşağıdaki gibi iki tane action metod bulunuyor:

// GET: /Filmler/Edit/5
public ActionResult Edit(int? id)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    Film film = db.Filmler.Find(id);
    if (movie == null)
    {
        return HttpNotFound();
    }
    return View(film);
}

// POST: /Filmler/Edit/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for 
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include="ID,Baslik,CikisTarihi,Tipi,Fiyati")] Film film)
{
    if (ModelState.IsValid)
    {
        db.Entry(film).State = EntityState.Modified;
        db.SaveChanges();
        return RedirectToAction("Index");
    }
    return View(film);
}
Farkettiysek ikinci Edit metodunun başında [HttpPost] özelliğine var. Bu özellik, aşırı yüklenmiş Edit metodunun, sadece gelen POST istekleri için çalışabileceğini belirtir. İlk Edit metoduna da [HttpGet] özelliğini atayabilirdik, ama gerekli de değil. Çünkü varsayılan olarak HttpGet olacak şekilde ayarlıdır. Bind özelliği ise diğer bir önemli güvenlik mekanizmasıdır. Hacker'ların istediğimiz özellik dışındaki özelliklere veri göndermesini (overposting) engeller. Sadece değişmesini istediğimiz özellikleri Bind'ın içerisine koymalıyız. Web uygulamamız oldukça basit olduğu için, model içerisindeki tüm verileri bind ediyoruz. Overposting hakkında daha fazla bilgiye buradan ulaşabilirsiniz.

ValidateAntiForgery özelliği ise, gelen sahte istekleri önlemek ve Edit view dosyasındaki @Html.AntiForgeryToken() ile eşleştirmek için kullanılır. Edit dosyasının bir kısmında aşağıdaki konumda bulunmaktadır.

@model MVCFilm.Models.Film

@{
    ViewBag.Title = "Edit";
}
<h2>Edit</h2>
@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()    
    <div class="form-horizontal">
        <h4>Film</h4>
        <hr />
        @Html.ValidationSummary(true)
        @Html.HiddenFor(model => model.ID)

        <div class="form-group">
            @Html.LabelFor(model => model.Baslik, new { @class = "control-label col-md-2"})
            <div class="col-md-10">
                @Html.EditorFor(model => model.Baslik)
                @Html.ValidationMessageFor(model => model.Baslik)
            </div>
        </div>
@Html.AntiForgeryToken(), Movies controller'ının Edit metodu ile eşleşen ve sahteciliği önleyen gizli bir token oluşturur. Siteler arası istek sahteciği (XSRF) hakkında daha fazla bilgiye buradan ulaşabilirsiniz.

HttpGet Edit metodu (yani dosya içerisindeki ilk tanımlanan Edit metodu), filmin ID parametresini alır ve Entity Framework'te varsayılan olarak bulunan Find metodunu kullanarak ilgili filmi arar. Devamında seçilen filmi Edit view'ına aktarır. Eğer istenen film bulunamazsa, HttpNotFound cevabını geri döndürülür.

Scaffolding sistemi Edit view'ını yaratırken, Film sınıfını inceledi ve sınıfın her özelliği için <label> ve <input> elemanlarını ekleyecek kodu oluşturdu. Aşağıdaki örnekteki, Visual Studio scaffolding sistemi tarafından oluşturulan Edit view'ına bakabiliriz:

@model MVCFilm.Models.Film

@{
    ViewBag.Title = "Edit";
}
<h2>Edit</h2>
@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()    
    <div class="form-horizontal">
        <h4>Movie</h4>
        <hr />
        @Html.ValidationSummary(true)
        @Html.HiddenFor(model => model.ID)

        <div class="form-group">
            @Html.LabelFor(model => model.Baslik, new {@class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Baslik)
                @Html.ValidationMessageFor(model => model.Baslik)
            </div>
        </div>
        <div class="form-group">
            @Html.LabelFor(model => model.CikisTarihi, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.CikisTarihi)
                @Html.ValidationMessageFor(model => model.CikisTarihi)
            </div>
        </div>
        @*Kodun kısa görünmesi için Tipi ve Fiyati özellikleri çıkartıldı.*@        
        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Save" class="btn btn-default" />
            </div>
        </div>
    </div>
}
<div>
    @Html.ActionLink("Back to List", "Index")
</div>
@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}
View şablonunun en üstünde, @model MVCFilm.Models.Film ifadesine sahip olduğuna dikkat edelim — Bu ifade, view'ın, Film tipinde bir model beklemesi gerektiğini belirler.

Scaffolding ile otomatik olarak oluşturulan kod, HTML kodlarını üretmek için birçok yardımcı metot (helper) kullanır:

  • Html.LabelFor yardımcısı, "Başlık", "Çıkış Tarihi", "Tipi", veya "Fiyatı" gibi alanların gösterilmesinde kullanılır.
  • Html.EditorFor yardımcısı ise bir HTML <input> elemanını işler.
  • Html.ValidationMessageFor yardımcısı ise, uygulandığı özellik ile ilişkili doğrulama mesajlarının gösterilmesinde kullanılır.
Uygulamamızı çalıştıralım ve /Filmler URL'ine gidelim. Edit linkine tıklayalım ve tarayıcı içerisinde sağ tıklayıp çıkan menüde kaynağı görüntüle'yi seçelim. Form elemanının kodu aşağıdaki şekildedir.

<form action="/Filmler/Edit/1" method="post">
   <input name="__RequestVerificationToken" type="hidden" value="UxY6bkQyJCXO3Kn5AXg-6TXxOj6yVBi9tghHaQ5Lq_qwKvcojNXEEfcbn-FGh_0vuw4tS_BRk7QQQHlJp8AP4_X4orVNoQnp2cd8kXhykS01" />  <fieldset class="form-horizontal">
      <legend>Film</legend>

      <input data-val="true" data-val-number="The field ID must be a number." data-val-required="The ID field is required." id="ID" name="ID" type="hidden" value="4" />

      <div class="control-group">
         <label class="control-label" for="Baslik">Baslik</label>
         <div class="controls">
            <input class="text-box single-line" id="Baslik" name="Baslik" type="text" value="Yenilmezler (Avengers)" />            <span class="field-validation-valid help-inline" data-valmsg-for="Baslik" data-valmsg-replace="true"></span>         </div>
      </div>

      <div class="control-group">
         <label class="control-label" for="CikisTarihi">Çıkış Tarihi</label>
         <div class="controls">
            <input class="text-box single-line" data-val="true" data-val-date="The field çıkış Tarihi must be a date." data-val-required="The Release Date field is required." id="CikisTarihi" name="CikisTarihi" type="date" value="04/04/2012" />            <span class="field-validation-valid help-inline" data-valmsg-for="ReleaseDate" data-valmsg-replace="true"></span>
         </div>
      </div>

      <div class="control-group">
         <label class="control-label" for="Tipi">Tipi</label>
         <div class="controls">
            <input class="text-box single-line" id="Tipi" name="Tipi" type="text" value="Aksiyon" />            <span class="field-validation-valid help-inline" data-valmsg-for="Tipi" data-valmsg-replace="true"></span>         </div>
      </div>

      <div class="control-group">
         <label class="control-label" for="Fiyati">Fiyati</label>
         <div class="controls">
            <input class="text-box single-line" data-val="true" data-val-number="The field Fiyati must be a number." data-val-required="The Fiyati field is required." id="Fiyati" name="Fiyati" type="text" value="7.99" />            <span class="field-validation-valid help-inline" data-valmsg-for="Fiyati" data-valmsg-replace="true"></span>         </div>
      </div>

      <div class="form-actions no-color">
         <input type="submit" value="Save" class="btn" />
      </div>
   </fieldset>
</form>
HTML <form> elemanı içerisindeki <input> elemanları, /Filmler/Edit URL'ine gönderilmek için atanan metodun özellikleridirler. Save butonuna tıklanıldığı zaman form verileri sunucuya gönderilecektir. İkinci satır ise, @Html.AntiForgeryToken() fonksiyonu tarafından üretilen, saklanmış (hidden) XSRF token'ını gösteriyor.

POST İsteğinin İşlenmesi

Aşağıdaki liste, Edit action metodunun HttpPost versiyonunu göstermektedir.

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include="ID,Baslik,CikisTarihi,Tipi,Fiyati")] Film film)
{
    if (ModelState.IsValid)
    {
        db.Entry(film).State = EntityState.Modified;
        db.SaveChanges();
        return RedirectToAction("Index");
    }
    return View(film);
}
ValidateAntiForgeryToken özelliği, view'daki @Html.AntiForgeryToken() metodunun çalıştırılması ile oluşturulan XSRF token'ını doğrulamaya yarıyor.

ASP.NET Model Binder, post edilen form değerlerini alır ve gelen verilerden bir Film nesnesi oluşturulur. ModelState.IsValid metodu, bir film nesnesini değiştirmek için kullanılan form tarafından iletilen verileri doğrular. Eğer veriler uygun ise, film verileri FilmDBContext'teki Filmler collection'ına eklenir. Yeni film verileri, FilmDBContext'in SaveChanges metodu çağırılarak veritabanına kaydedilir. Veriler kaydedildikten sonra kullanıcı, FilmlerController sınıfının filmlerin gösteren ve değişiklikleri uygulayan Index action metoduna  tekrar yönlendirir.

Tarayıcıda verilerin yanlış girilmesi halinde, bir hata mesajı gösterilir. Kullanıcı Javascript'i kapatsa bile, sunucu tarafında iletilen verilerin kontrolü yapılır ve yanlış girilen form verileri hata mesajlarıyla birlikte tekrar gösterilir. Sonraki yazımızda doğrulama kısmına daha detaylı bir şekilde bakacağız.

Edit.cshtml view şablonundaki Html.ValidationMessageFor helper'ları, belirli hata mesajlarının görüntülenmesinden sorumludurlar.


Bütün HttpGet metodları, birbirlerine benzer bir örüntüye sahiptirler: bir film nesnesini/nesnelerini alırlar ve model'ı view'a iletirler. Create metodu boş bir nesneyi, Create view'ına iletir. Create, edit, delete veya başka bir metod, verileri değiştirmek için [HttpPost] özelliğini kullanarak aşırı yüklenmeleri gereklidir. HTTP GET metodu içerisinde verileri değiştirmek, bu blogda da anlatıldığı gibi güvenlik riskine yol açar. Ayrıca GET metodu içerisinde verilerin değiştirilmesi, GET isteklerinin, "uygulamanın durumunu değiştirmemesini" gerektiren HTTP pratiklerine ve mimarisel REST şablonuna aykırıdır. Diğer bir deyişle bir GET işlemi, kalıcı veriler üzerinde değişiklik yapmayan, güvenli bir işlem olmalıdır.

Uygulamanın Yerelleştirilmesi

Uygulamamız ingilizce olmadığı için nokta ile sayıları ayırmak ve Amerikan tarih formatını kullanmak zorunda kalıyoruz. Uygulamamızı yerelleştirmek için https://github.com/jquery/globalize'dan ilgili cultures/globalize.cultures.js dosyasını indirmeli ve globalize.js dosyasını projemize dahil etmeliyiz. NuGet'tan da jQuery non-English doğrulamasını indirebilriz.

1. Önelikle Tools menüsünden NuGet Package Manager'a gelelim ve Manage NuGet Packages for Solution'ı seçelim.

2. Sol kısımdan Online'ı seçelim.
3. Sağ üstteki arama çubuğuna Globalize yazalım.


Install'a tıkladığımızda Scripts\jquery.globalize\globalize.js dosyası projemize eklenecektir. Fakat Scripts\jquery.globalize\cultures\ klasörü birçok Javascript dosyası içeridiği için bu paketin yüklenmesi birkaç dakikayı alabilir.

Views\Filmler\Edit.cshtml dosyasına aşağıdaki değişiklikleri uygulayalım:
@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")

<script src="~/Scripts/globalize/globalize.js"></script>
<script src="~/Scripts/globalize/cultures/globalize.culture.@(System.Threading.Thread.CurrentThread.CurrentCulture.Name).js"></script>
<script>
    $.validator.methods.number = function (value, element) {
        return this.optional(element) ||
            !isNaN(Globalize.parseFloat(value));
    }
    $(document).ready(function () {
        Globalize.culture('@(System.Threading.Thread.CurrentThread.CurrentCulture.Name)');
    });
</script>
<script>
    jQuery.extend(jQuery.validator.methods, {
        range: function (value, element, param) {
            //Ondalık değeri ayırmak için Globalization eklentisini kullanın
            var val = Globalize.parseFloat(value);
            return this.optional(element) || (
                val >= param[0] && val <= param[1]);
        }
    });
    $.validator.methods.date = function (value, element) {
        return this.optional(element) ||
            Globalize.parseDate(value) ||
            Globalize.parseDate(value, "dd-MM-yyyy");
    }
</script>
}
Her Edit view'ına bu kodu eklemek yerine, layout dosyasına taşıyabilirsiniz. Ayrıntılı bilgiyi burada bulabilirsiniz.

Eğer doğrulama (validation) şu an çalışmıyorsa, geçici bir çözüm olarak bilgisayarınızınUS English dilini kullanmasını veya tarayıcınızın Javascript'i devre dışı bırakmasını sağlayabilirsiniz. Bilgisayarınızın US English dilini kullanması için, projenin kök dizinindeki web.config dosyasına aşağıdaki globalization elemanını ekleyebilirsiniz.

  <system.web>
    <globalization culture ="en-US" />
    <!--diğer elemanlar kodun sade görünmesi için çıkarıldı-->
  </system.web>
Sonraki yazımızda arama işlemini uygulamamıza ekleyeceğiz.


Kaynaklar: ASP.NET

Hiç yorum yok:

Yorum Gönder