Code based ASP.NET MVC GridView
Edit on GitHub
Earlier this week a colleague of mine asked me if there was such thing as a  DataGrid or GridView or something like that in the ASP.NET MVC framework. My first answer was: "Nope!". I advised him to look for a nice foreach implementation or using ExtJS, Dojo or similar. Which made me think... Why not create a simple GridView extension method which generates a nice looking, plain-HTML grid with all required features like paging, editing, deleting, alternating rows, ...?
The idea was simple: an extension method to the HtmlHelper class would be enough. Required parameters: a header and footer template, item template, edit item template, ... But how to pass in these templates using a simple C# parameter... Luckily, C# 3.0 introduced lambdas! Why? They are super-flexible and versatile! For instance, take the following code:
[code:c#]
// C# code:
 public void RenderPerson(Person p, Action<T> renderMethod) {
     renderMethod(p);
 }
// ASP.NET code:
 <% RenderPerson(new Person(), person => { %>
     Hello! You are <%=person.Name%>.
 <% } %>
[/code]
It translates nicely into:
[code:c#]
Response.Write("Hello! You are Maarten.");
[/code]
Creating a GridView extension method should not be that hard! And it sure isn't.
Live demo
Perhaps I should put this last in my blog posts, but there are always people who are only reading the title and downloading an example:
- MvcGridView.zip (478.35 kb) (full example)
 - MvcGridView-1.0.zip (25.98 kb) (ASP.NET MVC 1.0 version)
 
1. The GridView extension method
Quite short and quite easy:
[code:c#]
public static class GridViewExtensions
 {
     public static void GridView<T>(
         this HtmlHelper html,
         GridViewData<T> data,
         Action<GridViewData<T>> headerTemplate,
         Action<T, string> itemTemplate,
         string cssClass,
         string cssAlternatingClass,
         Action<T> editItemTemplate, 
         Action<GridViewData<T>> footerTemplate)
     {
         headerTemplate(data);
        int i = 0;
         foreach (var item in data.PagedList)
         {
             if (!item.Equals(data.EditItem))
             {
                 itemTemplate(item, (i % 2 == 0 ? cssClass : cssAlternatingClass));
             }
             else
             {
                 editItemTemplate(item);
             }
            i++;
         }
        footerTemplate(data);
     }
 }
[/code]
2. GridViewData
Of couse, data will have to be displayed. And we'll need a property which sets the current item being edited. Here's my Model I'll be passing to the View:
[code:c#]
public class GridViewData<T>
 {
     public PagedList<T> PagedList { get; set; }
    public T EditItem { get; set; }
 }
[/code]
By the way, the PagedList<T> I'm using is actually a shameless copy from Rob Conery's blog a while ago.
3. The View
Of course, no rendered HTML without some sort of View. Here's a simplified version in which I pass the GridView<T> extension method the required data, header template, item template, edit item template and footer template. Also noice the alternating rows are simply alternating CSS styles (item and item-alternating).
[code:c#]
<%Html.GridView<Employee>(
     this.ViewData.Model,
     data => { %>
         <table class="grid" cellpadding="0" cellspacing="0">
     <% },
     (item, css) => { %>
         <tr class="<%=css%>">
             <td><%=Html.ActionImage<HomeController>(c => c.Edit(item.Id), "~/Content/edit.gif", "Edit", null)%></td>
             <td><%=Html.ActionImage<HomeController>(c => c.Delete(item.Id), "~/Content/delete.gif", "Delete", null)%></td>
             <td> </td>
             <td><%=item.Name%></td>
             <td><%=item.Email%></td>
         </tr>
     <% },
     "item",
     "item-alternating",
     item => { %>
         <%using (Html.Form<HomeController>(c => c.Save(item.Id), FormMethod.Post, new { id = "editForm" })) {>
             <tr class="item-edit">
                 <td><%=Html.SubmitImage("save", "~/Content/ok.gif", new { alt = "Update" })%></td>
                 <td><%=Html.ActionImage<HomeController>(c => c.Index(), "~/Content/cancel.gif", "Cancel", null)%></td>
                 <td> </td>
                 <td><%=Html.TextBox("Name", item.Name)%></td>
                 <td><%=Html.TextBox("Email", item.Email)%></td>
             </tr>
         <% } %>
     <% },
     data => { %> 
         </table>
 <% });%>
[/code]
4. The Controller
The Controller is perhaps the hardest part: it contains all methods that handle actions which are requested by the View. I have a Show action which simply shows the View with current data. Also, I have implemented an Edit and Save action. Make sure to check my example code download for the full example (earlier in this post).
[code:c#]
// ...
public ActionResult Show(int? page)
 {
     CurrentPage = page.HasValue ? page.Value : CurrentPage;
     GridViewData<Employee> viewData = new GridViewData<Employee>
     {
         PagedList = Employees.ToPagedList<Employee>(CurrentPage, 4)
     };
    return View("Index", viewData);
 }
public ActionResult Edit(int id)
 {
     GridViewData<Employee> viewData = new GridViewData<Employee>
     {
         PagedList = Employees.ToPagedList<Employee>(CurrentPage, 4),
         EditItem = Employees.Where( e => e.Id == id).FirstOrDefault()
     };
    return View("Index", viewData);
 }
public ActionResult Save(int id)
 {
     BindingHelperExtensions.UpdateFrom(
         Employees.Where(e => e.Id == id).FirstOrDefault(),
         Request.Form
     );
     return RedirectToAction("Show");
 }
// ...
[/code]
Note: based on ASP.NET MVC preview 3
This is an imported post. It was imported from my old blog using an automated tool and may contain formatting errors and/or broken images.


28 responses