static void

T4 preprocessing

Published Friday 10 June 2011

T4 preprocessed templates are a neat way of generating text at run time, which can be deployed to machines without Visual Studio. Here's MSDN

Here's a simple template, ClassWriter.tt, which must be marked CustomTool = "TextTemplatingFilePreprocessor" in properties (not "TextTemplatingFileGenerator" which is a normal T4).
<#@ template language="C#" #>
<#@ import namespace="System.Linq" #>
<#@ parameter type="Generator.Model.Table" name="table" #>
using System;
 
namespace <#= table.Namespace #>
{
    [Serializable]
    public class <#= table.Name #>
    {
<#  foreach(var column in table.Columns.Where(c=> !c.Hidden)) { #>
        public virtual <#= column.Type #> <#= column.Name #> { get; set; }
<#    } #>
    }
}

At development time, Visual Studio generates the class in the corresponding namespace which you can then call (yeah, it generates code for generating code...).

Notice we're passing a parameter, which has to have the full namespaced name (even "string" has to be "System.String").

The generated class is partial and the parameters are property getters with a backing field. MSDN suggests that to pass in your parameters you should manually code a partial class with conventional properties or a constructor. Actually, there's an easier no-code way. Use the Session property (which is just a Dictionary<string, object>) which you can use with an Initialize() method. Like this:

//create the class generated by TextTemplatingFilePreprocessor 
var generator = new ClassWriter();
//create a session dictionary, fill it and initialize
generator.Session = new Dictionary<stringobject>();
generator.Session.Add("table", table);
generator.Initialize();
//transform!
var text = generator.TransformText();
The key things to watch out for:

You can also use System.Runtime.Remoting.Messaging.CallContext but weirdly you must still initialize the Session dictionary (Initialize checks Session first, then CallContext).
 
You may be tempted to reuse the T4 template class like this.
//don't do this
var generator = new ClassWriter();
foreach (var item in list)
{
    generator.Session = new Dictionary<stringobject>();
    generator.Session.Add("table", item);
    generator.Initialize();
    var txt = generator.TransformText();
    WriteText(item, txt);
}
The template class actually uses an internal StringBuilder called GenerationEnvironment. So each call returns everything you wrote before. You can't actually reset the StringBuilder (although you can append to it with Write overloads). So, always create the template class within the loop.



Previously: ildasm and ilasm to sign an unsigned assembly (02 May 2011)