1 Ağustos 2014 Cuma

MVC Film Uygulaması - Arama İşlemleri (Part VII)

Bu bölümde Index action metoduna arama yeteneği kazandıracağız. Böylece sitemiz giren kullanıcılar film türüne veya adına göre arama yapabilecekler.

Index Formunun Güncellenmesi

FilmController sınıfımızdaki Index action metodunu değiştirerek yola koyulabiliriz. Metodu şu hale getirelim:
public ActionResult Index(string aranacakKelime) 
{           
    var filmler = from f in db.Filmler 
                 select f; 
 
    if (!String.IsNullOrEmpty(aranacakKelime) 
    { 
        filmler = filmler.Where(a => a.Baslik.Contains(aranacakKelime)); 
    } 
 
    return View(filmler); 
}
Index metodunun ilk satırında tüm filmleri getirmek için LINQ sorgusu oluşturduk:
var filmler = from f in db.Filmler
                 select f;
Şu an sadece sorguyu tanımladık, henüz veritabanında çalıştırmadık.

Eğer aranacakKelime parametresi gerçekten bir string'i içeriyorsa, filmler sorgusu, aranacakKelime parametresinin değeri ile filtreleme yapabilmesi için aşağıdaki şekilde değiştirilir:
    if (!String.IsNullOrEmpty(aranacakKelime)) 
    { 
        filmler = filmler.Where(a => a.Baslik.Contains(aranacakKelime)); 
    }
Yukarıdaki a => a.Baslik kod parçası bir Lambda ifadesidir.Lambda'lar, yukarıdaki Where metodu gibi metot-temelli LINQ sorgularında parametre olarak kullanılır. LINQ metodları, tanımlandıkları anda veya Where gibi metodlar çağıracak bir şekilde olsalar dahi o kod bloğunda çalışmazlar. Bunun yerine sorgunın çalıştırılması ertelenir, yani bir ifadenin ölçülmesi, değerler üzerinde gezilene dek veya ToList metodu çağırılıncaya dek ertelenir. Örneğin aşağıdaki sorgu, Index.cshtml dosyasında çalıştırılacak.

Uygulamamızı çalıştıralım ve /Filmler/Index'e gidelim. URL'e ?aranacakKelime=Yenilmezler gibi bir sorgu kelimesi ekleyelim. URL'e gittiğimizde filmler aşağıdaki şekilde filtrelenecektir:


Eğer Index metodunu, id isimli parametre alacak şekilde değiştirirsek id parametresi,  App_Start\RouteConfig.cs dosyasında varsayılan olarak tanımlanan rotadaki {id} yer tutucusu ile eşleşecektir.
{controller}/{action}/{id}
Index metodonu aşağıdaki şekilde güncelleyebiliriz:
public ActionResult Index(string id) 
{ 
    string aranacakKelime = id; 
    var filmler = from f in db.Filmler 
                 select f; 
 
    if (!String.IsNullOrEmpty(aranacakKelime)) 
    { 
        filmler = filmler.Where(a => a.Baslik.Contains(aranacakKelime)); 
    } 
 
    return View(filmler); 
}
Artık aranacak filmin adını sorgu string'i şeklinde değil de, rota verisi olarak (yani bir URL bölümünde) gönderebiliriz:


Şu an çok temiz bir URL yapımızla işlerimizi yürütür hale geldik. Ancak sayfamıza gelecek kullanıcılardan, her defasında URL'i değiştirerek film aramalarını bekleyemeyiz. Bu yüzden filmleri filtrelemek için arayüz kısmına bir şeyler ekleyeceğiz. Ama öncelikle Index metodumuzu eski haline döndürelim:
public ActionResult Index(string aranacakKelime) {           
     var filmler = from f in db.Filmler 
                  select f; 
 
    if (!String.IsNullOrEmpty(aranacakKelime)) 
    { 
        filmler = filmler.Where(a => a.Baslik.Contains(aranacakKelime)); 
    } 
 
    return View(filmler); 
}
Views\Filmler\Index.cshtml dosyasını açalım ve  @Html.ActionLink("Create New", "Create")'in hemen altına aşağıdaki işaretli satırları ekleyelim:
@model IEnumerable<MvcFilm.Models.Film> 
 
@{ 
    ViewBag.Title = "Index"; 
} 
 
<h2>Index</h2> 
 
<p> 
    @Html.ActionLink("Create New", "Create") 
     
     @using (Html.BeginForm()){    
         <p> Adı: @Html.TextBox("aranacakKelime") <br />   
         <input type="submit" value="Filtrele" /></p> 
        } 
</p>
Html.BeginForm helper'ı bir <form> etiketi oluşturmaya yarar. Bu helper, kullanıcı Filtrele butonuna tıklayarak formu gönderdiğinde, formun kendisine tekrar geri gönderilmesini sağlıyor.

Uygulamamızı çalıştıralım ve arama yapalım.


Farkettiysek HttpPost ile aşırı yüklenen bir Index metodumuz bulunmamakta. İhtiyacımız da yok zaten, çünkü metot, uygulamanın durumunu değiştirmiyor yani butona tıkladığımızda veritabanından veriler gelmiyor, sadece halihazırda getirilen filmler arasından filtreleme işlemi yapıyor.

Aşağıdaki HttpPost Index metodunu ekleyebiliriz. Bu durumda, action çağırıcısı HttpPost Index metoduyla eşleşecektir ve HttpPost Index metodu aşağıdaki görüntüdeki gibi çalışacaktır.
[HttpPost] 
public string Index(FormCollection fc, string aranacakKelime) 
{ 
    return "<h3>[HttpPost]Index metodu çalıştı: " + aranacakKelime + "</h3>"; 
}

Ancak bu HttpPost'lu Index metodunu eklesek bile, tamamının nasıl kodlayacağımıza dair bir sıkıntı var. Mesela kullanıcı yaptığı bir arama sonucunun URL'ini bir arkadaşına göndermek isteyebilir. Fakat farkedersek HTTP POST isteğinin URL'i, GET isteğinin URL'i ile birebir aynı (http://localhost:61049/Filmler). Yani URL'in kendisinde bir arama bilgisi bulunmuyor ve şu an, aranacak film bilgisi, sunucuya form verisi olarak iletiliyor. Yani kullanıcı, URL'i alarak arama bilgisini kaydedebilmesi veya bir arkadaşına gönderebilmesi mümkün değil.

Çözüm ise POST isteğininin, arama bilgisini URL'e eklemesini ve HttpGet'li Index metoduna yönlendirmesini belirleyecek bir BeginForm yapısı kullanmaktır. BeginForm metodu içerisindeki parametreleri aşağıdaki şekilde  değiştirelim:
@using (Html.BeginForm("Index","Filmler",FormMethod.Get))
Artık bir arama yaptığımızda URL, sorgu kelimelerini içerecek durumda. Ayrıca arama isteği, HttpPost Index metodumuz olsa bile şu an HttpGet Index action metoduna gidiyor.


Film Türüne Göre Arama Yapmak

Daha önceden Index metodunun HttpPost'lu olarak eklediğimiz versiyonunu silelim.
Şimdi kullanıcıların film türüne göre arama yapabilmesi için bir özellik ekleyeceğiz. Index metodunun içini aşağıdaki şekilde değiştirelim:
public ActionResult Index(string filmTipi, string aranacakKelime)
{
    var FilmTipiListesi = new List<string>();

    var FilmTipiSorgusu = from f in db.Filmler
                   orderby f.Tipi
                   select f.Tipi;

    FilmTipiListesi.AddRange(FilmTipiSorgusu.Distinct());
    ViewBag.filmTipi = new SelectList(FilmTipiListesi);

    var filmler = from f in db.Movies
                 select f;

    if (!String.IsNullOrEmpty(aranacakKelime))
    {
        filmler = filmler.Where(a => a.Baslik.Contains(aranacakKelime));
    }

    if (!string.IsNullOrEmpty(filmTipi))
    {
        filmler = filmler.Where(x => x.Tipi == filmTipi);
    }

    return View(filmler);
}
Değiştirdiğimiz Index metodu artık filmTipi adında yeni bir parametre alıyor. Kodun ilk birkaç satırı, veritabanından gelen film türlerini tutmaya yarayan bir List objesi oluşturuyor.

Aşağıdaki kod ise, veritabanından bütün film türlerinin getirilmesini sağlayan bir LINQ sorgusunu oluşturuyor.

var FilmTipiSorgusu = from d in db.Filmler
                          orderby d.Tipi
                          select d.Tipi;
Koddaki List yapısının AddRange metodu, tüm farklı türlerin (distinct) listeye eklenmesini sağlıyor (Eğer Distinct ifadesi olmasaydı, aynı birçok tür eklenecekti — örneğin aksiyon türü iki defa listeye eklenecekti). Sonra kod, ViewBag.filmTipi nesnesinde film türlerinin bir listesini tutuyor. Film türleri gibi olan kategori verileri ViewBag'de bir SelectList nesnesi olarak tutulur, ve devamında bu kategori verisine aşağıya doğru açılan bir liste kutucuğu (dropdown list box) ile erişim yapılması MVC uygulamalarda çok yaygın olarak kullanılan bir yaklaşımdır.

Aşağıdaki kod, filmTürü parametresinin nasıl kontrol edildiğini gösteriyor:
   if (!string.IsNullOrEmpty(filmTipi))
   {
      filmler = filmler.Where(x => x.Tipi == filmTipi);
   }

Türe Göre Arama Yapmak İçin Index View'ı Güncellemek

Views\Movies\Index.cshtml dosyasındaki TextBox helper'ının hemen üstüne Html.DropDownList helper'ını ekleyelim. Eklenmiş hali ile şu şekilde görünmesi gerekiyor:

@model IEnumerable<MvcFilm.Models.Film>
@{
    ViewBag.Title = "Index";
}
<h2>Index</h2>
<p>
    @Html.ActionLink("Create New", "Create")
    @using (Html.BeginForm("Index", "Filmler", FormMethod.Get))
    {
    <p>
        Türü: @Html.DropDownList("filmTipi", "Herhangi Biri")
        Adı: @Html.TextBox("aranacakKelime")
        <input type="submit" value="Filtrele" />
    </p>
    }
</p>
<table class="table">
Aşağıdaki kodda:
@Html.DropDownList("filmTipi", "Herhangi Biri")
filmTipi parametresi, ViewBag'deki bir IEnumerable<SelectListItem>'ı bulmak amacıyla, DropDownList helper'ına bir anahtar sağlamış oluyor. Hatırlarsak ViewBag'i, Index action metodu içerisinde tanımlamıştık:
public ActionResult Index(string filmTipi, string aranacakKelime)
{
    var FilmTipiListesi = new List<string>();

    var FilmTipiSorgusu = from f in db.Filmler
                   orderby f.Tipi
                   select f.Tipi;

    FilmTipiListesi.AddRange(FilmTipiSorgusu.Distinct());
    ViewBag.filmTipi= new SelectList(FilmTipiListesi);

    var filmler = from f in db.Filmler
                 select f;

    if (!String.IsNullOrEmpty(aranacakKelime))
    {
        filmler = filmler.Where(f => f.Baslik.Contains(aranacakKelime));
    }

    if (!string.IsNullOrEmpty(filmTipi))
    {
        filmler = filmler.Where(f => f.Tipi== filmTipi);
    }

    return View(filmler);
}
"Herhangi Biri" parametresi ise dropdown list elemanında önceden seçili olarak atanmış bulunuyor. Eğer aşağıdaki kodu yazsaydık:
@Html.DropDownList("filmTipi", "Aksiyon")
dropdown list elemanında "Aksiyon" türü varsayılan olarak seçili halde bulunacaktı. "Herhangi Biri"'nin listede yer almamasının nedeni ise zaten veritabanında böyle bir türün bulunmamasından dolayı. Yani eğer hiçbir türü seçmeden arama yaparsak, filmTipi sorgu string'i boş olarak ele alınır. Uygulamamızı çalıştıralım ve http://localhost:xxxxx/Filmler sayfasına gidelim. Tür olarak birini seçerek filmimizi arayalım:


Bu yazımızda, kullanıcıların film adı ve türünü girerek arama işlemlerini gerçekleştirebilecek şekilde Index action metodunu oluşturduk. Sonraki bölümde Film modelimize nasıl bir property ekleyeceğimizi ve otomatik olarak bir test veritabanı oluşturacak bir ilklendirici (initializer) nasıl eklenir onu göreceğiz.

Kaynaklar: ASP.NET

3 yorum: