Mvc’de Custom Paging, Sorting, Kolon Ekleme ve Excel’e Atma Bölüm1
Selamlar,
Bugün Mvc’de WebGrid kullanarak kendi paging ve sorting’imizi yazıp, Excel’e export edeceğiz. Ayrıca WebGrid’e özel bir kolon nasıl ekleniyor hep beraber inceleyeceğiz. Öncelikle WebGrid’de paging ve sorting varken neden kendimiz yazma ihityaç duyduk onu inceleyelim. WebGrid ve buna benzer birçok hazır tool bu işlemler için tüm datayı çeker. Diyelim ki 1000000 kayıdımız var. Ve sayfalama yapıcağız. En başta 25 kayıt göstermek için neden tüm datayı çekip, hem zaman hem de performans kaybına yol açalım. Genelde tooların tüm datayı çekmesindeki amaç, toplam kayıt sayısının bulunup, her sayfada gösterilecek satır sayısına göre tüm sayfalamanın html’e basılmasıdır. Biz de tüm bu işlemleri kendimiz üstlenip, performansı büyük oranda arttırıcak ve gereksiz yük kaybını ortadan kaldıracağız.
Öncelikli olarak boş bir Mvc projesi açıyoruz. Çalışacağımız database Northwind Databaseidir. CodeFirst (DB First) kullanılarak çalışılacak tablo, aşağıda Pocosu görülen ‘Customer’ tablosudur.
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 |
namespace TeklifFormu.Models { using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Data.Entity.Spatial; public partial class Customer { [StringLength(5)] public string CustomerID { get; set; } [Required] [StringLength(40)] public string CompanyName { get; set; } [StringLength(30)] public string ContactName { get; set; } [StringLength(30)] public string ContactTitle { get; set; } [StringLength(60)] public string Address { get; set; } [StringLength(15)] public string City { get; set; } [StringLength(15)] public string Region { get; set; } [StringLength(10)] public string PostalCode { get; set; } [StringLength(15)] public string Country { get; set; } [StringLength(24)] public string Phone { get; set; } [StringLength(24)] public string Fax { get; set; } } } |
Çalışılacak DbContext sınıfı, NorthWindContext aşağıdaki gibidir: Bu bölümde sadece ‘Customer’ tablosu ile çalışılacaktır.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
namespace TeklifFormu.Models { using System; using System.Data.Entity; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; public partial class NorthWindContext : DbContext { public NorthWindContext() : base("name=NorthWindContext") { } public virtual DbSet<Customer> Customers { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Entity<Customer>() .Property(e => e.CustomerID) .IsFixedLength(); } } } |
İlk datanın gösterileceği Index Action’ı aşağıdaki gibidir. Dikkat edileceği gibi view’a herhangi bir data model gönderilmemiştir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Helpers; using System.Web.Mvc; using TeklifFormu.Models; namespace TeklifFormu.Controllers { public class HomeController : Controller { // GET: Home public ActionResult Index() { return View(); } } } |
Index.cshtml: Aşağıda görüldüğü gibi herhangi bir html içerik sayfanın içinde tanımlanmamıştır. İlgili içerik server side’da oluşturulup ‘paging.js’ script dosyasında ‘$.getJSON‘ ile ekrana basılmaktadır.
1 2 3 4 5 6 7 |
<link href="~/WebGrid.css" rel="stylesheet" /> <script src="~/scripts/jquery-2.2.1.min.js"></script> <script src="~/scripts/paging.js"></script> @{ Page.Title = "Export To Excel"; } <h1>Export to Excel<span id="ExcelDiv"><img src="/excel.png" id="excel" alt="Export to Excel" title="Export to Excel" /></span></h1> |
Şimdi gelin adım adım ‘paging.js’i inceleyelim:
1-) Paging.js sayfa ilk yüklendiğinde ‘EfficientPaging’ Action’ına gidilip ilgili WebGrid ekrana basılır.
1 2 3 4 5 |
$(function () { //Sayfa ilk geldiginde yuklenir. $.getJSON("/Home/EfficientPaging", null, function (d) { // Dynamic WebGrid body içine basılır. $("body").append(d.Data); |
HomeController.cs EfficientPaging() İlgili WebGrid’in doldurulup sayfalandığı yer:
- Aşağıda ‘JsonResult‘ dönülen ve paramtere olarak ‘int? page‘ alan bir method tanımlanmıştır. Aslında burada sayfalama işlemi yapılmaktadır. ‘rowPerPage=25‘ ile her sayfada gösterilecek satır sayısı tanımlanmıştır. Örneğin 5. sayfa görülmek istenir ise bundan önceki 4 sayfa yani (4 x 25) kayıt atlanmalı yani skip yapılmalıdır. Daha sonra geri kalan kayıtlardan 25’i alınmalıdır. Yani take yapılmalıdır. İşte Skip(skip * rowPerPage).Take(rowPerPage) bunun için yazılmıştır. Kısaca 100 ile 125 kayıtları arası gösterilmektedir. İlgili tüm kayıt çekilip ‘data‘ nesnesine atılır.
- Sıra geldi bu datayı ‘WebGrid‘de göstermeye. Aşağıda görüldüğü gibi ilgili WebGrid sunucu tarafında oluşturulur. İlk parametre ‘data‘ sonra ‘canPage: false‘ verilerek otomatik paging kapatılmıştır. Bu işlemi biz yapacağız:) ‘canSort:false‘ ile kolonlara göre sıralama işlemi de kapatılmıştır. ‘rowsPerPage: rowPerPage‘ ile herbir sayfada kaç satır görüleceği set edilmiştir. Bu örnekde mesela 25. ‘grid.GetHtml()‘ ilgili grid’in Html’i alınmıştır. Grid’in daha güzel görünmesi için ‘WebGrid.css‘ yazılmıştır. Ayrıca ilgili header, footer gibi kısımlara bu css’den sitil atanmıştır. Grid’de Customer tablosuna ait görünmesi istenen kolonlar ‘columns: grid.column()‘ ile tanımlanmıştır. Bazı kolonlara allies verilerek ekrandaki görünümleri değiştirilmiştir. Örnek : (‘CompanyName’, ‘ Company Name’) olarak değiştirilmiştir.
- Sıra geldi sayfalama için toplam sayfa sayısını bulunmasına. Bunun için öncelikle toplam kayıt sayısı bulunur ve herbir sayfada gösterilecek kayıt sayısına bölünerek, toplam sayfalama sayısı bulunur. Bunun için kalan sayıya bakılarak paging değeri gerekirse(yani kalan sayı var ise) 1 arttırılır. Son olarak çekilen data(WebGrid) ve paging yani altta görülen sayfalama sayısı ‘json‘ olarak geri dönülür.
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 |
int rowPerPage = 25; // Her sayfada görülecek toplam sayır sayısı [HttpGet] public JsonResult EfficientPaging(int? page) { using (NorthWindContext dbContext = new NorthWindContext()) { int skip = page.HasValue ? page.Value - 1 : 0; var data = new List<Customer>(); data = dbContext.Customers.OrderBy(o => o.CustomerID).Skip(skip * rowPerPage).Take(rowPerPage).ToList(); var grid = new WebGrid(data, canPage: false, canSort: false, rowsPerPage: rowPerPage); var htmlString = grid.GetHtml(tableStyle: "webGrid", headerStyle: "webgrid-header", footerStyle: "webgrid-footer", alternatingRowStyle: "webgrid-alternating-row", selectedRowStyle: "webgrid-selected-row", rowStyle: "webgrid-row-style", htmlAttributes: new { id = "grid" }, columns: grid.Columns( //grid.Column("CustomerID", "ID"), grid.Column("CustomerID", "CustomerID"), grid.Column("CompanyName", "Company Name"), grid.Column("ContactName", "Contact Name"), grid.Column("Address"), grid.Column("City"), grid.Column("Country"), grid.Column("Phone")) ); // Eger bir satirdaki row count toplam satir sayisi ile bolumunda tam sayi ise direk pagecount alinir degil ise 1 arttirilir. var _count = dbContext.Customers.Count() % rowPerPage == 0 ? dbContext.Customers.Count() / rowPerPage : (dbContext.Customers.Count() / rowPerPage) + 1; return Json(new { Data = htmlString.ToHtmlString(), Count = _count }, JsonRequestBehavior.AllowGet); } } |
Index.cshtml’e basılan ekran görüntüsü aşağıdaki gibidir:
2-)Paging.js’de ilgili WebGrid’in ekran basılmasından sonra paging’in WebGrid’in footer’ına manuel olarak konulması: Aşağıda görüldüğü gibi ‘createFooter()‘ function’ı ile ‘<tfoot>‘ tagları arasında ilgili paging html’i HomeController’daki ‘EfficientPaging()‘ methodundan dönen toplam sayfalama sayısı kadar dönülerek oluşturulur. Ve ‘WebGrid”in ‘thead‘ html elementinden sonra konur. Önemli bir not: ‘body’ içindeki ‘#grid tfoot a‘ şeklinde ilgili paging buttonlarına erişilerek ‘onclick’ eventinde ‘EfficientPaging()‘ methoduna gidilerek ilgili ‘WebGrid’ html’i temizlenip dönen yeni html ekrana basılır ve temizlenen footer yani paging sonuna tekrardan eklenir. Eğer ‘onclick’ durumunda ilgili sayfalama buttonlarına ‘body”nin altından erişilmeyip direk ‘#grid tfoot a‘ şeklinde erişilmeye çalışılsa idi, html temizlendiğinde yeni konan footerdaki buttonlar tıklanamıyacaktı. Bundan dolayı ilgili paging buttonlarına ‘body‘ altından yani en üstteki parent elementinden erişilmektedir.
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 |
// Footer yani paging oluşturulur. var footer = createFooter(d.Count); //Tıklanma durumunda çağrılacak function burada tanımlanır. $("body").on("click", "#grid tfoot a", function (e) { e.preventDefault(); var data = { page: $(this).text()// Tıklanan sayfa numarası gönderilir. }; //Sayfalama buttonlarina basilinca paging yapilir. $.getJSON("/Home/EfficientPaging", data, function (html) { // gelen data table'a eklenir. $("#grid").remove(); //Var olan WebGrid kaldırılıp yeni çekilen datalar body içerisine basılır. $("body").append(html.Data); // footer tekrardan eklenir. $('#grid thead').after(footer); }); }); //Paging'in WebGrid'in sonuna eklendiği sayfalama buttonları. function createFooter(d) { var rowsPerPage = 25; var footer = "<tfoot><tr class='webgrid-footer'><td colspan='7'>"; for (var i = 1; i < (d + 1) ; i++) { footer = footer + "<a data-swhglnk='true' href=#><b>" + i + "</ b></a> "; } footer = footer + "</td></tr></tfoot>"; $("#grid thead").after(footer); console.log(footer); return footer; } |
Şimdi sıra geldi ‘WebGrid’ e verdiğimiz sitili, yani ‘WebGrid.css’ i incelemeye: Aşağıda görüldüğü gibi WebGrid’in header yani sıralama yapılacak kolonlarına , footer yani paging yapılacak kısma ve son olarak alternating yani her satır aşırıda bir atlanılan sitiller aşağıdaki gibi tanımlanmıştır.
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 |
.webgrid { width: 100%; border: 0px; border-collapse: collapse; white-space:nowrap; } .des { width:50%; } .webgrid a { color: #000; } .webgrid-header { padding: 6px 5px; text-align: center; background-color: #e8eef4; border-bottom: 2px solid #3966A2; height: 40px; border-top: 2px solid #D6E8FF; border-left: 2px solid #D6E8FF; border-right: 2px solid #D6E8FF; } .webgrid-footer { padding: 6px 5px; text-align: center; background-color: #e8eef4; border-top: 2px solid #3966A2; height: 30px; border-bottom: 2px solid #D6E8FF; border-left: 2px solid #D6E8FF; border-right: 2px solid #D6E8FF; } .webgrid-alternating-row { height: 30px; background-color: #f2f2f2; border-bottom: 1px solid #d2d2d2; border-left: 2px solid #D6E8FF; border-right: 2px solid #D6E8FF; } |
WebGrid’de Tabloda Olmayan Özel Kolon Ekleme: Bu bölümde WebGrid’e yeni bir kolon ekleyerek listelenen müşterilerin sipariş listesini gösteren bir detay button’u yaratacağız. Daha sonra tıklanma işleminde gidilecek detay sayfasını hep beraber kodlayacağız.
Web Grid’e yeni kolon aşağıdaki gibi eklenir: ‘header’ kısmına siparisler yazılmıştır. ‘format’ kısmına eklenecek olan html elementi tanımlanır. Lambda ile ‘item =>‘ WebGrid’e gelen kolonlara erişilir. Açılan yeni kolona basılacak ‘button‘ ‘HtmlString()‘ methodu ile Html’i oluşturulup, ‘onclick‘ durumunda ‘getDetail()‘ function’ı çağrılır. İlgili function’a parametre olarak ‘item.CustomerID‘ escape denilen ‘\”‘ karakteri gönderilir.
WebGrid’e custom eklenen kolon:
1 2 3 |
grid.Column( header: "Siparisler", format: (item) => new HtmlString("<input type='button' id='btnDetail' value='Detay' onclick=\"getDetail('" + item.CustomerID + "')\" />")), |
Index sayfasının son görünümü aşağıdaki gibidir. İlgili detay buttonları sol tarafta gözükmektedir.
3-)Paging.js’deki ‘getDetail()‘ function’ı ile ‘Detay’ buttonuna basılınca Customer’a ait sipariş listesine gitmek için ‘Order()‘ action’ı aşağıdaki gibi çağrılır. Görüldüğü gibi Order yani siparişler sayfası yeni bir pencerede açılmıştır.
1 2 3 4 |
function getDetail(ID) { window.open("Home/Order?CustomerID=" + ID, "_blank"); } |
HomeController.cs’deki ‘Order()‘ action’ı aşağıda görüldüğü gibi seçilen müşteriye ait tüm sipariş listesini ‘Freight‘ yani gemiye ödenecek en yüksek ücrete göre çekip, ilgili model’i, Order view’ına göndermektedir.
1 2 3 4 5 6 7 8 |
public ActionResult Order(string CustomerID) { using (NorthWindContext dbContext = new NorthWindContext()) { var model = dbContext.Orders.Where(or => or.CustomerID == CustomerID).OrderByDescending(or=>or.Freight).ToList(); return View(model); } } |
Order.cs: Order Poco’su aşağıdaki gibi tanımlanmıştır.(CodeFirst)
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 |
namespace TeklifFormu.Models { using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Data.Entity.Spatial; public partial class Order { public int OrderID { get; set; } [StringLength(5)] public string CustomerID { get; set; } public int? EmployeeID { get; set; } public DateTime? OrderDate { get; set; } public DateTime? RequiredDate { get; set; } public DateTime? ShippedDate { get; set; } public int? ShipVia { get; set; } [Column(TypeName = "money")] public decimal? Freight { get; set; } [StringLength(40)] public string ShipName { get; set; } [StringLength(60)] public string ShipAddress { get; set; } [StringLength(15)] public string ShipCity { get; set; } [StringLength(15)] public string ShipRegion { get; set; } [StringLength(10)] public string ShipPostalCode { get; set; } [StringLength(15)] public string ShipCountry { get; set; } public virtual Customer Customer { get; set; } } } |
Tüm müşteriler için sayfalama ve herbir müşteriye ait sipariş listesine, aşağıda görüldüğü gibi gidilebilir.
Order.cshtml: Customer’a göre çekilen Order sayfası aşağıdaki gibidir. Model olarak ‘List<TeklifFormu.Models.Order>’ beklemektedir.
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 |
@model List<TeklifFormu.Models.Order> @{ Layout = null; } <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>Order</title> <link href="~/Content/bootstrap.min.css" rel="stylesheet" /> </head> <body> <div class="container"> <div class="jumbotron"> <h1>Siparis Listesi</h1> @{var Musteri = Model.FirstOrDefault()?.CustomerID; } <p>@Musteri Adli Musterinin Siparisleri</p> </div> <div class="form-group"> <table class="table"> <thead> <tr> <th>Agirlik</th> <th>Gemi Adi</th> <th>Gemi Adresi</th> <th>Sehir</th> <th>Ulke</th> </tr> </thead> <tbody> @foreach(var custom in Model) { <tr> <td>@custom.Freight</td> <td>@custom.ShipName</td> <td>@custom.ShipAddress</td> <td>@custom.ShipCity</td> <td>@custom.ShipCountry</td> </tr> } </tbody> </table> </div> </div> </body> </html> |
Geldik makalenin ilk bölümünün sonuna. Bir sonraki bölümde istenilen kolona göre dinamik sıralama ve Excel’e atma işlemleri yapılacaktır. Bakalım paging ortamında dinamic sorting, işleri nasıl karıştıracak. Ayıca excel’e, ilgili dataların atılması durumunda, tasarımın korunması için neler yapılması gerekecek. Hepsini bir sonraki makalede hep beraber inceleyeceğiz.
Geldik bir makalenin daha sonuna. Bu makalenin devamında görüşmek üzere hoşçakalın.
Source:
Özellikle büyük veri tipinde çalışılacak projeler için çok çok gerekli ve faydalı bir yazı olmuş. Dediğiniz gibi umarım bu tip bilgi eksikliklerimizi tamamlayıp yeni teknolojilerin neler getireceğini konuştuğumuz günlerde gelir. Emeğiniz ve katkınız için teşekkürler.
Ben teşekkür ederim Talha.
O günler de gelecek. Hem de sandığımızdan çok daha yakında..
İyi çalışmalar…
merhaba,
bu yöntem mvc de değil de web forms da yapılabilir mi ?
Aynı kodları kullansak birşey değişir mi ?
Selamlar ilhan,
Eğer makalede geçen WebGrid’in WebForm karşılığını yazarsan mantık aynı mantık.
Kolay gelsin.