Sayfalar

12 Mart 2013 Salı

ASP.NET MVC İÇ İÇE BAĞIMLI DROPDOWNLIST (CASCADING DROPDOWNLIST)

Özellikle ülke, il, ilçe gibi birbirine bağlı olan nesnelerin dropdownlist içerisinde gösteriminde kullanılır. Yani bir dropdownlist diğerinin bir elemanına bağlı olarak doluyorsa.

Yine öncelikle kodları paylaşıyorum. Gerekli yerlerde yorum satırı olarak açıklamalar yapacağım.

Ulkeler.cs
public class Ulkeler
{
    public virtual int Id { get; set; }
    public virtual string Ad { get; set; }

    public virtual ICollection<Sehirler> Sehirler { get; set; }
}

Sehirler.cs
public class Sehirler
{
    public virtual int Id { get; set; }
    public virtual string Ad { get; set; }
    public virtual int PlakaKodu { get; set; }
    public virtual int UlkeId { get; set; }

    public virtual Ulkeler Ulke { get; set; }
}

KonumViewModel.cs
public class KonumViewModel
{
    public int Id { get; set; }

    [Required(ErrorMessage="{0} alanı gereklidir.")]
    [Display(Name="Ülkeler")]
    public int UlkeId { get; set; }

    [Required(ErrorMessage = "{0} alanı gereklidir.")]
    [Display(Name = "Şehirler")]
    public int SehirId { get; set; }
}

KonumController.cs
public class KonumController : Controller
{
    //
    // GET: /Konum/

    public ActionResult Index()
    {
        KonumViewModel model = new KonumViewModel();

        // örnek ülkeler listesi oluşturuyoruz.
        var ulkeler = new Ulkeler[] {
            new Ulkeler{Id=1,Ad ="Türkiye"},
            new Ulkeler{Id=2,Ad ="Azerbaycan"}
        };

        // yukarıda oluşturduğumuz ülkeler listesini
        // selectlist nesnesi içerisinde ViewBag ile
        // view sayfamıza göndereceğiz...
        ViewBag.Ulkeler = new SelectList(ulkeler, "Id", "Ad");

        return View(model);
    }

    public ActionResult Sehirler(int ulkeId)
    {
        List<Sehirler> sehirler = new List<Sehirler>();

        // eğer ülke id = 1 ise türkiyenin illerini ekle
        if (ulkeId == 1)
        {
            sehirler.Add(new Sehirler { Id = 1, Ad = "Malatya", PlakaKodu = 44 });
            sehirler.Add(new Sehirler { Id = 2, Ad = "İzmir", PlakaKodu = 35 });
            sehirler.Add(new Sehirler { Id = 3, Ad = "İstanbul", PlakaKodu = 34 });
            sehirler.Add(new Sehirler { Id = 4, Ad = "Ankara", PlakaKodu = 06 });
            sehirler.Add(new Sehirler { Id = 5, Ad = "Manisa", PlakaKodu = 45 });
        }

        // eğer ülke id = 2 ise azerbaycanın illerini ekle
        if (ulkeId == 2)
        {
            sehirler.Add(new Sehirler { Id = 1, Ad = "Şeki", PlakaKodu = 55 });
            sehirler.Add(new Sehirler { Id = 2, Ad = "Bakü", PlakaKodu = 90 });
            sehirler.Add(new Sehirler { Id = 3, Ad = "Sumgait", PlakaKodu = 34 });
        }

        // şehirler listesini Json olarak gönderiyoruz.
        return Json(sehirler);
    }
}

Index.cshtml
<script type="text/javascript">
    $(document).ready(function () {
        // ülke seçildiğinde bu fonksiyon çalışır
        $('#UlkeId').change(function () {
            // seçilen ülkenin id sini al
            var ulkeId = $(this).val();

            // secilen ülkenin id sini kullanarak Konum controller
            // sınıfı içerisindeki Sehirler metoduna çağrıda bulunuyoruz.
            // bu metod dan dönen listeyi kullanarak .each fonksiyonu ile
            // sehirleri dolduruyoruz...
            if (ulkeId != null && ulkeId != '') {
                $.ajax({
                    type: "post",
                    url: '@Url.Action("Sehirler", "Konum")',
                    data: { ulkeId: ulkeId },
                    success: function (sehirler) {
                        $.each(sehirler, function (index, sehir) {
                            $('#SehirId').append($('<option/>', {
                                value: sehir.Id,
                                text: sehir.Ad
                            }));
                        });
                    },
                    error: function () {
                        // bu kısımda eğer ajax işlemi başarısız ise
                        // hata mesajı verebiliriz.
                        alert("Hata");
                    },
                    beforeSend: function () {
                        // bu kısımda form postalanmadan önce yapılacak
                        // işler belirlenebilir. mesela postalama başladığı
                        // anda loading resmi görüntüleyebiliriz.
                    },
                    complete: function () {
                        // bu kısımda form postalandıktan sonra yapılacak
                        // işler belirlenebilir. mesela postalama bittiği
                        // anda loading resmi gizleyebiliriz.
                    }
                });
            }
        });
    });
</script>

@using (Html.BeginForm())
{
    <fieldset>
        <legend>KonumViewModel</legend>
        <div class="editor-label">
            @Html.LabelFor(model => model.UlkeId)
        </div>
        <div class="editor-field">
            @Html.DropDownListFor(model => model.UlkeId, ViewBag.Ulkeler as SelectList, "--- Ülke Seçiniz ---")
            @Html.ValidationMessageFor(model => model.UlkeId)
        </div>
        <div class="editor-label">
            @Html.LabelFor(model => model.SehirId)
        </div>
        <div class="editor-field">
            @Html.DropDownListFor(model => model.SehirId, Enumerable.Empty<SelectListItem>(), "--- Şehir Seçiniz ---")
            @Html.ValidationMessageFor(model => model.SehirId)
        </div>
        <p>
            <input type="submit" value="Kaydet" />
        </p>
    </fieldset>
}

Yukarıda yazdığım kodlar sistemin çalışmasını anlatmak içindi. Genelde bu işlemler veritabanından gelen verilerle yapılır.

Yani eğer verilerimizi veritabanından çekiyor olsaydık ve sınıflarımız veritabanı tablolarını temsil ediyor olsaydı (EntityFramework). O zaman controller sınıfımız aşağıdaki gibi olurdu.

KonumController.cs
public ActionResult Index()
{
    KonumViewModel model = new KonumViewModel();
    using (TestDB db = new TestDB())
    {
        // selectlist nesnesi içerisinde ViewBag ile
        // view sayfamıza göndereceğiz...
        ViewBag.Ulkeler = new SelectList(db.Ulkeler.ToList(), "Id", "Ad");

        return View(model);
    }
}

public ActionResult Sehirler(int ulkeId)
{
    using (TestDB db = new TestDB())
    {
        // seçilen ulkenin id sine eşit olan şehirler seciliyor.
        List<Sehirler> sehirler = db.Sehirler.Where(x => x.UlkeId == ulkeId).ToList();

        // şehirler listesini Json olarak gönderiyoruz.
        return Json(sehirler);
    }
}

Yaptığımız uygulamanın ekran görüntüleri:

 


GÜNCELLEME

Veritabanından çekerken gerekli olacak alanları çekmek gerekiyor. Yani Şehirler listesini veritabanından çekerken aşağıdaki gibi değiştirin kodu:

// seçilen ulkenin id sine eşit olan şehirler seciliyor.
var sehirler = db.Sehirler.Where(x => x.UlkeId == ulkeId).Select(x => new { Id = Id, Ad = Ad }).ToList();


Bunun sebebi EF ile veritabanından veri çekerken veritabanı ile alakalı metadata bilgileri ve daha kompleks bir veri yapısının gelmesi.

24 yorum:

  1. Bu konuyu paylaşmanız çok güzel.Asp.Net mvc yapısındaki çok karşılaşılan ve çözümü zor bulunan konulardan birisi idi.

    YanıtlaSil
  2. Veritabanından çekip json olarak göndermeye çalışınca hataya düşüyor. Siz denediniz mi acaba bu şekilde yapmayı. Belki ufak bir değişiklik daha istiyor olabilir.

    YanıtlaSil
    Yanıtlar
    1. Hata mesajını, kullandıgınız tarayıcının geliştirici araçlarını kullanarak yakalamaya çalışın, hata mesajını yazın, yardımcı olmaya çalışayım.

      Sonda eklediğim ekran görüntüsü, çalışan uygulamadan bir örnek, denemediğim kodları paylaşmıyorum. Zaten bundan sonra, örneğini hazırladığım projeyide ekleyeceğim...

      Sil
    2. Veritabanından EF ile çekilen veri içerisinde EF nin metadata bilgileride olduğundan ve ayrıca daha kompleks bir veriyapısı olduğundan Ajax bu veri yapısını kullanamıyor. Bundan dolayı veri tabanından çekerken Select ile gerekli olan alanları çekmek gerekiyor. Kodu güncelledim. Tekrardan bakabilirsiniz. Bu hata için kusura bakmayın...

      Sil
    3. eğer bu kod calısyorsa bravo size kac gundur aradığım bişeydi,Eger calısıyorsa sizi sürekli takip edeceğim hem net bi şekilde acıkladığınız için hem de suana kadar yapılan en mantıklı çözüm yolu olarak geldiğiiçin

      Sil
  3. Merhaba,
    Yukarda ki kodlarda anlamadığım bir iki şey var, eğer yardımcı olursan sevinirim.

    1. public 'ten sonra neden virtual kullandın. Virtual hangi durumlarda kullanılır.
    2. ICollection kullanımı anlamadım.

    YanıtlaSil
    Yanıtlar
    1. virtual anahtar kelimesi, abstract gibidir. yani, bir alt sınıfta, degiştirebileceğimiz anlamına gelir. ICollection sınıfı ise, tüm liste tiplarini (List, Arraylist, IEnumerable, IEquerable, ...) kapsar. Bundan dolayıda ICollection kullanırsanız tip donusumune gerek kalmaz.

      Sil
    2. Virtual kullanarak oluşturduğumuz veri yapısı için bellekte yer ayrılıyor. Yani nesne null olarak değilde içi boş olarak oluşturuluyor. Yeni ilişkili iki veri yapısı için; bu veri yapılarından birisi oluşturulunca, digeride boş olarak oluşturuluyor. Ama virtual kullanmazsak, diğeri null olarak oluşuyor. Örneğin, Sehir oluşturduğumuzda, eğer ülkeyi virtual tanımlamıssak, Sehir.Ulke.Ad dediğimizde, hata almayız ama boş bir değer gelir. Virtual tanımlamamıssak, Sehir.Ulke.Ad dediğimiz anda hata alırız. Çünkü, o şehir için ulke değeri null dır. Null olan bir sınıfın "Ad" isimli üyesine ulaşamayız.

      Sil
  4. harbiden bu virtual kafa karıştırıcı bsydi bnde yeni başladım araştırdım ama net açık bir bilgiye ulaşamadım siz daha iyi anlatmışsınız ...
    piyasada adam gibi MVC anlatan kitap yok inanabiliyormusunuz ?

    YanıtlaSil
  5. Merhaba Hocam Ben de Bunu il ilçye şeklinde uyeleri listelemek için kulandım yanlız contorler tarafında baktığım da breakpoint le ilçeler geliyor il Id sine göre ama dropdownlisfor da gelmiyor
    @Html.DropDownListFor(model => model.İlceID, Enumerable.Empty(), "--- İlce Seçiniz ---") ACEBA BURDANMI kaynaklı

    YanıtlaSil
    Yanıtlar
    1. Bu yorum yazar tarafından silindi.

      Sil
    2. Merhaba,

      Html içerisine elementlere yazdırdığınız verileri @html nesnesini kullanarak değilde açık yazmanıç her açıdan farklılık veririmlilik ve kolaylılık sağlayacaktır. Şöyle;

      selectt>
      TAGoptionn value="0">Seçiniz TAGoptionn> >>>>>>>>>>>>>>>>>>>>>> başlangıç değeri.
      @{
      foreach(var item in model.count)
      TAGoptionn value="@item.İlceID">@item.İlceAd TAGoptionn>
      }

      TAGselectt>

      Diyeceksiniz ki kardeşim tek kodla hallediliyor. Halledilebilir eve ama bu yöntem her zaman avantaj kazandırır. Bu şekilde Html elementlerin kontrolü tamamen sizdedir. Diğer türlü 1 parmağınız kilitlidir. Bilginize İyi Çalışmalar. Hatasız Kod Olmaz :)

      Tag yazdığım yerlere etiketi tamamlarsanız anlamlı olur. Google amca kızdı izin vermiyor

      Sil
  6. Hocam benim controler tarafındaki kodum şöyle
    [HttpPost]
    public ActionResult Sehirler(int ulkeId)
    {

    // seçilen ulkenin id sine eşit olan şehirler seciliyor.
    var sehirler = db.İlceler.Where(x => x.SehirID == ulkeId).Select(x=> new { Id=x.Id, Ad =x.Ad }).ToList();

    // şehirler listesini Json olarak gönderiyoruz.
    return Json(sehirler);

    }
    il seçiyorum ama ilçeler dolmuyor

    YanıtlaSil
    Yanıtlar
    1. Ali Rıza bey,
      Paylaşım öncesi deniyorum kodları demişsiniz. Ama buradaki kodlar denenmemiş gibi gözüküyor.
      Ayrıca Ülkeler değiştikçe Şehirler sıfırlanmadığı için (append) oluyor. Yani her ülke değiştiğinde O ülkenin şehirleride ilave oluyor.
      Aslında bu mantığı ben sırasıyla İl, İlçe, Semt ve Mahalle seçimine adapte edeceğim. Böyle bir örnek çok daha gerçek hayat senaryosu olur diye düşünüyorum. Böyle bir çalışmanız oldumu ? Varsa bunu paylaşabilirmisiniz ?

      Sil
    2. Ben bu konuyu İL, İLÇE, SEMT ve MAHALLE olarak genişlettim.Ayrıca Türkiye 'nin tümü için MsSql script 'i de oluşturdum. Benim çözümümde tüm veriler veritabanı tablolarından geliyor. Yani gerçek hayat senaryosu. Dileyen ile paylaşabilirim. serhat@saysis.net. Dilerseniz size de gönderebilirim.

      Sil
    3. Öncelikle yine tekrar edeyim. Kodları denemeden siteye koymuyorum. Yukarıda da tekrar ettiğim gibi ekran görüntüsü çalışan uygulamadan alıntıdır. Bu sitede yazdıgım kodların tamamı github üzerinde proje halinde bulunuyor ve tüm projelede çalışıyor. Append konusunda haklısınız. Her append işleminden önce Empty ve ya Clear metodlarından biri çalıştırılabilir. Kodların çalışmama sebepleri kullanılan teknolojilerin versiyonlarından ve ya veri erişim metodundan(db ye bağlanmıyorum, baglandıgım da da nasıl kullanılması gerektigini ayrıca yazmışım) da kaynaklanır. Paylaşımlar 1-2 sene öncesine ait.

      İkinci olarak elinize sağlık. Güzel bir iş yapmışsınız. Yaptığınız projeyi github üzerinden paylaşıp link verebilirsiniz. Böylece herkes için faydalı bir uygulama olmuş olur. Github da paylaşırsanız, bu siteye girmeyenler de uygulamanızı görebilir ve istifade edebilir.

      Sil
    4. Githubda paylaştım bu kodlamayı. https://github.com/sseral/MVC-Cascading-DropDowns-4-dropdowns- linkinden ulaşabilirsiniz. (Linkin sonundaki -'ye dikkat !)

      Anlamadığınız bir şey olursa bana serhat@saysis.net ile veya (0533) 468 25 10 numaradan ulaşabilirsiniz.


      Sil
  7. Bu yorum yazar tarafından silindi.

    YanıtlaSil

  8. function ComboDoldur(Combo1,Combo2, Datam,_Controller,_Action)
    {
    var c1 = $(Combo1).val();

    $.ajax({
    type: "post",
    url: '@Url.Action("Sehirler", "Konum")',
    data: { SorguId : c1 },
    success: function (sehirler) {
    $.each(sehirler, function (index, sehir) {
    $(Combo2).append($('', { value: sehir.Id, text: sehir.Ad }));
    });
    }
    });
    }

    dropdownlistleri yukarıdaki fonksiyon ile doldurmak istiyorum ama
    url: '@Url.Action("Sehirler", "Konum")', kısmına
    sehirler için _controler parametresini
    konum için action parametresini set edemedim acaba yapılabilirmi yöntemi nedir

    saygılarımla

    YanıtlaSil
  9. Merhabalar iyi çalışmalar diliyorum ben bu işlemi tek bir dropdownlis içinde yapmaya çalışıyorum ama olmadı yardımcı olabilir misiniz

    YanıtlaSil
  10. teşekkürler çok işime yaradı, ayrıca veritabanından çekerken modeldeki classları kullanmak yetiyor kendimiz class yazmamıza gerek kalmıyor. json verisi için de new field oluşturmaya gerek yok veritabanındaki field name lerin aynısını yazınca sorun çıkmıyor

    YanıtlaSil
  11. Hocam, benim uygulamamda makina tipleri ve buna bağlı kapasite değerleri var.. bağımlı dropdownlistfor elementine, değişen makina tipne göre kapasite değerleri geliyor. Bunları kaydedebiliyorum ve hesaplamalarımı yapabiliyorum. Ancak sayfayı düzenlemek için geri çağırdığımda bağımlı dropdownlist değeri daha önce dropdownlistten seçmiş olduğum değer olmuyor, sayfa yeniden yüklenirken yine boş bir dropdownlist ile karşı karşıyayım. Sayfayı geri çağırdığımda kaydettiğim makina kapasite değerinin gelmesine ihtiyacım var. Yönlendirmeleriniz için minnettar olurum. Saygılarımla


    @Html.EnumDropDownListFor(x => x.MakinaTipId, "Seçiniz", new { @id = "Tip" })
    @Html.DropDownListFor(x => x.MakinaKapasite, Enumerable.Empty(), "---Kapasite Seçiniz---" )

    YanıtlaSil
  12. Hocam, hallettim teşekkür ederim, yormayayım sizi.. empty enumarable tanımlamak yerine controller da gerekli listeyi hazırlayıp view e gönderince sorun kalmadı..
    teşekkür ediyorum.. yayınınız ilham verici :)

    YanıtlaSil
  13. Allah razı olsun yana döne çözüm arıyordum, çözümümü bulmada çok yardımınız dokundu çok teşekkürler. Elinize, aklınıza sağlık.

    YanıtlaSil