Rewriting the MvcContrib Grid part 3 - GridModels and GridRenderers

This is part 3 of a series about the new MvcContrib grid.

Internally, the new MvcContrib grid makes use of the GridModel and GridRenderer classes to create the grid definition and then render it. It is possible to make use of these classes yourself to customise the grid.

Grid Models

The GridModel stores the definition for the grid. That is, it contains column definitions, custom attributes and the renderer to use (see below). Each time you call a method on the Grid class it is delegated to the underlying GridModel. For example, calling the Columns defines a collection of GridColumn objects which are instantiated and stored in the GridModel:

<%= Html.Grid(Model.People).Columns(column => {
               column => column.For(x => x.Name);
               column => column.For(x => x.DateOfBirth);
}) %>

Instead of defining columns in your view, you can create your own GridModel and then pass this object to a call to Html.Grid:

public class PersonGridModel : GridModel<Person> {
    public PersonGridModel() {
        Column.For(x => x.Name);
        Column.For(x => x.DateOfBirth);
    }
}
<%= Html.Grid(Model.People).WithModel(new PersonGridModel()) %>

Note that GridModels aren't limited just to column definitions. Everything that you could invoke by calling Html.Grid has an equivalent method on the GridModel (eg setting Empty Text, custom section overrides, header attributes etc).

There are several advantages of this approach:

  • If you have large grid definitions using a GridModel can help to keep the view uncluttered.
  • It is easy to re-use the same grid in multiple locations.
  • You can re-use common grid code through inheritance.

There is one other benefit to using a GridModel. Visual Basic 2008 doesn't support Lambda Statements meaning that it is not possible to use the Columns() method on the Grid directly. By using a grid model then you can still make use of the grid from within VB (if you're that way inclined).

Grid Renderers

Internally, it is the job of the GridRenderer to take a GridModel and render it to the output stream. The default GridRenderer is the HtmlTableGridRenderer. Which, surprisingly enough, renders the grid as an HTML table. By specifying your own GridRenderer, you can completely alter how the grid is rendered without resorting to completely rewriting the grid component.

For example, in one of my larger ASP.NET MVC applications I frequently have the need to output data as an Excel spreadsheet and I used the grid to do so. In order to make this work I had to inherit from GridBase and override the methods related to generating output and then define a new set of helper methods to cater for the various Grid overloads. In the end I ended up with several overloads like this:

public static void ExcelGrid<T>(this HtmlHelper helper, string viewDataKey, Action<IRootGridColumnBuilder<T>> columns) where T : class
public static void ExcelGrid<T>(this HtmlHelper helper, string viewDataKey, Action<IRootGridColumnBuilder<T>> columns, Action<IGridSections<T>> sections) where T : class
public static void ExcelGrid<T>(this HtmlHelper helper, string viewDataKey, IDictionary htmlAttributes, Action<IRootGridColumnBuilder<T>> columns) where T : class 
public static void ExcelGrid<T>(this HtmlHelper helper, string viewDataKey, IDictionary htmlAttributes, Action<IRootGridColumnBuilder<T>> columns, Action<IGridSections<T>> sections) where T : class
public static void ExcelGrid<T>(this HtmlHelper helper, IEnumerable<T> dataSource, Action<IRootGridColumnBuilder<T>> columns) where T : class
public static void ExcelGrid<T>(this HtmlHelper helper, IEnumerable<T> dataSource, Action<IRootGridColumnBuilder<T>> columns, Action<IGridSections<T>> sections) where T : class 
public static void ExcelGrid<T>(this HtmlHelper helper, IEnumerable<T> dataSource, IDictionary htmlAttributes, Action<IRootGridColumnBuilder<T>> columns) where T : class 
public static void ExcelGrid<T>(this HtmlHelper helper, IEnumerable<T> dataSource, IDictionary htmlAttributes, Action<IRootGridColumnBuilder<T>> columns, Action<IGridSections<T>> sections) where T : class

...which was largely a duplication of the Grid overloads in mvccontrib. They could then be used like this:

<% Html.ExcelGrid(Model.People, column => {
         column.For(x => x.Name);
         column.For(x => x.DateOfBirth);
 }); %>

...which would produce the grid as Excel-XML.

With the new grid I can create a new GridRenderer implementation and then use it like this, with no need to create additional HtmlHelper extensions:

<%= Html.Grid(Model.People).Column(column => {
               column => column.For(x => x.Name);
               column => column.For(x => x.DateOfBirth);
}).RenderUsing(new ExcelGridRenderer<Person>());

The ExcelGridRenderer could inherit from GridRenderer and override the methods needed for constructing the grid header, footer, rows etc. You may notice that the methods on GridRenderer are very similar to those on the GridBase class. This is not a coincidence - most of the code in the GridRenderer is re-used from the old GridBase.

Note it is also possible to specify the renderer from within a GridModel:

public class PersonGridModel : GridModel<Person> {
    public PersonGridModel() {
        Column.For(x => x.Name);
        Column.For(x => x.DateOfBirth);
        RenderUsing(new ExcelGridRenderer<Person>());
    }
}

Hopefully this brief introduction to GridRenderers and GridModels shows how it is possible to make your grids extendable and reusable. In my next post I'm going to focus on Paging in more detail. My next post covers working around some of the issues of the WebForms View Engine.

Written on February 23, 2009