Using jQuery Grid with ASP.NET MVC

11 במרץ 2011

7 comments

Many newcomers to ASP.NET MVC are asking about a replacement for the classical ASP.NET WebForms GridView server control. 

As always (almost) there are 3 possible solutions:

  1. Do it your self – Write an HTML helper that renders some TABLE, TR and TD elements. For sure this is the hardest solution but it gives you absolute control over grid behavior and there is no need to learn new API because you are the owner of every bit and byte.
  2. Commercial implementation – You can find many grid commercial implementations which are specialized for the ASP.NET MVC framework. Telerik has nice demos that you can play with before making a decision.
  3. Open source solution – Many client side frameworks offers a general purpose grid implementation. You can take a look at ExtJS capabilities or Dojo.  

Not surprising jQuery too has a nice "griding" capabilities. The implementation is offered through a plugin named jQuery Grid which can be downloaded here.
The plugin is a pure JavaScript solution and there is no integration with ASP.NET MVC.

Phil Haack already posted a sample which shows how you can integrate jQuery Grid with ASP.NET MVC. However, the sample is focused around the internal implementation. In this blog post I would like to present a way to make jQuery Grid more MVC friendly. The purpose is to have a simple HTML helper which does all the hard work of coordinating client and server side needs and also to simplify the controller implementation.

Suppose we have the following model class:

public class Person
{     public int ID { get; set; }     [Required(ErrorMessage="Please specify a first name")]     [Display(Name = "First Name")]     public string FirstName { get; set; }     [Required(ErrorMessage = "Please specify a last name")]     [Display(Name = "Last Name")]     public string LastName { get; set; }
}

And we would like to create a controller action which returns a view with a nice AJAX grid. The controller actions should look something like that:

public ActionResult Index()
{     return View();
}
public ActionResult GetPersons(GridRequestParams gridRequestParams)
{
    using (BookContext context = new BookContext())
    {
        return this.GridData(context.Persons, gridRequestParams);
    }
}

The first action returns a view and the second action is responsible for returnning the actual data that is displayed by the grid. 
The view should construct a new grid using an HTML helper which allows us to work the same way as we do with plain HTML content (TextBox, CheckBox and others):

@{

    Html.Grid("gridPersons", Url.Action("GetPersons"))

        .Column("ID", "ID", 10, visible: false)

        .Column("FirstName", "First Name", 200)

        .Column("LastName", "Last Name", 200)

        .Style("persons")

        .PageSize(7)

        .DefaultSortColumn("LastName")

        .DefaultSortOrder(false)

        .Render();

}

Executing above code should yield the following browser output:

jQuery Grid

As you can see, generating the grid is quite easy and you get a full AJAX behavior for paging and sorting. If you are interested in seeing how you can implement those easy helpers then keep reading. Else, go to the end of this post and download the full sample.

In order to support this "lightness" code we need to work a bit hard and write some server and client side code.

Let's start with the required HTML helper named "Grid" which is responsible for collecting all grid general properties (like url, columns and sort order). The HTML helper then renders into the HTML response stream all the HTML and data that is required by the client side code.

Below is the code for "Render" method inside the new HTML helper. Please note how the method is deserializing  some C# objects into XML string. The XML is embedded inside the HTML response. Later on, the client side code will use this XML to construct a jQuery Grid.

public void Render()
{     Validate();     //     //  Convert grid information into xml descriptors     //     GridXmlDescription gridLayout = new GridXmlDescription()     {         url = this.url,         pageSize = this.pageSize,         sortColumn = this.defaultSortColumn,         sortOrder = (this.defaultSortOrderAsc ? "asc" : "desc"),     };     foreach (ColumnBuilder column in this.columns)     {         GridColumnXmlDescription layoutColumn = new GridColumnXmlDescription() {                                              name = column.Name,                                              header = column.Header,                                              width = column.Width,                                             visible = column.Visible};         gridLayout.columns.Add(layoutColumn);     }     //     //  Covert Xml descriptors to raw XML     //     string xml;     using (StringWriter writer = new StringWriter())     {         GridXmlDescription.Serializer.Serialize(writer, gridLayout);         xml = writer.GetStringBuilder().ToString();     }     //     //  Build the HTML reuired by the grid     //     helper.ViewContext.Writer.Write("<div id=\"" + this.id + "\"");     helper.ViewContext.Writer.Write(" data-isGrid=\"true\"");     helper.ViewContext.Writer.Write(" class=\"" + this.style + "\"");     helper.ViewContext.Writer.WriteLine(">");     helper.ViewContext.Writer.WriteLine("<xml>");     helper.ViewContext.Writer.WriteLine(xml);     helper.ViewContext.Writer.WriteLine("</xml>");     helper.ViewContext.Writer.WriteLine("</div>");
}

The generated HTML should be something like that:

<div id="gridPersons" data-isGrid="true" class="persons">
  <script type="text/xml" id="gridPersons_xml">
    <gridLayout xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
      <url>/Home/GetPersons</url>
      <pageSize>7</pageSize>
      <sortColumn>LastName</sortColumn>
      <sortOrder>desc</sortOrder>
      <columns>
        <column name="ID" header="ID" width="10" visible="false" />
        <column name="FirstName" header="First Name" width="200" visible="true" />
        <column name="LastName" header="Last Name" width="200" visible="true" />
      </columns>
    </gridLayout>
  </script>
</div>

Please note how the XML information is embedded inside a script element of type "text/xml". The browser ignores this content and will not try to render it.

Next, we need to write down the client side code which looks for the embedded XML information and constructs the jQuery Grid widget:

jQuery(document).ready(function () {     //     //  Find all div elements which are actually a grid     //     var grids = jQuery("div[data-isGrid=\'true']");     //     //  Inintialize each grid     //     grids.each(function () {         var grid = new Grid(jQuery(this));     });
});

The code searches for all div element with attribute "data-isGrid=true". Each matched div is considered the parent element for the new grid. The JavaScript object "Grid" is responsible for completing the job:

function Grid(rootDiv) {     var $this = this;     //     //  Prepare some usefull attributes     //     this.id = rootDiv.getAttr("id");     //     //  Extract the grid layout information from the HTML (as XML)     //     var gridDesc = jQuery(jQuery("#" + this.id + "_xml").html());     //     //  Extract required data from the embedded XML     //     var url = this.getXmlElement(gridDesc, "url");     var pageSize = this.getXmlElement(gridDesc, "pageSize", 10);     var sortColumn = this.getXmlElement(gridDesc, "sortColumn", null);     var sortOrder = this.getXmlElement(gridDesc, "sortOrder", "desc");     //     //  Add a table elememt to the mainDiv (table element is required by the jQuery grid plugin)     //     var tableId = this.id + "_table";     var table = jQuery("<table id='" + tableId + "' ></table>");     rootDiv.append(table);     //     //  Add a pager element     //     var pagerId = this.id + "_pager";     var pager = jQuery("<div id='" + pagerId + "' ></div>");     rootDiv.append(pager);     //     //  Read the xml grid layout and build an array of columnsModel     //     var columnsHeaders = [];     var columnsModel = [];     gridDesc.getElement("columns column").each(function () {         var column = jQuery(this);         columnsHeaders.push(column.getAttr("header"));         columnsModel.push({             name: column.getAttr("name"),             index: column.getAttr("name"),             width: column.getAttr("width"),             hidden: (column.getAttr("visible") == "false"),             isData: true         });     });     //     //  Build the actual grid     //     var options = {         jsonReader: {             root: "rows",             page: "page",             total: "total",             records: "records",             repeatitems: false,             id: "ID"         },         datatype: "json",         url: url,         mtype: "POST",         height: 250,         colNames: columnsHeaders,         colModel: columnsModel,         multiselect: false,         pager: pager,         rowNum: pageSize,         caption: ""     };     if (sortColumn != null) {         options.sortname = sortColumn;         options.sortorder = (sortOrder == "asc" ? "asc" : "desc");     }     table.jqGrid(options);
}

The code extracts the embedded XML information and constructs a jQuery Grid object using the plugin method named "jqGrid".

The most significant parameter that the jqGrid method recieves is the "url" parameter. This parameter should point to a server side action which returns a JSON data to be displayed in the grid. In our example the URL is set by the HTML helper to "/Home/GetPersons". This URL is handled by the controller's action GetPersons:

public ActionResult GetPersons(GridRequestParams gridRequestParams)
{
    using (BookContext context = new BookContext())
    {
        return this.GridData(context.Persons, gridRequestParams);
    }
}

As you can see the action returns a special action result named "GridData". This action result is responsible for converting the actual database query into a valid JSON result (that follows the right schema required by the jQuery Grid):

public class GridDataResult<T> : ActionResult
{     private GridResponseParams jsonResonse;     public GridDataResult(IQueryable<T> query, GridRequestParams gridParams)     {         string sidx = gridParams.sidx;         string sord = gridParams.sord;         int pageIndex = gridParams.page;         int pageSize = gridParams.rows;         //         //  Count of all persons in DB (is used by the grid pager)         //         int rowsCount = query.Count();         //         //  Sort all data         //         IQueryable<T> orderedQuery = query;         if (!string.IsNullOrEmpty(sidx))         {             orderedQuery = query.OrderBy(sidx, sord);         }         //         //  Cut into single page (after sorting)         //         var pageData = orderedQuery.Skip((pageIndex - 1) * pageSize).Take(pageSize).ToArray();         //         //  Convert the data into more grid friendly data (convert date into string)         //         var pageJsonData = (from row in pageData select (object)row).ToArray();         //         //  Build summary required by the grid          //         this.jsonResonse = new GridResponseParams()         {             total = ((int)Math.Ceiling((double)rowsCount / pageSize)),             page = pageIndex,             records = rowsCount,             rows = pageJsonData         };     }     public override void ExecuteResult(ControllerContext context)     {         JsonResult res = new JsonResult();         res.Data = this.jsonResonse;         res.JsonRequestBehavior = JsonRequestBehavior.AllowGet;         res.ExecuteResult(context);     }
}


Please note that the action is also hanlding paging and sorting manipulation.


As you probably suspect at the beginning of this post, implementing the HTML helper, action result and client side code is not a simple task. However, once we done that we can enjoy the power of simple ASP.NET MVC HTML helper and construct a Grid using only two single steps:

  1. Write a view which uses the HTML helper named "Grid"
  2. Write a controller's action which returns a collection of Model object

The full sample code can be downloaded here

Add comment
facebook linkedin twitter email

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

7 comments

  1. nadav hury 3 באוגוסט 2011 ב 15:55

    great post! thank u!

    Reply
  2. Bill Au10 באוקטובר 2011 ב 9:59

    Good start. I want more about these. As I want to write application with relational database with MVC 3 and jqgrid.

    Reply
  3. Russel6 בפברואר 2012 ב 18:39

    Awesome!! Simply Superb!!! Thank you very very much…
    It is really inspiring ….

    Reply
  4. Bob King10 בפברואר 2012 ב 23:54

    Great post! I've been working with another wrapper and finding it more than a bit wonky, so I'm anxious to give this one a try. Now – any guidance on implementing the search capabilities in an MVC-friendly way?

    Reply
  5. Vikas21 בספטמבר 2012 ב 16:16

    Facing problem with sample code.

    Always getting exception at

    var url = this.getXmlElement(gridDesc, "url");

    Reply
  6. Vikas27 בספטמבר 2012 ב 13:20

    I am facing issue on following line of code in Grid.Js for IE8 & IE7.

    var url = this.getXmlElement(gridDesc, "url");

    Your help is appreciated.

    Reply
  7. oric20 באוגוסט 2013 ב 21:02

    Samples was fixed and tested on IE8+

    Reply