Using a grid that can sort, page, and filter in Asp.Net MVC3–Part 1–Using the WebGrid WebHelper

If you are coming from Asp.Net WebForms development, you are probably use to controls that include a lot of base functionality out of the box.  Some of these controls were the DataGrid and GridView, which would construct an html table for you without writing a single line of html.  While these controls offered productivity gains because you did not need to mess with the html, css, or JavaScript in order to get them to run, they gave you little control on what the output was, and often times, the output was less than desirable.

In Asp.Net MVC development, you have complete control of your html output.  This is great news for those that want this type of power, but for those that still look for the productivity gains of some type of grid control, using MVC had its drawbacks.  There have been a few grid controls provided by the community (such as the one in MvcContrib), however, there is now one available in MVC3 out of the box.  With the release of MVC3 and WebMatrix, there is now a suite of web helpers, one of which is the WebGrid, which we will be using in Part 1 of this series to create a grid that can sort, page, and filter data on an html page.

The WebGrid WebHelper is located in the System.Web.WebHelpers assembly, which is referenced by default in a new MVC3 application. 

Getting the MvcWebGrid app setup

To begin, create a new MVC3 application called MvcWebGrid.  Make sure you select Empty for the template, and that the view engine is set to Razor.  Next, right click on the Controllers folder and select add->New Controller.  Name the controller HomeControler, and leave the checkbox to create the CRUD methods unchecked.

For simplicity sake, we will not connect to a real database in this sample.  Instead, I have created a class that returns a lists of music albums (generated from the MvcMusicStore tutorial at mvcmusicstore.codeplex.com).  You can find this class in the code download (linked at the end of this article), or here on github.  This is a simple static class with a single method called GetAlbums, which returns an IEnumerable.  Add this class to your models folder.

In your home controller, modify your Index action method to like like so:

   1:  public ActionResult Index()
   2:  {
   3:      var albums = AlbumRepo.GetAlbums();
   4:      return View(albums);
   5:  }

Nothing fancy happening here, we are just returning the whole list of albums to our view.  We still need to create our index view, but before we do, lets update the _Layout.cshtml file in the Views/Shared folder so our site has a bit more of a look to it.  Modify the file like so:

   1:  <!DOCTYPE html>
   2:  <html>
   3:  <head>
   4:      <title>@ViewBag.Title</title>
   5:      <link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" />
   6:      <script src="@Url.Content("~/Scripts/jquery-1.4.4.min.js")" type="text/javascript">
   7:      </script>
   8:  </head>
   9:   
  10:  <body>
  11:      <div id="header">
  12:          Welcome to my awesome music site where 
  13:          a deep dish of awesome stew is served up daily
  14:          <p/> <p /><p />
  15:      </div>
  16:      
  17:      @RenderBody()
  18:   
  19:      <div id="footer">
  20:          <p/> <p /><p />
  21:          You have reached the bottom of this page, now what?
  22:      </div>
  23:  </body>
  24:  </html>

Adding the WebGrid to the app

Now that we have a basic layout for our site, lets create the index view.  Go back to the home controller, right click anywhere within the index action method, and select add view.  Keep all the defaults and click Add.

Change the new view to look like the following:

   1:  @model IEnumerable<Album>
   2:   
   3:  @{
   4:      ViewBag.Title = "MVC Music Store Grid";
   5:  }
   6:   
   7:  @using (Html.BeginForm())
   8:  {
   9:      <fieldset>
  10:          <legend>Search</legend>
  11:   
  12:          <div>
  13:              Album Id:
  14:          </div>
  15:          <div>
  16:              <input type="text" id="albumid" name="albumid" />
  17:          </div>
  18:   
  19:          <div>
  20:              Album Name:
  21:          </div>
  22:          <div>
  23:              <input type="text" id="albumName" name="albumName" />
  24:          </div>
  25:   
  26:          <p>
  27:              <input type="submit" value="Search" />
  28:          </p>
  29:      </fieldset>
  30:  }
  31:   
  32:  <div id="myGrid">
  33:      @Html.Partial("_grid", Model)
  34:  </div>

We will be separating the WebGrid into its own partial view, so right click on the Views/Home folder, select add, and then view.  Name the view _grid, and check the “Create as a partial view” checkbox, then click add.  Prepending the name of our Razor view with a underscore tells Asp.Net that this file should not be served if requested directly, which is perfect for our partial views.

In our grid partial view, we will now be using the new WebGrid helper.  We start off creating an instance of the WebGrid (passing in our model into the constructor), and then call the GetHtml method which generates the html for the grid and outputs it to the screen:

   1:  @model IEnumerable<Album>
   2:   
   3:  @{
   4:      var grid = new WebGrid(Model);
   5:      @grid.GetHtml();
   6:  }

When we run the app, we get a web page that looks like so:

image

If you inspect the html for the new grid, you will see that it is clean markup, with no added styles or nasty viewstate that we have seen in the past with WebForms.  You might also notice that the table html is semantically correct, where the header is in the thead section, the content in the tbody section, and the paging in the tfoot section.  Having your tables formatted properly will go a long ways if you want to try to extend your table functionality using JavaScript libraries such as jQuery, where it was often difficult to achieve this in WebForms.

Another bonus is that the paging and sorting already work!  Out of the box, the WebGrid provides these functions by doing Get requests and passing in a few values over the query string.  The WebGrid will see these values in the new request, and adjust the output accordingly.  This means that the WebGrid expects the entire set of data in each view in order to generate the paging properly.  For small sets of data this is not a problem, but even in our sample app you can notice a lag when hitting the links.  Fixing this issue is out of the scope of this article, but just be aware that you will need to come up with a different solution if your data sets are large.

The WebGrid provides a lot of options for configuration.  These options are provided by passing in parameters to the WebGrid’s constructor, as well as the GetHtml method.

image

image

We will modify some of these options to add some Ajax functionality to the screen (so that paging and sorting don’t refresh the entire screen), as well as change how some of the columns appear.  Modify _grid.cshtml to look like so:

   1:  @model IEnumerable<Album>
   2:   
   3:  @{
   4:      var grid = new WebGrid(Model, rowsPerPage: 15, ajaxUpdateContainerId: "myGrid");
   5:      @grid.GetHtml(columns: grid.Columns(
   6:                          grid.Column("AlbumId", "Album Id"),
   7:                          grid.Column("Title", "Title"),
   8:                          grid.Column("Artist", "Artist")
   9:                          ));
  10:  }

In the constructor, we setting the size of the page to handle 15 albums at a time, as well as setting the container id of the div in the index.cshtml page that holds our grid.  The simple act of setting the ajaxUpdateContainerId will automatically enable our screen to use Ajax to update the screen on pages and sorts.  All we need to do is make sure jQuery is included in the project, which it is by default in the _Layout.cshtml page.

The GetHtml method take a columns param, where we pass in a list of column objects we want to use on the grid.

One thing I don’t particularly care for is how the Ajax is inserted into the page.  If you take a look at the source, all of the links now have onclick methods inserted into them.  This gets away from the unobtrusive JavaScript theme the rest of MVC3 strived so hard to achieve.  We will talk some more about unobtrusive JavaScript in the next section.

Filtering the grid

You probably noticed that our index.cshtml page has had a form in it that we will be using to filter the data in the grid.  This form is simple by design, and will allow us to filter the form either by the Album Id, or the Album Name.  Before we do anything with the form, lets add a new action method in our home controller that will let us filter the data:

   1:  [HttpPost]
   2:  public ActionResult Index(int? albumId, string albumName)
   3:  {
   4:      var albums = AlbumRepo.GetAlbums();
   5:   
   6:      if (!string.IsNullOrEmpty(albumName))
   7:          albums = albums.Where(a => a.Title.Contains(albumName)).ToList();
   8:   
   9:      if (albumId.HasValue)
  10:          albums = albums.Where(a => a.AlbumId == albumId).ToList();
  11:   
  12:      return View(albums);
  13:  }
 
This new action method has the same name as our other action method, but has the HttpPost attribute which tells MVC that this method will only respond to post requests.  We accept two params into the method that will be used to filter the data. (Note: the code in this action method is not a good way to filter data, it is just demo code, so don’t reuse it in production).
Now when we run the app, we can filter the data.  However, our site is doing a complete post and refresh of the page every time we do a search.  We will now add Ajax to the form.
We will use the new unobtrusive Ajax support that is in MVC3 to accomplish this, and do it without having to write a single line of JavaScript.  The Ajax.BeginForm has been around for awhile in MVC, but it has been changed to now use jQuery and not the Microsoft Ajax library.  Also, there is no more JavaScript inserted into the screen on your behalf (unlike the WebGrid Ajax support shown above).  Instead, MVC3 uses HTML5 data attributes to mark the form, and a special script that is used to wire the Ajax functionality up at runtime.
In index.cshtml, change the Html.BeginForm line to the following:
   1:  @using (Ajax.BeginForm(new AjaxOptions 
   2:      { InsertionMode = InsertionMode.Replace, UpdateTargetId = "myGrid" }))
   3:  {
The AjaxOptions parameter tells our form how to handle the Ajax reqeuest.  In this case, we are telling it we want the InsertionMode to replace the current contents in the myGrid div tag (much like we did with the paging and sorting on the grid).  All that is left to do is add the JavaScript file that will make this magic happen.  In the _Layout.cshtml file, add the following line directly below the line that includes jQuery:
   1:  <script type="text/javascript" src="../../Scripts/jquery.unobtrusive-ajax.min.js">
   2:  </script>  

Make sure you don’t include any of the JavaScript files that start with Microsoft, as those are just included for backwards compatibility.  In MVC3, you want to be using the scripts who’s filenames start with jQuery.

If you run the app now, you might notice a strange oddity when filtering the data.  The entire response is copied into the myDiv tag, meaning that we get another copy of the form, along with the WebGrid table again.  When using Ajax.BeginForm, the response that is sent is expected to only be the content that changes, not the entire page.  We can easily fix this and return just the updated grid by changing the index action method that accepts the HttpPost to return a partial view of only the grid and not the entire index view (this is why we separated the grid out from the index view in the first place).

Update the index action method for the filtering to return a partial view by modifying the last line as such:

   1:  return PartialView("_grid", albums);

<p>
  Now when you enter an a AlbumId or a partial Album name, the grid will filter and display properly.
</p>

<p>
  <strong>Summary</strong>
</p>

<p>
  In this post, I went over how the new WebGrid html helper can be used to quickly generate html tables with paging, sorting, and filtering.&#160; I also showed how to enable the Ajax features of the WebGrid, as well as using the new unobtrusive Ajax features in MVC3.&#160; If you are familiar with webforms development, but new to MVC, then you can use these techniques to easily add rich functionality to your MVC apps.&#160; These techniques did not require deep knowledge of html or JavaScript to accomplish our desired results.
</p>

<p>
  In my next artilce, I will show how to do the same thing using straight html and JavaScript (via jQuery).&#160; We won’t rely on the framework to generate any of our code, but will instead dig deep and get close to the metal.&#160; I think you will discover it is not as scary as it sounds.
</p>

<p>
  You can download the code via github (click the Download button to download all the code in a zip file):<a href="https://github.com/elylucas/MvcWebGrid-Part1">https://github.com/elylucas/MvcWebGrid-Part1</a>
</p>