Generating strongly typed content links in ASP .Net MVC with T4 templates

Edit: I have made some corrections to this post in a **subsequent post.**

I read an interesting article by David Ebbo on generating strongly typed helpers for ASP .Net MVC using T4 templates.  I have used ASP .Net MVC on a recent project and while I really like the framework (and the testability) the use of string literals made major changes difficult and error-prone.

T4 templates seem ideal for this type of scenario – if we can generate classes for the content that we are referencing via strings, we should be able to use strongly typed links instead of string literals.

In the link I mentioned above David takes a look at generating links using strongly-typed helpers.  I will be taking a look at referencing static content such as images, scripts or stylesheets.

What we are trying to achieve

At the moment, when we wish to reference static content such as a stylesheet, we need to do the following.

<head runat="server">
  <link href="<%= Url.content("~/Content/Site.css") %>" rel="stylesheet" type="text/css" />
</head>

The obvious problem comes in when we move this content – because our links are not strongly typed, we can easily introduce errors.  This is an even bigger problem with changing controller names and actions.

What I would like to do is the following.

<head runat="server">
  <link href="<%= Url.content(Content.Site) %>" rel="stylesheet" type="text/css" />
</head>

Note that I’m not replacing the Url.Content call – this is actually an instance of the UrlHelper class which is instantiated with the correct RequestContext – we can’t replace it.  We could use extension methods, but the syntax is much neater using properties and static classes.

Using T4 templates

T4 templates are a built into Visual Studio and is incredibly useful in all kinds of code generation scenarios.  When compared to the CodeDOM model of code generation, I find the concept of T4 templates to be simpler but much harder to implement.

If you’ve never used T4 templates before, keep in mind that Visual Studio treats them as ordinary text files.  This means you have no intellisense or code completion.  However, there are a whole bunch of Visual Studio plug-ins that add this functionality – the one that I used is the T4 Editor from Tangible Engineering.

Let’s write some code

The first thing we need to do is to find the Visual Studio project that our T4 template file resides in.  This may seem a little confusing at first, but it makes sense – we could be using this template to generate anything – in this case I know the template file will be in the root folder of the web project.  The code here may seem rather tricky, but if you’ve created a Visual Studio plug-in before it should be pretty easy.  Most of the credit for this section goes to David Ebbo.

<#
    // First just get the visual studio project this file belongs to
    var dte = (DTE)((IServiceProvider)Host).GetService(typeof(SDTE));
    Project project = GetProjectContainingT4File(dte, Path.GetFileName(Host.TemplateFile));

    GenerateLinksForFolder(project, "Scripts");
    GenerateLinksForFolder(project, "Content");
#>

Once you have the project object, simply select the folder that you want to generate static classes for.

void RecursivelyIterateThroughFolder(ProjectItem item, string currentPath)
    {
        var fileNameWithoutExtension = Sanitize(Path.GetFileNameWithoutExtension(item.Name));
        var fileName = Path.GetFileName(item.Name);
        if (item.ProjectItems.Count == 0)
        {
#>
            public const string <#=fileNameWithoutExtension#> = "<#=currentPath + fileName#>";
<#+
        }
        else
        {
#>
            public static class <#=fileNameWithoutExtension#>
            {
<#+
            foreach(ProjectItem childItem in item.ProjectItems)
            {
                RecursivelyIterateThroughFolder(childItem, currentPath + fileName + "/");
            }
#>
            }
<#+
        }
    }

The generated code looks like this (I fixed the indentation, which is difficult to get right with the code generation).

public static class Content
{
    public static class Images
    {
        public const string Add = "~/Content/Content/Images/Add.png";
        public const string Check = "~/Content/Content/Images/Check.png";
        public const string Delete = "~/Content/Content/Images/Delete.png";
        public const string Edit = "~/Content/Content/Images/Edit.png";
        public const string FolderBackground = "~/Content/Content/Images/FolderBackground.gif";
        public const string FolderBottom = "~/Content/Content/Images/FolderBottom.gif";
        public const string FolderTop2 = "~/Content/Content/Images/FolderTop2.gif";
        public const string Guy = "~/Content/Content/Images/Guy.png";
        public const string Mail = "~/Content/Content/Images/Mail.png";
        public const string Papers = "~/Content/Content/Images/Papers.jpg";
    }

    public const string Site = "~/Content/Content/Site.css";
}

And now we can generate images using strongly typed links.

<img src="<%= Url.Content(Content.Images.Edit) %>" alt="Edit" />

Pretty neat.  The obvious next step would be to create a generic template that you can include in any MVC project (for content, controllers, views, etc).

There are 2 issues with this approach:

  1. The generated content is only created when the T4 template is saved.  I tried to add a post-build event to generate the content when compiling, but my use of the host object caused issues.  I need to try and find a neat way of doing this.
  2. When publishing the website, we obviously don’t want to include the template file – I want to try and see what options I have here.  It might be as simple as excluding the template file when doing a deployment.