MVC#

Building a Data Grid in ASP.NET MVC

Posted in ASP.NET MVC, HTML Helpers by BlueCoder on 11/02/2010

Abstract: . In this post I’m going to show how you can build a Data Grid in ASP.NET MVC which has the sorting and paging features while you have enough control over the generated HTML  and more important you do this in a testable manner. And also you have bookmarking feature which is useful when user likes to share a specific page of the sorted data just by copying and pasting the URL from browser’s address bar. We avoid using JavaScript for this so the users which often disable JavaScript  in their browser don’t miss anything.

One of the first requirements of any web application is to show the data in a gird like manner. In web form world this is a simple task you put the Grid View Control directly into the .aspx page  and configure  its data source and you’ve done. But this way you lose some important things. You don’t have enough control over  the generated HTML and also you lose the bookmarking feature because paging and sorting information will be  saved in viewstate which is another issue in when you develop in  web form. In this post I’m going to show how you can build a Data Grid Helper method which has all the power of GridView (like sorting and paging) while you also have enough control over  the generated HTML  and you do all  these in a testable manner and also you have bookmarking feature which is useful when user likes to share a specific page of data which is sorted in a desired way.

To build any HTML helper in MVC.NET you should write an extension method that extends HtmlHelper class. Because I want to avoid importing namespaces and configure Web.Config file I put the static class needed to create this extension method  in System.Web.Mvc.Html namespace.

First I’m going to show a Grid Data Helper which has the sorting capability and then I’m going to create a Pager helper to add paging capability to this Grid. I use the Grid Data helper codes which “Stephen Walther” mentioned in his “ASP.NET MVC framework Unleashed” book and  for the paging part I going to use Paging helper codes which “Steven Sanderson” has mentioned in his “Pro ASP.NET MVC framework” book which generates the HTML markup for page links and I also extend it to create a dynamic pager and also I use the Dynamic Linq sample code to write simple sorting logic and avoid using many switch case block of codes.

Lets jump into code and then dig into how it works:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.IO;

namespace System.Web.Mvc.Html
{
    public static class DataGridHelper
    {
        public static string DataGrid<T>(this HtmlHelper helper)
        {
            return DataGrid<T>(helper, null, null);
        }

        public static string DataGrid<T>(this HtmlHelper helper, object data)
        {
            return DataGrid<T>(helper, data, null);
        }

        public static string DataGrid<T>(this HtmlHelper helper, object data, string[] columns)
        {
            //Get items
            var items = (IEnumerable<T>)data;
            if (items == null)
                items = (IEnumerable<T>)helper.ViewData.Model;

            //Get column names
            if (columns == null)
                columns = typeof(T).GetProperties().Select(p => p.Name).ToArray();

            //Create HtmlTextWriter
            var writer = new HtmlTextWriter(new StringWriter());

            //Open table tag
            writer.RenderBeginTag(HtmlTextWriterTag.Table);

            //Render Table Header
            writer.RenderBeginTag(HtmlTextWriterTag.Thead);
            RenderHeader(helper, writer, columns);
            writer.RenderEndTag();

            // Render table body
            writer.RenderBeginTag(HtmlTextWriterTag.Tbody);
            foreach (var item in items)
                RenderRow<T>(helper, writer, columns, item);
            writer.RenderEndTag();

            //Close  table tag
            writer.RenderEndTag();

            //return the string
            return writer.InnerWriter.ToString();
        }

        private static void RenderHeader(HtmlHelper helper, HtmlTextWriter writer, string[] columns)
        {
            writer.RenderBeginTag(HtmlTextWriterTag.Tr);
            foreach (var columnName in columns)
            {
                writer.RenderBeginTag(HtmlTextWriterTag.Th);
                var currentAction = (string)helper.ViewContext.RouteData.Values["action"];
                var link = helper.ActionLink(columnName, currentAction, new { sort = columnName });
                writer.Write(link);
                writer.RenderEndTag();
            }
            writer.RenderEndTag();
        }

        public static void RenderRow<T>(HtmlHelper helper, HtmlTextWriter write, string[] columns, T item)
        {
            write.RenderBeginTag(HtmlTextWriterTag.Tr);
            foreach (var columnName in columns)
            {
                write.RenderBeginTag(HtmlTextWriterTag.Td);
                var value = typeof(T).GetProperty(columnName).GetValue(item, null) ?? String.Empty;
                write.Write(helper.Encode(value.ToString()));
                write.RenderEndTag();
            }
            write.RenderEndTag();
        }

    }
}

The core part of this helpers  simply makes a table. As you see this helper is a generic method so you can use it for all of your domain model classes. You can send model data and columns names through the parameters or you can simply pass it through the ViewModel and let the helper do the work for you. The first interesting part of code is where we use a bit of reflection to find the columns name of the our model:


columns = typeof(T).GetProperties().Select(p => p.Name).ToArray();

In this line of code we get the name of all properties of the model we passed to helper and put them on the columns array, then we use this array to generate the HTML markup of our gird’s header and also we are going use it for generating the links needed for calling the action method and passing the column name as sort expression to it.
Then we begin to create the markup and as you see we use HtmlTextWriter class to do this task more clean, more readable, more error proof, and also more testable but you can use StringBuilder class or just use any method you feel comfortable.
As you see the more interesting part of code are RenderHeader and RenderRow methods lets dig into:
RenderHeader : In this method we going to make the header columns as a link to call the sorting for the grid. To create the links we need to get the current action through this line of code :

var currentAction = (string)helper.ViewContext.RouteData.Values[“action”];

Then we use Html.ActionLink helper method to generate the actual link markups:

var link = helper.ActionLink(columnName, currentAction, new { sort = columnName });

RenderRow : In this method we use a bit of reflection to get the value of the properties of model to fill each cell and also we use column array we to specify the name of the property:

var value = typeof(T).GetProperty(columnName).GetValue(item, null) ?? String.Empty;

Also note that we use Html.Encode method to pass only valid Html markup to the view and prevent XSS or other JS attacks:

write.Write(helper.Encode(value.ToString()));

And that’s it our Grid Data Helper completed and has the sorting feature but what about paging?
I’m going to create the paging helper a bit more reusable so I’ll be able to use it outside of Grid Data and also I’m interested in separate the paging logic from sorting for the sake of SoC. Lets first check the source code and then see how it works :

namespace System.Web.Mvc.Html
{
    public static class PageLinkHelper
    {
        public static string PageLink(this HtmlHelper html, int currentPage, int totalPages, Func<int, string> pageUrl)
        {

            var diff = 1;
            StringBuilder result = new StringBuilder();
            var TotalPages = totalPages;

            if (currentPage < 1)
                currentPage = 1;

            if ((currentPage + diff) < totalPages)
                totalPages = currentPage + diff;
            var startPage = 1;
            if ((currentPage - diff) > startPage)
                startPage = currentPage - diff;

            if ((currentPage - diff) > 1)
            {
                TagBuilder tag3 = new TagBuilder("a");
                tag3.Attributes.Add("href", pageUrl(1));
                tag3.InnerHtml = "1";
                result.AppendLine(tag3.ToString());
            }

            if ((currentPage - (diff + 1)) > 1)
            {
                TagBuilder tag2 = new TagBuilder("a");
                tag2.Attributes.Add("href", pageUrl(currentPage - (diff + 1)));
                tag2.InnerHtml = "...";
                result.AppendLine(tag2.ToString());
            }

            for (int i = startPage; i <= totalPages; i++)
            {
                TagBuilder tag = new TagBuilder("a");
                tag.Attributes.Add("href", pageUrl(i));
                tag.InnerHtml = i.ToString();
                if (i == currentPage)
                    tag.AddCssClass("pageSelected");
                result.AppendLine(tag.ToString());
            }

            if ((currentPage + (diff + 1)) < TotalPages)
            {
                TagBuilder tag2 = new TagBuilder("a");
                tag2.Attributes.Add("href", pageUrl(currentPage + (diff + 1)));
                tag2.InnerHtml = "...";
                result.AppendLine(tag2.ToString());
            }

            if ((currentPage + diff) < TotalPages)
            {
                TagBuilder tag3 = new TagBuilder("a");
                tag3.Attributes.Add("href", pageUrl(TotalPages));
                tag3.InnerHtml = TotalPages.ToString();
                result.AppendLine(tag3.ToString());
            }
            return result.ToString();
        }
    }
}

This helper gets the current page and total page and also a method to generate the links.
We will pass all this data directly in the view as you see soon. I just wanted to show the core part of this pager helper is :

 for (int i = startPage; i <= totalPages; i++)
            {
                TagBuilder tag = new TagBuilder("a");
                tag.Attributes.Add("href", pageUrl(i));
                tag.InnerHtml = i.ToString();
                if (i == currentPage)
                    tag.AddCssClass("pageSelected");
                result.AppendLine(tag.ToString());
            }

Which we create the markup for each page and add a CSS class to the current page, the remaining codes just is to generate “… “ links and check the difference between current page and first or last page. I create a logic which just show the first, last and current pages and also the previous and next pages link and shows “…” to get access to farther page links. Obviously you can change it to satisfies your needs and also I’ll waiting to hear about more efficient and better ways to implement these kind of logic in the comments.
All the work we done until here is just generates the dumb HTML markups to use in the view! We should create the core logic in Controller. But first I want to note some important topics. First of all I’m using the default route configuration for this demo and also take advantage of Repository and View Model patterns. The database I use is Northwind sample DB and I use EF as ORM and also I use a simple DI pattern to decouple the Controller from the repository and the only dependency is at the constructor of the controller.
OK let’s just check the source code of Controller then dig into how it works :

namespace Mvc2Application1.Controllers
{
    public class ProductController : Controller
    {
        private IProductsRepository _repository;
        private int _pageSize;
        public ProductController():this(new ProductsRepositoryEF())
        {

        }

        public ProductController(IProductsRepository repository)
        {
            _repository = repository;
            _pageSize = 10;
        }

        public ActionResult List(string sort, int? page)
        {
            var currentPage = page ?? 1;
            ViewData["SortItem"] = sort;
            sort = sort ?? "Name";
            ViewData["CurrentPage"] = currentPage;
            ViewData["TotalPages"] = (int)Math.Ceiling((float)_repository.Products.Count() / _pageSize);

            var products = from p in _repository.Products
                           select new ProductViewModel()
                           {
                               Name = p.ProductName,
                               Price = p.UnitPrice ?? 0,
                               Category = p.Category.CategoryName
                           };
            var sortedProducts = products
                .OrderBy(sort)
                .Skip((currentPage - 1) * _pageSize)
                .Take(_pageSize);
            return View(sortedProducts);
        }

As you see we pass the sort and page information to action method through parameters and pass the SortItem CurrentPage and TotalPages to view through ViewData dictionary. I used the ViewModel pattern to just pass the needed columns to the view and also flatten the complex abject to a relatively simple class. The most interesting part is where instead of passing lambda expression I just passed the property name as the sort condition. If you try this you get error. To be able to do this dynamic query through LINQ you need to add the Dynamic.cs class from the sample codes included in Visual Studio into your project and also add the System.Linq.Dynamic namespace reference to your Controller class for more info about using Dynamic Linq check out the Scott Guthrie’s blog post.and that’s it as you see our controller is also simple enough to work with then let’s go to view code and complete our masterpiece!

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<IEnumerable<Mvc2Application1.Models.ViewModels.ProductViewModel>>" %>
<%@ Import Namespace="Mvc2Application1.Models.ViewModels" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
	List
</asp:Content>

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">

    <h2>List of Product</h2>
    <%=Html.DataGrid<ProductViewModel>() %>
    <div>
        <%=Html.PageLink((int)ViewData["CurrentPage"],
            (int)ViewData["TotalPages"],
            p => Url.Action("List",
                new { page = p,
                    sort = (string)ViewData["SortItem"] }))%>
    </div>
</asp:Content>

First note I imported the required namespace. As you see the view code is very simple, we show the Data Grid just by calling the Html.DataGrid helper and for the paging part we just call the Html.PageLink Helper we just created. The most interesting part is the method we pass to this helper:

p => Url.Action("List",new { page = p,sort = (string)ViewData["SortItem"]})

This way every page link goes to the same action method (List) and pass the page and sort parameter to it so the user can share the specific part of data just by passing the Url to others because all the paging and sorting information can be seen in the address bar. And that’s it we just created our Data Grid and now we have the complete control over generated HTML and also we can easily test our helper codes. As Rob Connery says every time you have an if in your view create a helper! This way your view is easy to test and also more HTML friendly.
Summary : We just created two useful helper methods which we can use all over our project to show a Data Grid which have the sorting and paging features we also took advantage of Dynamic Linq to write a simple sorting logic and we also took advantage of some advanced feature of C# like generics and reflection to write our Data Grid helper and also used the TagBuilder and HtmlTextWriter classes to write a clean and testable helper. Our Paging logic is completely separate from our Data Grid so we can use it with any other list of data.

Update: You can download sample project here .And also don’t Forget to download the Northwind DB here and copy it to your App_Data folder .


Shout it

kick it on DotNetKicks.com

About these ads

19 Responses

Subscribe to comments with RSS.

  1. Ryan said, on 22/02/2010 at 08:50

    Hi Ali, I discovered this post while doing research for my own (unwritten) post on incorporating reusable paging and sorting in MVC. My idea is to utilize an ActionFilter to separate the sorting/paging from the action logic. The benefit of this is that you don’t have to copy it everywhere (or even think about it!).

    Here is a preview of my idea as it would apply to your post:

    public class SortAndPageAttribute : ActionFilterAttribute
    {
    private string _Sort;
    private int _Page;
    private const int _PageSize = 20;

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
    var parameters = filterContext.ActionParameters;
    if(parameters.ContainsKey(“sort”) && parameters.ContainsKey(“page”))
    {
    _Sort = parameters["sort"].ToString();
    _Page = int.Parse(parameters["page"].ToString());
    }
    }

    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
    var viewResult = filterContext.Result as ViewResultBase;
    if (viewResult != null && viewResult.ViewData.Model != null)
    {
    viewResult.ViewData.Model = viewResult.ViewData.Model
    .OrderBy(_Sort)
    .Skip((_Page – 1) * _PageSize)
    .Take(_PageSize);
    }
    }
    }

    Then your action would look like:

    [SortAndPage]
    public ViewResult List()
    {
    var products = _repository.Products; // This should be IQueryable
    return ViewPage(products);
    }

    I prefer this approach because it results in a very clean and concise action method. I’ll be formalizing this and posting it on my blog later this week.

    • ali62b said, on 22/02/2010 at 12:00

      That’s it! its more clean and more readable and more easy to test. I really enjoyed your solution and cant wait to see your post online. Don’t forget to notify whenever you post it.
      Thanks for sharing your idea here I really looking for a way to put the sort and page logic out of my ActionMethod.

    • Marius said, on 02/04/2010 at 22:13

      In order to use LINQ on the Model you will need to cast it to a known type am I right?How do you cast?

      • Ryan said, on 03/04/2010 at 01:23

        @Marius, I haven’t gotten back to checking this out. I think you may be right that the model is probably of type object here. I hadn’t considered that. I’m actually doing the paging and sorting inside my service layer right now for several reasons, the biggest being that I need to sort and page before projecting from my model to a view model/DTO. I don’t want to expose the internals of my model to the MVC layer.

  2. Ryan said, on 04/03/2010 at 22:08

    Ali, sorry I haven’t gotten back to you sooner. I’ve been swamped recently so it’s probably going to be a while before I get to that post.

  3. Paresh said, on 24/03/2010 at 13:13

    Hi Ali,

    I tried ur code the page gets loaded first but the paging doesn’t seems to work. Can you please provide working Demo project ?

    • ali62b said, on 24/03/2010 at 21:24

      I Updated the post.
      You can now download the sample code.
      Hope this helps.

      • Paresh said, on 25/03/2010 at 13:30

        Hi Ali,

        Thanks for the project
        I am able to run the project but not able to call the product page.

      • ali62b said, on 26/03/2010 at 11:48

        Hi Paresh,
        Did you try …./Product/List Url ?

      • Paresh said, on 26/03/2010 at 13:27

        Thanks Ali, Its working.
        Now i will able to merge ur code in my project, will reach you for any further doubt.
        Thanks for quick reply.. :)

      • ali62b said, on 26/03/2010 at 18:36

        Glad to hear that it helped, and good luck.

  4. [...] look at it here : http://www.asp.net/community —> Article of Day and look for my article : Building a Data Grid in ASP.NET MVC Have [...]

  5. [...] Building a Data Grid in ASP.NET MVC (Ali Bastani) [...]

  6. licnikontaktioglasi said, on 09/06/2010 at 21:51

    Thanks for post from Australia

    Jozefin

  7. [...] have tried using the "https://mvcsharp.wordpress.com/2010/02/11/building-a-data-grid-in-asp-net-mvc/&quot; but the generic extension method is anticipating to know the specific type that it should work [...]

  8. [...] – MVC#, ASP.NET MVC 2 RC 2 & VS 2010 RC https://mvcsharp.wordpress.com/2010/02/11/building-a-data-grid-in-asp-net-mvc/ – Building a Data Grid in ASP.NET MVC, paging, sorting Table sorting & pagination with [...]

  9. websoftcreation said, on 14/02/2012 at 04:18

    This is very useful for developers.Thanks for uploading it.

    Regards
    Websoftcreation,Jaipur

  10. atul said, on 03/07/2012 at 05:39

    Way too basic but i still appreciate ur contribution..but that would be so great if you can show how to update and delete data using custom edit template and row template

  11. Erwin said, on 16/09/2012 at 16:18

    There is certainly a great deal to learn about this topic.

    I like all of the points you’ve made.


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: