Aracılığıyla paylaş


Verimli Veri Sayfalama Uygulama

Microsoft tarafından

PDF’yi İndir

Bu, ASP.NET MVC 1 kullanarak küçük ama eksiksiz bir web uygulaması oluşturmayı gösteren ücretsiz bir "NerdDinner" uygulaması öğreticisinin 8. adımıdır.

8. Adım, /Dinners URL'mize sayfalama desteği eklemeyi gösterir, böylece aynı anda 1000 akşam yemeği görüntülemek yerine, bir kerede yalnızca 10 yaklaşan akşam yemeği görüntüleriz ve son kullanıcıların SEO kolay bir şekilde listenin tamamında sayfalandırmalarına ve iletmelerine izin veririz.

ASP.NET MVC 3 kullanıyorsanız , MVC 3 ile Çalışmaya Başlama veya MVC Müzik Deposu öğreticilerini izlemenizi öneririz.

NerdDinner 8. Adım: Disk Belleği Desteği

Sitemiz başarılı olursa, yaklaşan binlerce akşam yemeği olacaktır. Kullanıcı arabirimimizin bu akşam yemeklerinin tümünü işleyecek şekilde ölçeklendirildiğinden ve kullanıcıların bunlara göz atmasına izin verdiğinden emin olmamız gerekir. Bunu etkinleştirmek için /Dinners URL'mize sayfalama desteği ekleyerek aynı anda 1000 akşam yemeği görüntülemek yerine yalnızca 10 yaklaşan akşam yemeği görüntüleyeceğiz ve son kullanıcıların tüm listeyi SEO dostu bir şekilde sayfalandırmalarına ve iletmelerine izin vereceğiz.

Index() Eylem Yöntemi Özet

DinnersController sınıfımızdaki Index() eylem yöntemi şu anda aşağıdaki gibi görünür:

//
// GET: /Dinners/

public ActionResult Index() {

    var dinners = dinnerRepository.FindUpcomingDinners().ToList();
    return View(dinners);
}

/Dinners URL'sine bir istek gönderildiğinde, yaklaşan tüm akşam yemeklerinin listesini alır ve ardından bunların listesini işler:

İnek Akşam Yemeği Yaklaşan Akşam Yemeği liste sayfasının ekran görüntüsü.

IQueryable<T'i anlama>

Iqueryable<T> , .NET 3.5'in bir parçası olarak LINQ ile sunulan bir arabirimdir. Disk belleği desteği uygulamak için yararlanabileceğiniz güçlü "ertelenmiş yürütme" senaryolarına olanak tanır.

DinnerRepository'de FindUpcomingDinners() yöntemimizden bir IQueryable<Dinner> dizisi döndüreceğiz:

public class DinnerRepository {

    private NerdDinnerDataContext db = new NerdDinnerDataContext();

    //
    // Query Methods

    public IQueryable<Dinner> FindUpcomingDinners() {
    
        return from dinner in db.Dinners
               where dinner.EventDate > DateTime.Now
               orderby dinner.EventDate
               select dinner;
    }

FindUpcomingDinners() yöntemimiz tarafından döndürülen IQueryable<Dinner> nesnesi, LINQ to SQL kullanarak veritabanımızdan Dinner nesnelerini almak için bir sorguyu kapsüller. Daha da önemlisi, sorgudaki verilere erişmeyi/verileri yinelemeyi deneyene veya üzerinde ToList() yöntemini çağırana kadar sorguyu veritabanında yürütmez. FindUpcomingDinners() yöntemimizi çağıran kod isteğe bağlı olarak sorguyu yürütmeden önce IQueryable<Dinner> nesnesine ek "zincirleme" işlemler/filtreler eklemeyi seçebilir. LINQ to SQL daha sonra veriler istendiğinde veritabanında birleştirilmiş sorguyu yürütecek kadar akıllıdır.

Sayfalama mantığını uygulamak için DinnersController'ın Index() eylem yöntemini, döndürülen IQueryable<Dinner> dizisine toList() çağırmadan önce ek "Atla" ve "Al" işleçlerini uygulayacak şekilde güncelleştirebiliriz:

//
// GET: /Dinners/

public ActionResult Index() {

    var upcomingDinners = dinnerRepository.FindUpcomingDinners();
    var paginatedDinners = upcomingDinners.Skip(10).Take(20).ToList();

    return View(paginatedDinners);
}

Yukarıdaki kod, veritabanındaki ilk 10 yaklaşan akşam yemeğini atlar ve ardından 20 akşam yemeğini geri döndürür. LINQ to SQL, bu atlama mantığını web sunucusunda değil SQL veritabanında gerçekleştiren iyileştirilmiş bir SQL sorgusu oluşturacak kadar akıllıdır. Bu, veritabanında milyonlarca yaklaşan Akşam Yemeğimiz olsa bile, bu isteğin bir parçası olarak yalnızca istediğimiz 10'un alınacağı anlamına gelir (verimli ve ölçeklenebilir).

URL'ye "sayfa" değeri ekleme

Belirli bir sayfa aralığını sabit kodlamak yerine URL'lerimizin kullanıcının hangi Akşam Yemeği aralığını istediğini belirten bir "page" parametresi içermesini istiyoruz.

Querystring değeri kullanma

Aşağıdaki kod, Index() eylem yöntemimizi bir querystring parametresini destekleyecek ve /Dinners?page=2 gibi URL'leri etkinleştirecek şekilde nasıl güncelleştirebileceğimizi gösterir:

//
// GET: /Dinners/
//      /Dinners?page=2

public ActionResult Index(int? page) {

    const int pageSize = 10;

    var upcomingDinners = dinnerRepository.FindUpcomingDinners();

    var paginatedDinners = upcomingDinners.Skip((page ?? 0) * pageSize)
                                          .Take(pageSize)
                                          .ToList();

    return View(paginatedDinners);
}

Yukarıdaki Index() eylem yönteminin "page" adlı bir parametresi vardır. parametresi null atanabilir bir tamsayı olarak bildirilir (int? bunu gösterir). Bu, /Dinners?page=2 URL'sinin parametre değeri olarak "2" değerinin geçirilmesine neden olacağı anlamına gelir. /Dinners URL'si (querystring değeri olmadan) null bir değerin geçirilmesine neden olur.

Atlanması gereken akşam yemeği sayısını belirlemek için sayfa değerini sayfa boyutuyla (bu örnekte 10 satır) çarpıyoruz. Null değer atanabilir türlerle ilgilenirken yararlı olan C# null "birleşim" işlecini (??) kullanıyoruz. Yukarıdaki kod, sayfa parametresi null olduğunda sayfaya 0 değerini atar.

Katıştırılmış URL değerlerini kullanma

Querystring değeri kullanmanın bir alternatifi, sayfa parametresini gerçek URL'nin içine eklemektir. Örneğin: /Dinners/Page/2 veya /Dinners/2. ASP.NET MVC, bunun gibi senaryoları desteklemeyi kolaylaştıran güçlü bir URL yönlendirme altyapısı içerir.

Herhangi bir gelen URL veya URL biçimini istediğimiz denetleyici sınıfına veya eylem yöntemine eşleyen özel yönlendirme kurallarını kaydedebiliriz. Tek yapmamız gereken global.asax dosyasını projemizde açmaktır:

İnek Akşam Yemeği gezinti ağacının ekran görüntüsü. Genel nokta bir x seçili ve vurgulanmış.

Ardından, yollara yapılan ilk çağrı gibi MapRoute() yardımcı yöntemini kullanarak yeni bir eşleme kuralı kaydedin. Aşağıdaki MapRoute()

public void RegisterRoutes(RouteCollection routes) {

   routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

    routes.MapRoute(                                        
        "UpcomingDinners",                               // Route name
        "Dinners/Page/{page}",                           // URL with params
        new { controller = "Dinners", action = "Index" } // Param defaults
    );

    routes.MapRoute(
        "Default",                                       // Route name
        "{controller}/{action}/{id}",                    // URL with params
        new { controller="Home", action="Index",id="" }  // Param defaults
    );
}

void Application_Start() {
    RegisterRoutes(RouteTable.Routes);
}

Yukarıda "UpcomingDinners" adlı yeni bir yönlendirme kuralı kaydediyoruz. Url biçiminin "Dinners/Page/{page}" olduğunu gösteriyoruz; burada {page}, URL'nin içine eklenmiş bir parametre değeridir. MapRoute() yönteminin üçüncü parametresi, bu biçimle eşleşen URL'leri DinnersController sınıfındaki Index() eylem yöntemiyle eşlememiz gerektiğini gösterir.

Querystring senaryomuzda daha önce sahip olduğumuz Index() kodunun aynısını kullanabiliriz; ancak artık "page" parametremiz querystring'den değil URL'den gelir:

//
// GET: /Dinners/
//      /Dinners/Page/2

public ActionResult Index(int? page) {

    const int pageSize = 10;

    var upcomingDinners = dinnerRepository.FindUpcomingDinners();
    
    var paginatedDinners = upcomingDinners.Skip((page ?? 0) * pageSize)
                                          .Take(pageSize)
                                          .ToList();

    return View(paginatedDinners);
}

Ve şimdi uygulamayı çalıştırıp /Dinners yazınca ilk 10 yaklaşan yemeği göreceğiz:

İnek Akşam Yemekleri Yaklaşan Akşam Yemekleri listesinin ekran görüntüsü.

/Dinners/Page/1 yazdığınızda, akşam yemeklerinin sonraki sayfasını göreceğiz:

Yaklaşan Akşam Yemekleri listesinin sonraki sayfasının ekran görüntüsü.

Sayfa gezintisi kullanıcı arabirimi ekleme

Sayfalama senaryomuzu tamamlamanın son adımı, kullanıcıların Akşam Yemeği verilerini kolayca atlayabilmesi için görünüm şablonumuzda "sonraki" ve "önceki" gezinti kullanıcı arabirimini uygulamaktır.

Bunu doğru bir şekilde uygulamak için veritabanındaki toplam Akşam Yemeği sayısını ve bunun kaç sayfaya çevrildiği hakkında bilgi sahibi olmamız gerekir. Daha sonra şu anda istenen "sayfa" değerinin verilerin başında mı yoksa sonunda mı olduğunu hesaplamamız ve buna göre "önceki" ve "sonraki" kullanıcı arabirimini göstermemiz veya gizlememiz gerekir. Bu mantığı Index() eylem yöntemimizde uygulayabiliriz. Alternatif olarak, projemize bu mantığı daha yeniden kullanılabilir bir şekilde kapsülleyen bir yardımcı sınıf ekleyebiliriz.

Aşağıda, .NET Framework yerleşik List<T> koleksiyonu sınıfından türetilen basit bir "PaginatedList" yardımcı sınıfı yer almaktadır. Herhangi bir IQueryable veri dizisini sayfalandırmak için kullanılabilecek yeniden kullanılabilir bir koleksiyon sınıfı uygular. NerdDinner uygulamamızda IQueryable<Akşam Yemeği> sonuçları üzerinde çalışmasını sağlayacağız, ancak diğer uygulama senaryolarında IQueryable<Product> veya IQueryable<Customer> sonuçlarına karşı da kolayca kullanılabilir:

public class PaginatedList<T> : List<T> {

    public int PageIndex  { get; private set; }
    public int PageSize   { get; private set; }
    public int TotalCount { get; private set; }
    public int TotalPages { get; private set; }

    public PaginatedList(IQueryable<T> source, int pageIndex, int pageSize) {
        PageIndex = pageIndex;
        PageSize = pageSize;
        TotalCount = source.Count();
        TotalPages = (int) Math.Ceiling(TotalCount / (double)PageSize);

        this.AddRange(source.Skip(PageIndex * PageSize).Take(PageSize));
    }

    public bool HasPreviousPage {
        get {
            return (PageIndex > 0);
        }
    }

    public bool HasNextPage {
        get {
            return (PageIndex+1 < TotalPages);
        }
    }
}

Yukarıda "PageIndex", "PageSize", "TotalCount" ve "TotalPages" gibi özellikleri hesaplayıp kullanıma sunma şekline dikkat edin. Ayrıca, koleksiyondaki veri sayfasının özgün dizinin başında mı yoksa sonunda mı olduğunu belirten "HasPreviousPage" ve "HasNextPage" adlı iki yardımcı özelliği kullanıma sunar. Yukarıdaki kod, iki SQL sorgusunun çalıştırılmasına neden olur. İlk olarak Dinner nesnelerinin toplam sayısını alır (bu, nesneleri döndürmez; tamsayı döndüren bir "SELECT COUNT" deyimi gerçekleştirir) ve ikincisi ise geçerli veri sayfası için veritabanımızdan yalnızca ihtiyacımız olan veri satırlarını alır.

Ardından DinnerRepository.FindUpcomingDinners() sonucumuzdan PaginatedList<Akşam Yemeği> oluşturmak ve görünüm şablonumuza geçirmek için DinnersController.Index() yardımcı yöntemimizi güncelleştirebiliriz:

//
// GET: /Dinners/
//      /Dinners/Page/2

public ActionResult Index(int? page) {

    const int pageSize = 10;

    var upcomingDinners = dinnerRepository.FindUpcomingDinners();
    var paginatedDinners = new PaginatedList<Dinner>(upcomingDinners, page ?? 0, pageSize);

    return View(paginatedDinners);
}

Ardından \Views\Dinners\Index.aspx görünüm şablonunu ViewPage IEnumerable Dinner yerine<ViewPage<NerdDinner.Helpers.PaginatedList<Akşam Yemeği'nden>>>> devralacak şekilde güncelleştirebilir ve sonraki ve önceki gezinti kullanıcı arabirimini göstermek veya gizlemek için görünüm şablonumuzun altına aşağıdaki kodu ekleyebiliriz<:

<% if (Model.HasPreviousPage) { %>

    <%= Html.RouteLink("<<<", "UpcomingDinners", new { page = (Model.PageIndex-1) }) %>

<% } %>

<% if (Model.HasNextPage) {  %>

    <%= Html.RouteLink(">>>", "UpcomingDinners", new { page = (Model.PageIndex + 1) }) %>

<% } %>

Yukarıdaki köprülerimizi oluşturmak için Html.RouteLink() yardımcı yöntemini nasıl kullandığımıza dikkat edin. Bu yöntem, daha önce kullandığımız Html.ActionLink() yardımcı yöntemine benzer. Aradaki fark, Global.asax dosyamızda kuracağımız "UpcomingDinners" yönlendirme kuralını kullanarak URL'yi oluşturmamızdır. Bu, Index() eylem yöntemimize şu biçime sahip URL'ler oluşturmamızı sağlar: /Dinners/Page/{page} – burada {page} değeri, geçerli PageIndex'i temel alarak yukarıda sağladığımız bir değişkendir.

Ve şimdi uygulamamızı yeniden çalıştırdığımızda tarayıcımızda bir kerede 10 akşam yemeği göreceğiz:

İnek Yemeği sayfasındaki Yaklaşan Akşam Yemekleri listesinin ekran görüntüsü.

Ayrıca sayfanın en altında, arama motoru tarafından erişilebilen URL'leri kullanarak verilerimiz üzerinde ileri ve geri atlamamıza olanak tanıyan ve >>> gezinti kullanıcı arabirimi de bulunur<<<:

Yaklaşan Akşam Yemekleri listesinin yer aldığı İnek Yemekleri sayfasının ekran görüntüsü.

Yan Konu: IQueryable<T'nin etkilerini anlama>
IQueryable<T> , çeşitli ilginç ertelenmiş yürütme senaryolarına (disk belleği ve oluşturma tabanlı sorgular gibi) olanak tanıyan çok güçlü bir özelliktir. Tüm güçlü özelliklerde olduğu gibi, nasıl kullandığınıza dikkat etmek ve kötüye kullanılmadığından emin olmak istiyorsunuz. Deponuzdan bir IQueryable<T> sonucu döndürülmesi, çağrı kodunun zincirleme işleç yöntemlerine eklenmesine olanak sağladığını ve dolayısıyla nihai sorgu yürütmesine katıldığını bilmek önemlidir. Bu özelliği çağıran kod sağlamak istemiyorsanız, zaten yürütülen bir sorgunun sonuçlarını içeren IList<T> veya IEnumerable<T> sonuçlarını geri döndürmelisiniz. Sayfalandırma senaryolarında bu, çağrılan depo yöntemine gerçek veri sayfalandırma mantığını göndermenizi gerektirir. Bu senaryoda FindUpcomingDinners() bulucu yöntemimizi PaginatedList döndüren bir imzaya sahip olacak şekilde güncelleştirebiliriz: PaginatedList< Dinner> FindUpcomingDinners(int pageIndex, int pageSize) { } Veya bir IList<Akşam Yemeğini> geri döndürüp bir "totalCount" çıkış parametresi kullanarak Akşam Yemeği toplam sayısını döndürebilirsiniz: IList<Akşam Yemeği> BulUpcomingDinners(int pageIndex, int pageSize, out int totalCount) { }

Sonraki Adım

Şimdi uygulamamıza kimlik doğrulaması ve yetkilendirme desteği eklemeye bakalım.