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(" ")
85: If options.ShowEditButton Then
86: sb.Append(htmlHelper.ActionLink(options.EditButtonText, options.EditAction, New With {.Id = identityValue}))
87: sb.Append(" ")
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(" ");
83: }
84: if (options.ShowEditButton)
85: {
86: sb.Append(htmlHelper.ActionLink(options.EditButtonText, options.EditAction, new { Id = identityValue }));
87: sb.Append(" ");
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
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.