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

clip_image002

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

5 Comments

  • Hi Stephen,

    Very nice tips. Please keep to post MVC tips.

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

  • 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.

  • 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.

  • That's not just the best ansewr. It's the bestest answer!

Comments have been disabled for this content.