Ajax loading of MVC WebGrid in partial view

In previous posts I have discussed some basics of how to use WebGrid in MVC applications. While implementing sorting for one of the grids of a MVC application, I ran into an interesting problem when WebGrid was contained inside a partial view. Following screenshot describes the structure of grid I was implementing.

webgrid in partial view

I have a dropdown that allows user to filter results based on a criteria. When user selects an option from this dropdown list, an Ajax request is initiated to get new results based on the selection. The result of this Ajax action is a partial view that populates WebGrid below the filter section dropdown list.

Here are the problems that you will run into when WebGrid is in partial view.

  • When you will clock on a header to sort the results, you will loose the selection from the dropdown list as filter parameter. That means result from sorting will return results that will use default filter criteria.
  • When you will click on header to sort the results, the partial view that contains WebGrid is loaded as a separate view instead of being part of the page itself.

Before I go into details of these issues and how I fixed it, let me show some prototype code snippets that are used for this implementation.

Main View

 <div class="panel">
   <div class="panel-body">
    @using (Ajax.BeginForm("ViewPosts", "Grid", gridDataAjaxOpions))
    {
     <span>Published since:</span>@Html.DropDownListFor(m=>m.PostFilter.PublishedSinceDays, 
       daysFilterList, new{id="publishedSinceDays"});
    }
   </div>
</div>
@Html.Partial("_BlogPostList", Model)
    

Partial View

<div id="postGridPanel">
    @grid.GetHtml(
        htmlAttributes: new { id = "postsgrid" },
        columns: grid.Columns(
            grid.Column("Title", "Blog title"),
            grid.Column("Author", "Blog Author", @<div post_id="@item.PostId">@item.Author</div>),
            grid.Column("DateCreated", "Created On", @<i>@item.DateCreated.ToString("yyyy-MM")</i>, 
             "mystyle", false)
            ),
        mode: WebGridPagerModes.Numeric | WebGridPagerModes.NextPrevious,
        tableStyle: "table table-striped",
        footerStyle: "pagination"
         )
</div>
    

Query string parameters are keys to sorting

The way sorting work in WebGrid is that table headers are anchor tags that contain sorting parameters (column names, sort direction etc.) in query string of the link. Following code snippet shows how the tag looks like in my prototype implementation.

<a href="/Grid/ViewPosts?sort=Title&sortdir=ASC">Blog title</a>
    

You can see that this link contains sort and sortdir query string paremeters that drive sorting for my column Blog title. Most important thing to note about this is that when you will click on header to sort the records, it is going to send a GET http request to controller to partial view. The path for this link is based on HttpContext.Request.Path. The underlying implementation of WebGrid builds the link based on the value of request path. Therefore if your AJAX request is sending POST request to action that has name different than the one that was used to load the records initially then your link for header will point to that action method. Following code snippets show actions implemented in controller.

public ActionResult ViewPosts()
{...}

[HttpPost]
public PartialViewResult ViewPostResults(PostCollection model)
{...}
    

If you will implement your AJAX loading of WebGrid, the your header links will look like /Grid/ViewPostResults. Therefore when you will click on header, then you will get partial view loaded as main view. This means the key to solving the issue is to make sure that header link points to correct action that executes GET request for initial loading. Therefore to take care of this issue, I made sure that action methods for GET and POST request are same. In my case the action methods look like as shown below.

public ActionResult ViewPosts()
{...}

[HttpPost]
public PartialViewResult ViewPosts(PostCollection model)
{...}

Pass filter parameters as part of query string

Second issue we have is that when you click on header to sort results, the filter parameters used to trigger POST request are lost because GET request does not carry values selected in dropdown list. Therefore the solution to this issue is contained in query string once more. If we can append our dropdown selected value as additional query string parameter in header link, then we have what we needed for our action method. Following shows how header link looks like in my implementation.

http://dev.byteblocks.com/Grid/ViewPosts?sort=Title&sortdir=ASC&PublishedSinceDays=90
    

You can see that I have PublishedSinceDays query string parameter appended to header link. The easiest way to accomplish is implement a small JavaScript code that will modify header links when page is loaded. Following code snippet shows how I implemented this fix.

<script type="text/javascript">
    $(document).ready(function () {
        // Filter values.
        var filterDays = $('#publishedSinceDays').val();
        // Look for all anchor tags.
        $('#postsgrid thead a').each(function () {
            var href = $(this).attr("href");
            if (href.indexOf("PublishedSinceDays") == -1) {
                href += ("&PublishedSinceDays=" + filterDays);
            }
            $(this).attr("href", href);
        });
    });
</script>

Following code snippet shows how query string parameters are processed during GET request to find values of filter parameters passed in GET request.

private PostFilter CreateFilterFromRequest(HttpRequestBase request)
 {
    var filter = new PostFilter() {PublishedSinceDays = 30};

    if (!string.IsNullOrEmpty(Request["PublishedSinceDays"]))
    {
        var days = -1;
        filter.PublishedSinceDays = 
          Int32.TryParse(Request["PublishedSinceDays"], out days) ? days : 30;
    }
    return filter;
}

public ActionResult ViewPosts()
{
    var filter = CreateFilterFromRequest(this.Request);
..... and more...
}
    

Demo

You can see this technique in action by clicking here.

comments powered by Disqus

Blog Tags