Rewriting the MvcContrib Grid part 3 – GridModels and GridRenderers
February 23, 2009 – 19:47
This is part 3 of a series about the new MvcContrib grid.
- Part 1 – Introduction
- Part 2 – New Syntax
- Part 3 – GridModels and GridRenderes
- Part 4 – Limitations of the WebFormsViewEngine
- Part 5 – The Action Syntax
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
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.
12 Responses
Hi Jeremy, thanks for the changes. They are cool. I wrote about them on my blog http://fknet.wordpress.com/2009/03/04/exploring-new-mvccontribgrid/ and also some feedback for improvements.
Frantisek
GridModel question:
does ‘GridModel’ expose the Model ?
ie. I want to take in a specific type…let’s say :GridModel<IList>
But inside the PeopleGridModel I want to provide some possible alternative linq expressions on the model(ie. OrderBy, Where, etc)
Might look like:
public class PersonGridModel : GridModel<IList> {
public PersonGridModel() {
this.Model.OrderBy(x => x.Name)
Column.For(x => x.Name);
Column.For(x => x.DateOfBirth););
}
}
?
Perhaps I should just use GridModel<IEnumerable>
Hi Steve,
No, the GridModel doesn’t expose the underlying datasource – its purpose is to store a grid definition rather than perform any manipulation of the data.
You could use a custom GridRenderer to do this as it does have access to the datasource, but surely sorting the data is a concern of the controller rather than the UI?
Duh, nevermind, sorry. I see now it takes the object type itself
GridModel
Not a list
Could you share how you created the ExcelGridRenderer? I am trying to create my own GridRenderer like you suggested and some sample code would be great!
Thanks,
David
Hi David,
Here’s a sample: http://www.jeremyskinner.co.uk/files/ExcelGridRenderer.cs
This builds up some Excel-XML for rendering the grid. Hope this helps.
If we are using separate GridModel class like
public class UserGridModel : GridModel
{
public UserGridModel()
{
Column.For(u => Id);
Column.For(u => u.Name);
Column.For(u => u.Email);
}
}
How do we add Html.ActionLink in this case as I don’t see any reference to HtmlHelper available here.
Amitabh, I replied to your question on the other post (http://www.jeremyskinner.co.uk/2009/02/22/rewriting-the-mvccontrib-grid-part-2-new-syntax/ )
I’m attempting to use SortableHtmlGridRenderer. That renderer assumes that the SortBy and SortOrder values are in the query string before the page is rendered. There’s two terrible assumptions there; namely, the fact that they exist at all and the fact that they exist in the query string. They should be coming out of Request.Params and if that fails it should check session or give me a delegate to get those values when they don’t exist in Request.Params. And the first time you show the grid the user hasn’t clicked on any columns. I thought it would be smart enough to use the Sortable(true/false) methods in the model declaration.
Hi Brannon,
Thanks for your comments. Firstly, let me say that I didn’t actually write the SortableHtmlGridRenderer and it isn’t something I’m very familiar with (you might want to consider posting your comment on the MvcContrib mailing list, that way the author of the renderer is more likely to see it).
I will try and take a look at these problems and create a better sorting implementation for the grid at some point, but this is unlikely to be before the MVC2 release.
This is just fantastic. I’m not sure why I haven’t come across this before. I did a substantial MVC project last year which was etremely rewarding but challenging and this would have been great had I found it. Thanks for putting in the work so we don’t have to!