ASP.NET MVC Tip #14 – Create a Template Helper Method

In this tip, you learn how to create and use templates in the MVC framework that you can use to display database data. I show you how to create a new MVC Helper method named the RenderTemplate() method.

While I was back home in California during the 4th of July weekend, I was talking to my smarter, older brother about the differences among building web applications with ASP.NET Web Forms, ASP.NET MVC, and Ruby on Rails. I was bemoaning the fact that I really missed using controls when building an ASP.NET MVC application. In particular, I was complaining that I missed the clean separation between HTML and UI logic provided by the templates that you get with an ASP.NET Web Forms control. A Repeater control is really not the same as a for…next loop.

My brother told me something surprising. He said “Templates, Ruby on Rails has templates, they are called partials.” At first, I didn’t understand. I thought partials in the Ruby on Rails world were more or less the same as user controls in the ASP.NET MVC world. However, my brother explained that when you render a partial in a Ruby on Rails application, you can pass a collection of items. Each item in the collection is rendered with the partial.

Cool. You can use the same approach to create templates in an ASP.NET MVC application. Create a new helper method that accepts an IEnumerable and a path to a user control. The helper method can use the user control as a template for each item from the IEnumerable. Listing 1 contains the code for a new helper method named the RenderTemplate() method.

Listing 1 – TemplateExtensions.vb (VB.NET)

Imports System
Imports System.Text
Imports System.Collections
Imports System.Web.Mvc
 
Public Module TemplateExtensions
 
    <System.Runtime.CompilerServices.Extension()> _
    Public Function RenderTemplate(ByVal helper As HtmlHelper, ByVal items As IEnumerable, ByVal virtualPath As String) As String
        Dim sb = New StringBuilder()
        For Each item As Object In items
            sb.Append(helper.RenderUserControl(virtualPath, item))
        Next item
        Return sb.ToString()
    End Function
 
 
End Module

Listing 1 – TemplateExtensions.cs (C#)

using System;
using System.Text;
using System.Collections;
using System.Web.Mvc;
 
namespace Helpers
{
    public static class TemplateExtensions
    {
 
        public static string RenderTemplate(this HtmlHelper helper, IEnumerable items, string virtualPath)
        {
            var sb = new StringBuilder();
            foreach (object item in items)
            {
                sb.Append( helper.RenderUserControl(virtualPath, item));
                
            }
            return sb.ToString();
        }
 
 
    }
}

 

Imagine, for example, that you want to display a list of movies. You can use the HomeController in Listing 2 to return a collection of Movie entities. The Index() action executes a LINQ to SQL query and passes the query results to the Index view.

Listing 2 – HomeController.vb (VB.NET)

Public Class HomeController
    Inherits System.Web.Mvc.Controller
 
 
    Private _dataContext As New MovieDataContext()
 
 
    Public Function Index() As ActionResult
        Dim movies = _dataContext.Movies
        Return View(movies)
    End Function
 
 
End Class

Listing 2 – HomeController.cs (C#)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Tip14.Models;
 
namespace Tip14.Controllers
{
    public class HomeController : Controller
    {
        private MovieDataContext _dataContext = new MovieDataContext();
 
 
        public ActionResult Index()
        {
            var movies = _dataContext.Movies;
            return View(movies);
        }
 
    
    }
}

The view in Listing 3 simply calls the RenderTemplate() method passing the method the ViewData.Model and the path to an MVC user control that contains the template for each movie.

Listing 3 -- Index.aspx (VB.NET)

<%@ Page Language="VB" AutoEventWireup="false" CodeBehind="Index.aspx.vb" Inherits="Tip14.Index" %>
<%@ Import Namespace="Tip14" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title></title>
</head>
<body>
    <div>
    
    <%= Html.RenderTemplate(ViewData.Model, "~/Views/Home/MovieTemplate.ascx") %>
    
    </div>
</body>
</html>

Listing 3 -- Index.aspx (C#)

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Index.aspx.cs" Inherits="Tip14.Views.Home.Index" %>
<%@ Import Namespace="Helpers" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title></title>
</head>
<body>
    <div>
    
    <%= Html.RenderTemplate(ViewData.Model, "~/Views/Home/MovieTemplate.ascx") %>
    
    </div>
</body>
</html>

The MovieTemplate.ascx MVC User Control is strongly typed. The code-behind file for this user control is contained in Listing 4. Notice that the User Control is strongly typed to represent a Movie entity.

Listing 4 – MovieTemplate.ascx.vb (VB.NET)

Public Partial Class MovieTemplate
    Inherits System.Web.Mvc.ViewUserControl(Of Movie)
 
End Class

Listing 4 – MovieTemplate.ascx.cs (C#)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Tip14.Models;
 
namespace Tip14.Views.Home
{
    public partial class MovieTemplate : System.Web.Mvc.ViewUserControl<Movie>
    {
    }
}

Finally, the view part of the MVC user control is contained in Listing 5. Notice that you can use expressions like ViewData.Model.Title and ViewData.Model.Director to display the movie title and movie director. These expressions work because you are using a strongly typed MVC User Control that represents a movie entity.

Listing 5 – MovieTemplate.ascx (VB.NET)

<%@ Control Language="VB" AutoEventWireup="false" CodeBehind="MovieTemplate.ascx.vb" Inherits="Tip14.MovieTemplate" %>
 
<b><%= ViewData.Model.Title %></b>
<br />
Director: <%= ViewData.Model.Director %>
 
<hr />

Listing 5 – MovieTemplate.ascx (C#)

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="MovieTemplate.ascx.cs" Inherits="Tip14.Views.Home.MovieTemplate" %>
 
<b><%= ViewData.Model.Title %></b>
<br />
Director: <%= ViewData.Model.Director %>
 
<hr />
 

When you request the Index view, you get the page shown in Figure 1. Notice that the MVC User Control has been rendered for each movie.

Figure 1 – The Movies records rendered with a template

clip_image002

Summary

In this tip, I explained how you can create and use templates in an ASP.NET MVC application. I’ve demonstrated how you can create a template by creating an MVC User Control and use the template to control how a set of database records is rendered. Now, there is no reason to ever use a Repeater control in an ASP.NET MVC application.

You can download the TemplateExtensions (which includes the RenderTemplate() method) by clicking the following link:

Download the Code

12 Comments

  • This already exists as Html.RenderComponent and Html.RenderUserControl I believe.

  • Ah, nevermind, you were just wanting to get rid of the lines for a foreach loop.

  • @Malkir -- The RenderComponent and RenderUserControl methods don't take a collection of items. The RenderTemplate() method described in this tip accepts a collection of items (such as database records) and renders each item using the template.

  • @Stephen, I think malkir was referring to the code that already exists in the "RenderUserControl" and accompanying methods (extensions on HtmlHelper). It means you're repeating a lot of stuff that's already there (the methods are even named the same "InstantiateControl", "DoRendering", "RenderPage" etc.). Why not just make a single extension method:

    public static string RenderTemplate(this HtmlHelper helper, IEnumerable items, string virtualPath)
    { var sb = new StringBuilder();
    foreach (object item in items)
    { sb.Append( helper.RenderUserControl(virtualPath, item);
    }
    return sb.ToString();
    }

    and forget about the rest (no need for that).

    Cheers,
    Mio

  • This is a nice one, althou I had a deja vu feeling.

    I digged up Dynamic Data sources to see how they are instantiating Templates since DD is based on templates.

    The instantiation is similar to yours, but maybe more ASP.Net "friendly":

    (IFieldTemplate) BuildManager.CreateInstanceFromVirtualPath(virtualPath, typeof(IFieldTemplate));

    They use BuildManager straight instead of Activator.CreateInstance.

    Upon digging the sources I thought it further (beware I'm new to MVC bits, but not the concept :-)). Why can't we have standard "field" templates in a designated directory within an MVC app, which ones can be used by various views as needed and can be created on demand by a ITemplateFactory class.

    Thanks,
    Attila

  • @Mio - Really good point. I removed everything but the loop and a call to RenderUserControl(). Thanks!

  • Stephen, it would be nice if you could add the call that needs to be done from the ViewPage...

  • @Simone -- I updated the tip so that it includes the ViewPage - thanks for pointing that out!

  • Hello Stephen,

    I'm using this helper method in my MVC application. However, in preview 5 it doesn't work anymore, because a text writer is used to render partial views aka usercontrols. Any idea how to fix this?

    Thanx,
    Henk

  • i use this:

    public static void RenderTemplate(this HtmlHelper helper, IEnumerable items, string templateName)
    {

    foreach (object item in items)
    {
    helper.RenderPartial(templateName, item);
    }
    }
    Template should be placed in shared directory for use with all controllers.

  • How can we render UserControl using ASP.NET MVC Framework Preview 5?

  • Would you please post the above workaround using MVC Preview 5? We are in trouble while rendering UserControl by Html.RenderPartial().

Comments have been disabled for this content.