ASP.NET MVC Tip #8 – Create an ASP.NET MVC GridView Helper Method

In this tip, you learn how to extend the ASP.NET MVC framework with a new helper method that displays an HTML table of database data.

Currently, the ASP.NET MVC framework does not include anything that is the direct equivalent of the ASP.NET Web Forms GridView control. If you want to display a table of database data then you must write out all of the HTML and inline script each and every time that you want to display the data. In this tip, I show you how to add a GridView() extension method to the HtmlHelper class.

An extension method is a method added to one class by another class. You can use extension methods to give existing classes additional super powers. In our case, we want to give the HtmlHelper class, the class that you use in an MVC View Page, a new GridView() method that renders an HTML table of database data.

You create an extension method in slightly different ways when working with Visual Basic .NET and when working with C#. You create an extension method with Visual Basic .NET by creating a module and decorating functions in the module with the <Extension> attribute. You create an extension method with C# by creating a static class and using the keyword this with the first parameter of each extension method exposed by the static class.

The code for the GridView() extension methods is contained in Listing 1.

Listing 1 – GridExtensions.vb (VB)

   1: Imports System
   2: Imports System.Text
   3: Imports System.Collections.Generic
   4: Imports System.Linq
   5: Imports System.Data.Linq.Mapping
   6: Imports System.Data.Linq
   7: Imports System.Web.UI
   8: Imports System.Web.Mvc
   9: Imports System.Web
  10: Imports System.Runtime.CompilerServices
  11:  
  12:  
  13: Namespace Helpers
  14:  
  15:     Public Module GridExtensions
  16:  
  17:         <Extension()> _
  18:         Public Function GridView(ByVal htmlHelper As HtmlHelper, ByVal table As ITable) As String
  19:             Return GridView(htmlHelper, table, Nothing, New GridViewOptions())
  20:         End Function
  21:  
  22:         <Extension()> _
  23:         Public Function GridView(ByVal htmlHelper As HtmlHelper, ByVal table As ITable, ByVal headers As String()) As String
  24:             Return GridView(htmlHelper, table, headers, New GridViewOptions())
  25:         End Function
  26:  
  27:         <Extension()> _
  28:         Public Function GridView(ByVal htmlHelper As HtmlHelper, ByVal table As ITable, ByVal includeLinks As Boolean) As String
  29:             Return GridView(htmlHelper, table, Nothing, includeLinks)
  30:         End Function
  31:  
  32:         <Extension()> _
  33:         Public Function GridView(ByVal htmlHelper As HtmlHelper, ByVal table As ITable, ByVal headers As String(), ByVal includeLinks As Boolean) As String
  34:             Dim options As New GridViewOptions()
  35:             If Not includeLinks Then
  36:                 options.ShowViewButton = False
  37:                 options.ShowEditButton = False
  38:                 options.ShowDeleteButton = False
  39:             End If
  40:             Return GridView(htmlHelper, table, Nothing, options)
  41:         End Function
  42:  
  43:         <Extension()> _
  44:         Public Function GridView(ByVal htmlHelper As HtmlHelper, ByVal table As ITable, ByVal headers As String(), ByVal options As GridViewOptions) As String
  45:             ' Show edit column?
  46:             Dim showEditColumn As Boolean = options.ShowViewButton Or options.ShowEditButton Or options.ShowDeleteButton
  47:  
  48:             ' Get identity column name
  49:             Dim identityColumnName As String = GridExtensions.GetIdentityColumnName(table)
  50:  
  51:             ' Get column names and headers
  52:             Dim columnNames = GridExtensions.GetColumnNames(table)
  53:             If IsNothing(headers) Then
  54:                 headers = columnNames
  55:             End If
  56:  
  57:             ' Open table
  58:             Dim sb As New StringBuilder()
  59:             sb.AppendLine("<table>")
  60:  
  61:             ' Create Header Row
  62:             sb.AppendLine("<thead>")
  63:             sb.AppendLine("<tr>")
  64:             If showEditColumn Then
  65:                 sb.Append("<th></th>")
  66:             End If
  67:             For Each header As String In headers
  68:                 sb.AppendFormat("<th>{0}</th>", header)
  69:             Next
  70:             sb.AppendLine("</tr>")
  71:             sb.AppendLine("</thead>")
  72:  
  73:             ' Create Data Rows
  74:             sb.AppendLine("<tbody>")
  75:             sb.AppendLine("<tr>")
  76:             Dim row As Object
  77:             For Each row In table
  78:                 If showEditColumn Then
  79:                     Dim identityValue As Integer = CType(DataBinder.GetPropertyValue(row, identityColumnName), Integer)
  80:                     sb.Append("<td><small>")
  81:                     If (options.ShowViewButton) Then
  82:                         sb.Append(htmlHelper.ActionLink(options.ViewButtonText, options.ViewAction, New With {.Id = identityValue}))
  83:                     End If
  84:                     sb.Append("&nbsp;")
  85:                     If options.ShowEditButton Then
  86:                         sb.Append(htmlHelper.ActionLink(options.EditButtonText, options.EditAction, New With {.Id = identityValue}))
  87:                         sb.Append("&nbsp;")
  88:                     End If
  89:                     If options.ShowDeleteButton Then
  90:                         sb.Append(htmlHelper.ActionLink(options.DeleteButtonText, options.DeleteAction, New With {.Id = identityValue}))
  91:                         sb.Append("</small></td>")
  92:                     End If
  93:                 End If
  94:                 For Each columnName As String In columnNames
  95:                     Dim value As String = DataBinder.GetPropertyValue(row, columnName).ToString()
  96:                     sb.AppendFormat("<td>{0}</td>", HttpUtility.HtmlEncode(value))
  97:                 Next
  98:                 sb.AppendLine("</tr>")
  99:             Next
 100:             sb.AppendLine("</tbody>")
 101:  
 102:             sb.AppendLine("</table>")
 103:             Return sb.ToString()
 104:         End Function
 105:  
 106:         Public Function GetColumnNames(ByVal table As ITable) As String()
 107:             Return table.Context.Mapping.GetMetaType(table.ElementType).PersistentDataMembers.Select(Function(m) m.Name).ToArray()
 108:         End Function
 109:  
 110:         Public Function GetIdentityColumnName(ByVal table As ITable) As String
 111:             Return table.Context().Mapping().GetMetaType(table.ElementType).DBGeneratedIdentityMember().Name
 112:         End Function
 113:     End Module
 114:  
 115: End Namespace

Listing 1 – GridExtensions.cs (C#)

   1: using System;
   2: using System.Text;
   3: using System.Collections.Generic;
   4: using System.Linq;
   5: using System.Data.Linq.Mapping;
   6: using System.Data.Linq;
   7: using System.Web.UI;
   8: using System.Web.Mvc;
   9: using System.Web;
  10:  
  11: namespace Tip8.Helpers
  12: {
  13:     public static class GridExtensions
  14:     {
  15:  
  16:         public static string GridView(this HtmlHelper htmlHelper, ITable table)
  17:         {
  18:             return GridView(htmlHelper, table, null, new GridViewOptions());
  19:         }
  20:  
  21:         public static string GridView(this HtmlHelper htmlHelper, ITable table, string[] headers)
  22:         {
  23:             return GridView(htmlHelper, table, headers, new GridViewOptions());
  24:         }
  25:  
  26:         public static string GridView(this HtmlHelper htmlHelper, ITable table, bool includeLinks)
  27:         {
  28:             return GridView(htmlHelper, table, null, includeLinks);
  29:         }
  30:  
  31:         public static string GridView(this HtmlHelper htmlHelper, ITable table, string[] headers, bool includeLinks)
  32:         {
  33:             var options = new GridViewOptions();
  34:             if (!includeLinks)
  35:             {
  36:                 options.ShowViewButton = false;
  37:                 options.ShowEditButton = false;
  38:                 options.ShowDeleteButton = false;
  39:             }
  40:             return GridView(htmlHelper, table, null, options);
  41:         }
  42:  
  43:         public static string GridView(this HtmlHelper htmlHelper, ITable table, string[] headers, GridViewOptions options)
  44:         {
  45:             // Show edit column?
  46:             bool showEditColumn = options.ShowViewButton || options.ShowEditButton || options.ShowDeleteButton; 
  47:  
  48:             // Get identity column name
  49:             string identityColumnName = GridExtensions.GetIdentityColumnName(table);
  50:  
  51:             // Get column names and headers
  52:             var columnNames = GridExtensions.GetColumnNames(table);
  53:             if (headers == null)
  54:                 headers = columnNames;
  55:  
  56:             // Open table
  57:             var sb = new StringBuilder();
  58:             sb.AppendLine("<table>");
  59:  
  60:             // Create Header Row
  61:             sb.AppendLine("<thead>");
  62:             sb.AppendLine("<tr>");
  63:             if (showEditColumn)
  64:                 sb.Append("<th></th>");
  65:             foreach (String header in headers)
  66:                 sb.AppendFormat("<th>{0}</th>", header);
  67:             sb.AppendLine("</tr>");
  68:             sb.AppendLine("</thead>");
  69:  
  70:             // Create Data Rows
  71:             sb.AppendLine("<tbody>");
  72:             sb.AppendLine("<tr>");
  73:             foreach (Object row in table)
  74:             {
  75:                 if (showEditColumn)
  76:                 {
  77:                     int identityValue = (int)DataBinder.GetPropertyValue(row, identityColumnName);
  78:                     sb.Append("<td><small>");
  79:                     if (options.ShowViewButton)
  80:                     {
  81:                         sb.Append(htmlHelper.ActionLink(options.ViewButtonText, options.ViewAction, new { Id = identityValue }));
  82:                         sb.Append("&nbsp;");
  83:                     }
  84:                     if (options.ShowEditButton)
  85:                     {
  86:                         sb.Append(htmlHelper.ActionLink(options.EditButtonText, options.EditAction, new { Id = identityValue }));
  87:                         sb.Append("&nbsp;");
  88:                     }
  89:                     if (options.ShowDeleteButton)
  90:                     {
  91:                         sb.Append(htmlHelper.ActionLink(options.DeleteButtonText, options.DeleteAction, new { Id = identityValue }));
  92:                     }
  93:                     sb.Append("</small></td>");
  94:                 }
  95:                 foreach (string columnName in columnNames)
  96:                 {
  97:                     string value = DataBinder.GetPropertyValue(row, columnName).ToString();
  98:                     sb.AppendFormat("<td>{0}</td>", HttpUtility.HtmlEncode(value));
  99:                 }
 100:                 sb.AppendLine("</tr>");
 101:             }
 102:             sb.AppendLine("</tbody>");
 103:  
 104:             sb.AppendLine("</table>");
 105:             return sb.ToString();
 106:         }
 107:  
 108:         public static string[] GetColumnNames(ITable table)
 109:         {
 110:             return table
 111:                 .Context
 112:                 .Mapping
 113:                 .GetMetaType(table.ElementType)
 114:                 .PersistentDataMembers.Select(m => m.Name)
 115:                 .ToArray();
 116:         }
 117:  
 118:         public static string GetIdentityColumnName(ITable table)
 119:         {
 120:             return table
 121:                 .Context
 122:                 .Mapping
 123:                 .GetMetaType(table.ElementType)
 124:                 .DBGeneratedIdentityMember
 125:                 .Name;
 126:         }
 127:     }
 128:  
 129: }

Listing 1 contains multiple version of the GridView() method. Each version of the GridView() method accepts a different set of parameters. For example, the first version of the GridView() method accepts a LINQ to SQL table and renders all of the columns and rows from the table. Other versions of the GridView() method enable you to customize the GridView headers and edit links.

The MVC view in Listing 2 demonstrates multiple ways of calling the GridView() method to display the contents of a database table.

Listing 2 – Index.aspx (VB)

   1: <%@ Page Language="VB" MasterPageFile="~/Views/Shared/Site.Master" AutoEventWireup="false" CodeBehind="Index.aspx.vb" Inherits="Tip8.Index" %>
   2: <%@ Import Namespace="Tip8.Helpers" %>
   3:  
   4: <asp:Content ID="indexContent" ContentPlaceHolderID="MainContent" runat="server">
   5:    
   6:    <h1>Simple Grid</h1>
   7:    
   8:    <%= Html.GridView(ViewData.Model) %>
   9:  
  10:    <h1>Simple Grid without Links</h1>
  11:    
  12:    <%= Html.GridView(ViewData.Model, false) %>
  13:  
  14:    <h1>Simple Grid with Custom Headers</h1>
  15:    
  16:    <%=Html.GridView(ViewData.Model, New String() {"AA", "BB", "CC", "DD"})%>
  17:    
  18:    <h1>Simple Grid with Custom Links</h1>
  19:    
  20:    <%=Html.GridView(ViewData.Model, Nothing, New GridViewOptions With {.ViewButtonText = "Look", .ShowEditButton = False, .ShowDeleteButton = False})%>
  21:  
  22:  
  23:  
  24: </asp:Content>

Listing 2 – Index.aspx (C#)

   1: <%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" AutoEventWireup="true" CodeBehind="Index.aspx.cs" Inherits="Tip8.Views.Home.Index" %>
   2: <%@ Import Namespace="Tip8.Helpers" %>
   3:  
   4: <asp:Content ID="indexContent" ContentPlaceHolderID="MainContent" runat="server">
   5:    
   6:    <h1>Simple Grid</h1>
   7:    
   8:    <%= Html.GridView(ViewData.Model) %>
   9:  
  10:    <h1>Simple Grid without Links</h1>
  11:    
  12:    <%= Html.GridView(ViewData.Model, false) %>
  13:  
  14:    <h1>Simple Grid with Custom Headers</h1>
  15:    
  16:    <%= Html.GridView(ViewData.Model, new string[] {"AA", "BB", "CC", "DD"} )%>
  17:    
  18:    <h1>Simple Grid with Custom Links</h1>
  19:    
  20:    <%= Html.GridView(ViewData.Model, null, new GridViewOptions { ViewButtonText = "Look", ShowEditButton=false, ShowDeleteButton=false } )%>
  21:  
  22:  
  23:    
  24: </asp:Content>

The view in Listing 2 generates the HTML page displayed in Figure 1. The page contains four separate grids of data (the figure only shows the first three).

Figure 1 – The Index View

image

Notice that the ViewData.Model is passed to the GridView() helper method. The ViewData.Model represents a LINQ to SQL Table. The code-behind file for the Index view strongly types the model as a System.Data.Linq.ITable class. The model is passed to the view by the controller code in Listing 3.

Listing 3 – HomeController.vb (VB)

   1: Public Class HomeController
   2:     Inherits System.Web.Mvc.Controller
   3:  
   4:     Private _db As New MovieDataContext()
   5:  
   6:     Function Index()
   7:         Return View(_db.Movies)
   8:     End Function
   9: End Class

Listing 3 – HomeController.cs (C#)

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Linq;
   4: using System.Web;
   5: using System.Web.Mvc;
   6: using Tip8.Models;
   7:  
   8: namespace Tip8.Controllers
   9: {
  10:     public class HomeController : Controller
  11:     {
  12:         private MovieDataContext _db = new MovieDataContext();
  13:  
  14:         public ActionResult Index()
  15:         {
  16:            return View(_db.Movies);
  17:         }
  18:     }
  19: }

I’m not completely happy with the GridView() helper method discussed in this tip. The problem with using an extension method is that it makes it difficult to customize the appearance of the columns in the GridView. For example, I would like to be able to format currency and date columns. Better yet, it would be nice if there was a way to have the equivalent of a template column. In tomorrow’s tip, I will explore an entirely different method of encapsulating a GridView when working with the ASP.NET MVC framework.

You can download the code for the GridView() helper method by clicking the following link. The download includes both Visual Basic .NET and C# versions of the code.

Download the Code

Discussion

  1. Hi Stephen,

    Very nice tips. Please keep to post MVC tips.

  2. http:// says:

    We find your tips very useful in our work. Thanks.

  3. This could be complementary to my gridview extension method (blog.maartenballiauw.be/…/…T-MVC-GridView.aspx). It allows customizing the HTML output but does not auto-generate columns.

  4. http:// says:

    Good work. There is also a grid control available in MVCContrib project that has some nice additional features such as Paging, column formatting ,etc.

  5. http:// says:

    @Maarten – Just read your article at blog.maartenballiauw.be/…/…T-MVC-GridView.aspx — great article! I thought it was interesting how you handled passing templates — very cool.

    @Ajin – Thanks for pointing out the MvcContrib Grid at http://www.codeplex.com/MVCContrib. One problem with this Grid is that it does not support Visual Basic. But, it is definitely worth taking a look at if you are doing C#.

  6. http:// says:

    Why are people so opposed to creating and using controls? The definition of controls was one of the best things to come out of ASP.NET Web Forms. Sure I don’t agree with how some of the controls were implemented or the HTML they generated, but you could always create your own or derive and override. A control is going to be 100x more configurable than trying to cram everything into a method signature and it’s 5,000 overloads.

  7. http:// says:

    Eric,

    Did you read the first part of the article? He mentions that there is no “out of the box” control in the MVC framework, which is the whole reason he did this article.

    Nice article Stephen 🙂

  8. http:// says:

    Have you considered something similiar to passing parameters how jQuery does?

    ie. “[{name:”Column1″, sortable:false, width:100px}]”

    Eric, do you realize how a ‘control’ is made? It uses ‘Render()’ 🙂 So, a bunch of 100x properties are setup and someone writes up all this for you on your behalf in webforms.

    He is just showing here how you can create your own controls.

  9. Gill Bates says:

    Oh Stephen! You totally turn me on with your articles!

  10. Pedro Motuinho says:

    I’m using “ASP.NET MVC 1.0 provides a new Model-View-Controller (MVC) framework on top of the existing ASP.NET 3.5 runtime.” and got the error “Error 1 ‘ActionLink’ is not a member of ‘System.Web.Mvc.HtmlHelper’. …VBTip8Tip8HelpersGridExtensions.vb 82 35 Tip8

  11. Mario says:

    Nice control, makes life easier, in many situations.
    I added a class, for table, td, th,.., and also check what data type for fomating, to suit all my needs.

  12. MOV to DVD says:

    If you plan to run the application in production environment (e.g. IIS), it is better to avoid Development Server and use

  13. Great Post! Really this code will help me a lot. Thanks.

  14. Testy says:

    would be wonderful it the downloadable code actually worked.

  15. Ashraf says:

    Greate!

    Can we add search and sorting option in this grid?

  16. 35344 It looks like DataContextExtensions.cs line 45 of the Save method should pass the primaryKeyName through to Update.

  17. positivealex says:

    hi stephen!

    I dont onderstand where you get the class GridViewOptions…

    I haven’t it in my mvc 1.0 framework unfortunately.

    how can i add this in my project environment ?

  18. positivealex says:

    Sorry. I downloaded the code. It’s right there. Thank you.

  19. HD Video Converter says:

    As the users of HD Camcorders like Sony, Canon, Panasonic, this HD Converter is necessary to help us convert hd Video easily and quickly. The Converter for HD provides several practical editing functions to help you achieve ideal output effect. Trim function is to cut videos into clips which you can just convert and transfer to your player. Crop function helps you remove black bars around the movie. You could use Effect function to adjust video brightness, contrast, saturation and more parameters. More powerful and considerate functions are waiting for you to explore.
    M2TS Converter and MTS Converter l MOD Converter