Creating T4 templates at runtime (build-time)?
Asked Answered
V

7

23

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?

Voorhees answered 21/2, 2010 at 21:40 Comment(0)
T
16

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.

Terryl answered 21/2, 2010 at 22:26 Comment(11)
Wooow O.o I am downloading this right now and I will let you know how it goes. If it does everything you say it does, you'll be an absolute life saver!Voorhees
Edited the answer now, as you said in the email, the "Data" variable doesn't exist, because it is named "data". It's a method parameter, hence the lowercase "d".Terryl
"The way the template engine works is that it produces a .cs file and feeds it to the C# compiler" : Wouldn't that also mean that the machine running this needs VisStudio or at least something else installed? Or is there a way that your programatically calling the C# compiler? I didn't think the compiler was shipped with the .net runtime... Although since IIS can compile ASP.NET on the fly, I guess it must be out there somewhere...Valdemar
@Lasse Wow, I guess I missed it when they added that feature. Thanks!Valdemar
To get access to the command line external compilers, you need to install the SDK, but the compiler itself is part of the .NET runtime, unless you install the "Client profile runtime", which I think is missing the compiler. Also, to be precise, I don't actually produce a file per se, I produce the source code in memory and feed it to the C# compiler class, adding references to assemblies and setting options before I ask it to compile the code. There's no file on disk in any of this, even the assembly is generated in memory.Terryl
Great code, works flawlessly. Plus excellent support from Lasse. Kudos!Voorhees
@Lasse: Links do not seem to work any more. How can I get my hand on this code?Riser
Updated links have been posted. Unfortunately there is no binary repository at the moment, I'll look at posting new ones for that but since the binary was just a build of the trunk, I decided that if I want to bring back binary support, it has to be in a more controlled manner.Terryl
Let me know if you have any questions, either by comments here or email to [email protected]Terryl
Posted a link to a template I use in a project, just below the repository links, hopefully it will give you some examples of how to do things with this code.Terryl
Looks like all links are down again? Do you still have these samples available somewhere?Coir
H
14

If you can use Visual Studio 2010 for template creation and editing, then you can use precompiled templates, which were designed for exactly this scenario and are the supported option from Microsoft.

You design the template in Visual Studio, precompile it and deploy an assembly that has no dependencies on Visual Studio along with your application.

http://www.olegsych.com/2009/09/t4-preprocessed-text-templates/

Heaves answered 18/5, 2010 at 2:33 Comment(1)
Link is broken.Guide
O
6

The assembly that implements T4 text transformation is Microsoft.VisualStudio.TextTemplating.dll, which ships with Visual Studio.

If you want to start from first principles, you need to implement Microsoft.VisualStudio.TextTemplating.ITextTemplatingEngineHost, and pass your implementation of it as an argument to Microsoft.VisualStudio.TextTemplating.Engine.ProcessTemplate(), which will perform the transformation.

This gives you more flexibility than calling out to TextTransform.exe.

However, if your code is a shipping product, it is unclear what the licensing of this assembly is, and whether you have the rights to redistribute it with your application.

Redistributing this assembly would avoid the need for Visual Studio to be installed.

Olimpia answered 22/2, 2010 at 3:24 Comment(2)
Unfortunately this doesn't meet our needs because of the licensing issue, as this product WILL be shipped to end users. Thanks anyways :)Voorhees
There is also an open-source implementation by the Mono project. anonsvn.mono-project.com/viewvc/trunk/monodevelop/main/src/…Prophet
V
3

The T4 templates can be compiled with the TextTransform.exe command line tool. You could have your application create a .tt file, then call TextTransform.exe to generate the output.

Valdemar answered 21/2, 2010 at 21:48 Comment(3)
Doesn't Visual Studio need to be installed on the user's machine with this solution? This is a limitation for us.Voorhees
Mono has a fairly capable text transformation implementation. I would take a look at that if dependencies are an issue.Gradely
Yes, for now, it's a depedency on Visual Studio. This will change with VS2010 / .NET 4 and the so-called precompiled T4 templates. See here for info: olegsych.com/2009/09/t4-preprocessed-text-templatesGrandiloquent
H
3

It is completely possible to use T4 at runtime.

Microsoft doesn't really support this scenario in any reasonable way in .NET 3.5. It sounds like .NET 4.0 will have better support from Microsoft.

Mono does provide some support for this scenario in .NET 3.5.

I have proven out this concept successfully with .NET 3.5 with help from the Mono T4 implementation, but an off the shelf solution for this problem for .NET 3.5 would require a ton more effort than I have invested so far.

You can find the Mono T4 implementation here:

https://github.com/mono/monodevelop/tree/master/main/src/addins/TextTemplating

I have documented some of the issues I encountered trying to run T4 templates from .NET code here:

Options for running T4 templates from .NET code

Hengel answered 2/3, 2010 at 14:35 Comment(0)
S
0

One mistake I made is I added a "Text Template" file. To generate text at runtime, pick the "Preprocessed Text Template" instead. If you originally picked "Text Template", it's an easy change to set the Custom Tool to "TextTemplatingFilePreprocessor" in the file properties in VS.

Samovar answered 27/11, 2012 at 14:56 Comment(0)
M
0

Liquid might be a good choice for this. This is an open-source template language, read more about the language here: https://shopify.github.io/liquid/

Here is an implementation for .NET: https://github.com/dotliquid/dotliquid

The syntax is quite nice. Here is some sample code for C#:

    class Person
    {
        public string Name { get; set; }
        public int Age { get; set; }

        public List<string> Friends { get; set; }
    }

    static void Main(string[] args)
    {
        Template.RegisterSafeType(typeof(Person), new string[]
            {
                nameof(Person.Name),
                nameof(Person.Age),
                nameof(Person.Friends),
            });

        Template template = Template.Parse(
@"<h1>hi {{name}}</h1> 
<p>You are{% if age > 42' %} old {% else %} young{% endif %}.</p>
<p>You have {{ friends.size }} friends:</p>
{% assign sortedfriends = friends | sort %}
{% for item in sortedfriends -%}
  {{ item | escape }} <br />
{% endfor %}

");
        string output = template.Render(
            Hash.FromAnonymousObject(
                new Person()
                {
                    Name = "James Bond",
                    Age = 42,
                    Friends = new List<string>()
                    {
                        "Charlie",
                        "<TagMan>",
                        "Bill"
                    }
                } ));

        Console.WriteLine(output);

/* The output will be: 

<h1>hi James Bond</h1>
<p>You are young.</p>
<p>You have 3 friends:</p>

  &lt;TagMan&gt; <br />
  Bill <br />
  Charlie <br />             

*/

    }
Marymarya answered 13/9, 2018 at 13:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.