In this tip, I show you how you can create two new HTML Helpers that you can use within an ASP.NET MVC View. I show you how you can use extension methods to create new HTML Helpers for displaying bulleted and numbered lists.

When building a View for an ASP.NET MVC application, you can take advantage of HTML Helpers to render standard HTML tags. For example, instead of typing this:

<input name="inpSubmit" type="submit" value="Click Here!" />

You can type this:

<%= Html.SubmitButton("inpSubmit", "Click Here!") %>

Over the long run, HTML Helpers can save you a lot time. But what if there isn’t an HTML Helper for a tag that you want to render? For example, imagine that you want to display a bulleted list of database records in a View. The HtmlHelper class doesn’t include a method that lets you render a bulleted list. Don’t give up. If the HTML Helper doesn't include a method that you need, just extend it!

You can add new functionality to the HtmlHelper class by creating new extension methods. An extension method looks just like a normal instance method. However, unlike a normal instance method, you add extension methods to a class by defining the methods in a completely different class.

In Visual Basic .NET, you create extension methods by creating a module and decorating the extension methods with a special attribute. In C#, you define extension methods in a static class and use the keyword this to indicate the class being extended.

Here’s how you can add extension methods to the HtmlHelper class to display both ordered and unordered list of database records:

Listing 1 – ListExtensions.vb (VB.NET)

   1: Imports System
   2: Imports System.Collections
   3: Imports System.Text
   4: Imports System.Web
   5: Imports System.Web.Mvc
   6: Imports System.Runtime.CompilerServices
   7:  
   8:  
   9: Namespace HtmlHelpers
  10:  
  11:     Public Module ListExtensions
  12:  
  13:         <Extension()> _
  14:         Public Function OrderedList(ByVal HtmlHelper As HtmlHelper, ByVal items As Object) As String
  15:             Return "<ol>" + ListExtensions.GetListItems(items) + "</ol>"
  16:         End Function
  17:  
  18:         <Extension()> _
  19:         Public Function UnorderedList(ByVal HtmlHelper As HtmlHelper, ByVal items As Object) As String
  20:             Return "<ul>" + ListExtensions.GetListItems(items) + "</ul>"
  21:         End Function
  22:  
  23:  
  24:         Private Function GetListItems(ByVal items As Object) As String
  25:             If items Is Nothing Then
  26:                 Throw New ArgumentNullException("items")
  27:             End If
  28:             If Not TypeOf items Is IEnumerable Then
  29:                 Throw New InvalidCastException("items must be IEnumerable")
  30:             End If
  31:  
  32:             Dim EnumItems As IEnumerable = CType(items, IEnumerable)
  33:             Dim builder As New StringBuilder()
  34:             For Each item As Object In EnumItems
  35:                 builder.AppendFormat("<li>{0}</li>", HttpUtility.HtmlEncode(item.ToString()))
  36:             Next
  37:             Return builder.ToString()
  38:         End Function
  39:  
  40:     End Module
  41: End Namespace

Listing 1 – ListExtensions.cs (C#)

   1: using System;
   2: using System.Collections;
   3: using System.Text;
   4: using System.Web;
   5: using System.Web.Mvc;
   6:  
   7: namespace BulletedListHelper.HtmlHelpers
   8: {
   9:     public static class ListExtensions
  10:     {
  11:         public static string OrderedList(this HtmlHelper helper, Object items)
  12:         {
  13:             return "<ol>" + ListExtensions.GetListItems(items) + "</ol>";
  14:         }
  15:  
  16:         public static string UnorderedList(this HtmlHelper helper, Object items)
  17:         {
  18:             return "<ul>" + ListExtensions.GetListItems(items) + "</ul>";
  19:         }
  20:  
  21:  
  22:         private static string GetListItems(Object items)
  23:         {
  24:             if (items == null)
  25:                 throw new ArgumentNullException("items");
  26:             if (items is IEnumerable == false)
  27:                 throw new InvalidCastException("items must be IEnumerable");
  28:  
  29:             var enumItems = (IEnumerable)items;
  30:             var builder = new StringBuilder();
  31:             foreach (Object item in enumItems)
  32:                 builder.AppendFormat("<li>{0}</li>", HttpUtility.HtmlEncode(item.ToString()));
  33:             return builder.ToString();
  34:         }
  35:  
  36:     }
  37: }

The ListExtensions class has two public methods: OrderedList() and UnorderedList(). You pass a collection of items to either method to display either a numbered or bulleted list of items. Notice that these methods return strings. Really, an HTML Helper method is nothing more than a method that renders a formatted string to the browser.

After you create the extension methods, you can use the methods in a View like this:

Listing 2 – Index.aspx

   1: <%@ Page Language="VB" MasterPageFile="~/Views/Shared/Site.Master" AutoEventWireup="false" CodeBehind="Index.aspx.vb" Inherits="BulletedListHelper.Index" %>
   2: <%@ Import Namespace="BulletedListHelper.HtmlHelpers" %>
   3:  
   4: <asp:Content ID="indexContent" ContentPlaceHolderID="MainContent" runat="server">
   5:   
   6:  
   7:    <h1>Movies (Ordered)</h1>
   8:  
   9:    <%= Html.OrderedList(ViewData.Model) %>
  10:  
  11:  
  12:    <h1>Movies (Unordered)</h1>
  13:  
  14:  
  15:    <%= Html.UnorderedList(ViewData.Model) %>
  16:  
  17:  
  18: </asp:Content>

Notice that the BulletedList.HtmlHelpers namespace gets imported at the top of the file. The method Html.OrderedList() is used to render a numbered list and the method Html.UnorderedList() is used to render a bulleted list. Notice that these methods are being called on the HtmlHelper exposed by the Html property of the View just like any other extension method. When you open this View in a browser, you get the page in Figure 1:

Figure 1 – Index.aspx Rendered with Custom HTML Helpers

image

Finally, the Index() method exposed by the HomeController in Listing 3 illustrates how you can pass a collection of movie records to the Index.aspx View. The movie records are retrieved by taking advantage of a Linq to SQL query.

Listing 3 – HomeController.vb (VB.NET)

   1: Public Class HomeController
   2:     Inherits System.Web.Mvc.Controller
   3:  
   4:     Private db As New MoviesDataContext()
   5:  
   6:     Function Index()
   7:         Dim movies = From m In db.Movies Select m.Title
   8:         Return View(movies)
   9:     End Function
  10:  
  11:    
  12: 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 BulletedListHelper.Models;
   7:  
   8: namespace BulletedListHelper.Controllers
   9: {
  10:     public class HomeController : Controller
  11:     {
  12:  
  13:         private MoviesDataContext db = new MoviesDataContext();  
  14:  
  15:         public ActionResult Index()
  16:         {
  17:             var movies = from m in db.Movies select m.Title;
  18:             return View(movies);
  19:         }
  20:  
  21:     }
  22: }

You can use this approach to render just about anything within an ASP.NET MVC View. For example, you can use a similar approach to create TreeViews, Menus, tabstrips, whatever.

Click Here to Download the Source Code

If you liked this blog post then please Subscribe to this blog.
posted on Friday, June 13, 2008 8:07 PM | Filed Under [ ASP.NET MVC Tips ]

Comments

Gravatar
# re: ASP.NET MVC Tip #1 - Create New HTML Helpers with Extension Methods
Posted by David Kemp
on 6/26/2008 3:29 PM

Why have you used <br /><br />public static string UnorderedList(this HtmlHelper helper, object items)<br /><br />rather than <br /><br />public static string UnorderedList<ITEMTYPE>(this HtmlHelper helper, IEnumerable<ITEMTYPE> items) ?<br /><br />Surely your method catches the wrong type in the test/at run time, whereas using generics, we can stop anything that doesn't implement IEnumerable<T> being passed into the method.<br /><br /><br />
Gravatar
# re: ASP.NET MVC Tip #1 - Create New HTML Helpers with Extension Methods
Posted by http://
on 6/27/2008 3:22 PM

@David - Good point, I should have used generics here instead of making an assumption about the type and casting.
Gravatar
# re: ASP.NET MVC Tip #1 - Create New HTML Helpers with Extension Methods
Posted by David
on 7/31/2008 9:21 AM

Hi, have you had any luck using ToAttributeList() in your extension methods in vb.net? I always get an error saying ToAttributeList is not a member of VB$AnonymousType_0(Of String) and i have no idea why...<br /><br />Cheers!
Gravatar
# re: ASP.NET MVC Tip #1 - Create New HTML Helpers with Extension Methods
Posted by http://
on 8/24/2008 1:24 PM

Great! Very helpful!
Gravatar
# re: ASP.NET MVC Tip #1 - Create New HTML Helpers with Extension Methods
Posted by http://
on 11/14/2008 1:26 AM

very helpful steve, thanks. I have been watching/reading your tutorials to learn MVC and you do a good job at laying out the basics of the MVC framework.
Gravatar
# re: ASP.NET MVC Tip #1 - Create New HTML Helpers with Extension Methods
Posted by Len Ocin
on 5/26/2009 11:27 PM

Did you realize that the SubmitButton HTMLHelper have been removed?<br />To quote Phil Haack:<br />"Yeah, we are trying to keep the helpers somewhat minimalist. Not only that, we've received feedback from some people that many of our helpers are unnecessary. "I know HTML, why do I need a SubmitButton helper?".<br /><br />We figured that since input buttons generally are not used to render user input, the helper method has marginal value. After all, with the VS HTML editor, you get intellisense when you type <input type=... />. It tells you which attributes are available. Our helper doesn't.<br /><br />On the other hand, I've seen several threads where people were upset that this was gone. Is this a real problem?<br /><br />The thing we want to avoid is having a helper for every html element. That would make no sense: Html.Div.<br /><br />We want to come up with a design rationale for helpers. Maybe we have one for all the common form elements, not just the ones that show user input. That's a possibility in which case we would include SubmitButton."<br /><br />Please keep your post up to date Stephen. Your kind of postings is what newcomers to this awesome way of doing things are looking at and so far you have cause quite a few confusions to me, and don't know how many others.
Comments have been closed on this topic.