Creating T4 templates at runtime (build-time)?

Amberite picture Amberite · Feb 21, 2010 · Viewed 13.9k times · Source

We are building an inhouse application which needs to generate HTML files for upload into eBay listings. We are looking to use a template engine to generate the HTML files based on database and static fields that we have pre-defined. The template also needs to have logic capabilities (if-then, foreach, etc).

We have looked at T4 and it looks perfect, but we don't see anything about whether it has the capabilities to be used at runtime, so that a user can create the T4 template, and then the application can "compile" it and generate the final HTML file. Is this possible, and how?

If not, are there other frameworks we should be looking at that has all these capabilities?

Answer

Lasse V. Karlsen picture Lasse V. Karlsen · Feb 21, 2010

I have a similar set of classes that I use for this, embedding templated text generation into software.

Basically, it works like old-style ASP, you surround C# code in <%...%> blocks, and you can emit results by using <%= expression %>.

You can pass a single object into the template code, which of course can be any object type you like, or simply an array of parameters. You can also reference your own assemblies if you want to execute custom code.

Here's how emitting a class would look:

<%
var parameters = (string[])data;
var namespaceName = parameters[0];
var className = parameters[1];
%>
namespace <%= namespaceName %>
{
    public class <%= className %>
    {
    }
}

You can of course loop through things:

<% foreach (var parameter in parameters) { %>
<%= parameter %>
<% } %>

and put code in if-blocks etc.

The class library is released on CodePlex here:

as well as on NuGet.

The project comes with examples, download the source or browse it online.

To answer questions by email also here, for others to see:

  1. All types of C# code that fit into a method call can be compiled in the template. It runs normal C# 3.5 code, with everything that means, there's no artificial limits. Only things to know is that any if, while, for, foreach, etc. code that contains template code to emit must use braces, you cannot do a single-line if-then type block. See below for the method-call limitation.
  2. The data parameter corresponds to whatever was passed in as the parameter to the .Generate(x) method from your application, and is of the same type. If you pass in an object you have defined in your own class libraries, you need to add a reference to the template code in order to properly access it. (<%@ reference your.class.library.dll %>)
  3. If you reuse the compiled template, it will in essence only be a method call to a class, no additional overhead is done on the actual call to .Generate(). If you don't call .Compile() yourself, the first call to .Generate() will take care of it. Also note that the code runs in a separate appdomain, so there's a slight marshalling overhead related to copying the parameter and result back and forth. The code, however, runs at normal JITted .NET code speed.

Example of if-block:

<% if (a == b) { %>
This will only be output if a==b.
<% } %>

There's no artificial limits on formatting the code either, pick the style that suits you best:

<%
    if (a == b)
    {
%>
This will only be output if a==b.
<%
    }
%>

Only note that all non-code parts of the template will pretty much be output as-is, which means tabs and such following %> blocks will be output as well.

There is one limit, all the code you write must fit inside a single method call.

Let me explain.

The way the template engine works is that it produces a .cs file and feeds it to the C# compiler, this .cs file roughyly looks like this:

using directives

namespace SomeNamespace
{
    public class SomeClass
    {
        public string Render(object data)
        {
            ... all your code goes here
        }
    }
}

This means that you cannot define new classes, new methods, class-level fields, etc.

You can, however, use anonymous delegates to create functions internally. For instance, if you want a uniform way of formatting dates:

Func<DateTime, string> date2str = delegate(DateTime dt)
{
    return dt.ToString("G");
};

then you can simply use that in the rest of the template code:

<%= date2str(DateTime.Now) %>

Only requirement I have is that you don't upload the files onto the web and claim you wrote the code, other than that you're free to do what you want with it.

Edit 23.04.2011: Fixed links to CodePlex project.