Category Archives: T4

Creating a text templating engine Host

I’m in the process of creating a little application to codegen some source code for me from an XML schema (yes I can do this with xsd but I wanted the code to be more configurable). Instead of writing my own template language etc. I decided to try and leverage the T4 templating language.

I could simply write some code that can be called from a T4 template, but I decided it would be nicer if the codegen application simply acted as a host to the T4 template and allowed the template to call code on the host, so here’s what I did…

Running a T4 template from your application

The first thing I needed was to be able to actually run a T4 template. To do this you’ll need to add the following references

  • Microsoft.VisualStudio.TextTemplating.11.0
  • Microsoft.VisualStudio.TextTemplating.Interfaces10.0
  • Microsoft.VisualStudio.TextTemplating.Interfaces.11.0

Obviously these are the versions at the time of writing, things may differ in the future.

Next we need to instantiate the T4 engine, this is achieved by using the Microsoft.VisualStudio.TextTemplating namespace and with the following code

Engine engine = new Engine();
string result = engine.ProcessTemplate(File.ReadAllText("sample.tt"), host);

Note: The host will be supplied by us in the next section and obviously “sample.tt” would be supplied at runtime in the completed version of the code.

So, here we create a Engine and supply the template string and host to the ProcessTemplate method. The result of this call is the processed template.

Creating the host

Our host implementation will need to derive from MarshalByRefObject and implement the ITextTemplatingEngineHost interface.

Note: See Walkthrough: Creating a Custom Text Template Host for more information of creating a custom text template.

What follows is a basic implementation of the ITextTemplatingEngineHost based upon the Microsoft article noted above.

public class TextTemplatingEngineHost : MarshalByRefObject, ITextTemplatingEngineHost
{
   public virtual object GetHostOption(string optionName)
   {
      return (optionName == "CacheAssemblies") ? (object)true : null;
   }

   public virtual bool LoadIncludeText(string requestFileName, 
            out string content, out string location)
   {
      content = location = String.Empty;

      if (File.Exists(requestFileName))
      {
         content = File.ReadAllText(requestFileName);
         return true;
      }
      return false;
   }

   public virtual void LogErrors(CompilerErrorCollection errors)
   {
   }

   public virtual AppDomain ProvideTemplatingAppDomain(string content)
   {
      return AppDomain.CreateDomain("TemplatingHost AppDomain");
   }

   public virtual string ResolveAssemblyReference(string assemblyReference)
   {
      if (File.Exists(assemblyReference))
      {
         return assemblyReference;
      }

      string candidate = Path.Combine(Path.GetDirectoryName(TemplateFile), 
            assemblyReference);
      return File.Exists(candidate) ? candidate : String.Empty;
   }

   public virtual Type ResolveDirectiveProcessor(string processorName)
   {
      throw new Exception("Directive Processor not found");
   }

   public virtual string ResolveParameterValue(string directiveId, 
            string processorName, string parameterName)
   {
      if (directiveId == null)
      {
         throw new ArgumentNullException("directiveId");
      }
      if (processorName == null)
      {
         throw new ArgumentNullException("processorName");
      }
      if (parameterName == null)
      {
         throw new ArgumentNullException("parameterName");
      }

      return String.Empty;
   }

   public virtual string ResolvePath(string path)
   {
      if (path == null)
      {
         throw new ArgumentNullException("path");
      }

      if (File.Exists(path))
      {
         return path;
      }
      string candidate = Path.Combine(Path.GetDirectoryName(TemplateFile), path);
      if (File.Exists(candidate))
      {
         return candidate;
      }
      return path;
   }

   public virtual void SetFileExtension(string extension)
   {
   }

   public virtual void SetOutputEncoding(Encoding encoding, bool fromOutputDirective)
   {
   }

   public virtual IList<string> StandardAssemblyReferences
   {
      // bare minimum, returns the location of the System assembly
      get { return new[] { typeof (String).Assembly.Location }; }
   }

   public virtual IList<string> StandardImports
   {
      get { return new[] { "System" }; }
   }

   public string TemplateFile { get; set; }
}

Now the idea is that we can subclass the TextTemplatingEngineHost to implement a version specific for our needs.

Before we look at a specialization of this for our purpose, let’s look at a T4 template sample for generating our code

Note: mycodegen is both my assembly name and the namespace for my code generator which itself hosts the T4 engine.

<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ assembly name="mycodegen" #>
<#@ import namespace="mycodegen" #>
<#@ output extension=".cs" #>

<#
   ICodeGenerator cg = ((ICodeGenerator)this.Host);
#>

namespace <#= cg.Namespace #>
{
   <# 
   foreach(var c in cg.Classes) 
   {
   #>
   public partial class <#= c.Name #>
   { 
      <#  
      foreach(Property p in c.Properties)
      {
          if(p.IsArray)
          {
      #>
         public <#= p.Type #>[] <#= p.Name #> { get; set; }
      <#
           }
           else
           {
      #>
         public <#= p.Type #> <#= p.Name #> { get; set; }
      <#
           }
      }
      #>
   }
   <#
   }
   #>
}

So in the above code you can see that our host will support an interface named ICodeGenerator (which is declared in the mycodegen assembly and namespace). ICodeGenerator will simply supply the class names and properties for the classes extracted from the XML schema and we’ll use the T4 template to generate the output. By using this template we can easily change how we output our classes and properties, for example xsd creates fields which are not required if we use auto-implemented property syntax, plus we can change the naming convention, property name case and so on an so forth. Whilst we could add code to the partial classes generated by including other files implementing further partial methods etc. if a type no longer exists in a month or two we need to ensure we deleted the manually added code if we want to keep our code clean. Using the T4 template we can auto generate everything we need.

References

Walkthrough: Creating a Custom Text Template Host
Processing Text Templates by using a Custom Host

Getting started with T4 templates

What are T4 templates

First off, T4 stands for Text Template Transformation Toolkit. It allows us to embed C# (or VB.NET) code into documents and optionally combine the code with plain text, which can then be used to generate new documents – therefore similar to the way ASP or PHP engines work when producing HTML. The output text document might be a C# file, HTML file or pretty much any other file type you want.

A T4 template file ends in a .tt extension.

Within Visual, when a T4 template is executed the resultant file is created as a child node (in the solution explorer) off of the template’s node.

Let’s get started

If you create a solution (or have a solution ready), then on a selected project, select the “Add New Item” option. Then select “Text Template”, give it a name (mine’s named Test.tt) and add to the project.

By default the file will look similar to the following (this is from the Visual Studio 2012/2013 T4 item template)

<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".txt" #>

Note: by default Visual Studio (well 2013 at least) does not offer syntax highlighting. You can use Tools | Extensions and Updates to add T4 Toolbox which is a free syntax highlighter etc. for T4.

So in the template code above we can see that <#@ #> are used to enclose “directives”.

The template adds a reference to the System.Core assembly and imported the namespaces System.Linq, System.Text and System.Collections.Generic. The template sets the output extension (by default) to .txt. Hence the template will generate a file name Test.txt file (in my case). Obviously the name of the file is a copy of the name of your template file.

The template directive is described here.

We can see that the language attribute denotes whether we want to write VB or C# code within our code statement blocks.

The hostspecific attribute denotes that we want the T4 template to allow us access to the this.Host property of type ITextTemplatingEngineHost. From this we can use methods such as the ResolvePath method, which allows us to get a combined path of our supplied filename or relative path, combined with the current path of the solution. See ITextTemplatingEngineHost Interface for more information.

Finally the debug attribute denotes that we want the intermediate code to include code which helps the debugger to identify more accurately where an exception or the likes has occurred.

Include Directive

We can also include T4 code using the <#@ include file=”mycode.tt” #> See T4 Include Directive.

Blocks

So we’ve looked at the <#@ #> directive, but there’s more…

<# Statement Blocks #>

Statement blocks allow you to embed code into your template. This code can simply be blocks of code or can be used to wrap loops or if statements which in turn wrap around output text, as per

<#
for(int i = 0; i < 3; i++)
{
#>
   Hello
<#
}
#>

or, as mentioned, simply embedding code blocks, such as

<#
for(int i = 0; i < 3; i++)
{
   WriteLine("Hello");
}
#>

which results in the same output at the previous sample. See also Statement Syntax.

<#+ Class Feature Blocks #>

Class feature blocks allow you to create code which you can reuse within your template. Statement blocks are like the inner blocks of a method, but ofcourse it would be useful to be able to actually create reusable methods. This is where Class Blocks come in.

For example

<#= WrapInComment("Hello") #>

<#+
private string WrapInComment(string text)
{
   return "<!-- " + text + " -->";
}
#>

It’s important to note that the statement block calling code is actually before the method declaration.

In this example we return a string, which can be used from a <#= Expression Block #> (see below). If the method returned void then it would equally be callable from a <# Statement Blocks #>.

<#= Expression Blocks #>

Expression blocks allow us to write out results from our code to the generated output text, for example

<#
int sum = 10 + 4;
#>

<#= sum #>

So the first block is a statement block where we declare the sum variable and it is output in situ via the <#= #> code. See also Expression Syntax (Domain-Specific Languages).

Great, now let’s do something with all this!

Let’s write a T4 template which will take a C# class, which contains our model (as a partial class) and has properties marked with XmlElementAttribute and then generates another partial class for the type, with some new ShouldSerializeXXX methods – the idea being any string property which is empty or null, will not be serialized.

This example template is not perfect and I’m sure there are better ways to do things, but I really wanted to create a more meaningful example than a “Hello World”, so bare with me.

<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ assembly name="System" #>
<#@ assembly name="System.Xml" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Xml.Serialization" #>
<#@ import namespace="System.Reflection" #>
<#@ import namespace="System.CodeDom.Compiler" #>
<#@ output extension=".cs" #>
<#
string model = File.ReadAllText(this.Host.ResolvePath("MyModel.cs"));

CodeDomProvider provider = CodeDomProvider.CreateProvider("CSharp");
CompilerParameters cp = new CompilerParameters();
cp.GenerateExecutable = false;
cp.GenerateInMemory = true;
cp.ReferencedAssemblies.Add("System.dll");
cp.ReferencedAssemblies.Add("System.Core.dll");
cp.ReferencedAssemblies.Add("System.Xml.dll");

CompilerResults cr = provider.CompileAssemblyFromSource(cp, new string[] { model });
if (cr.Errors.Count > 0)
{
   Error("Errors detected in the compiled model");
}
else
{
#>
using System;

<#
    bool nsAdded = false;

    Type[] types = cr.CompiledAssembly.GetTypes();
	foreach (Type type in types)
	{
        // time to generate code
        if(nsAdded == false)
        {
#>
namespace <#=type.Namespace#>
{
<#
        }
        nsAdded = true;
#>
    public partial class <#=type.Name#>
    {
<#
		List<PropertyInfo> toGenerate = new List<PropertyInfo>();

		foreach (PropertyInfo pi in type.GetProperties())
		{
		    if(pi.PropertyType == typeof(string))
			{
			    XmlElementAttribute[] a = (XmlElementAttribute[])pi.GetCustomAttributes(typeof(XmlElementAttribute), true);
				if (a != null && a.Length > 0)
				{
#>
        public bool ShouldSerialize<#=pi.Name#>()
        {					
            return !String.IsNullOrEmpty(<#=pi.Name#>);
        }
<#
                }
            }
		}
#>
    }

<#
        }
    }
#>
}

Before we look at the output from this T4 template, let’s quickly review what’s in this template. Firstly we reference the two assembles, System and System.Xml then add our “using” clauses via the import directive. The template is marked as hostspecific so we can use the Host property.

We read the MyModel.cs file, in this instance. Although we could have possibly looked to interact with the Visual Studio environment instead to achieve this. Then we create a C# CodeDomProvider, we could use Roslyn instead for this. The purpose of the CodeDomProvider is solely to use it to compile the MyModel.cs and allow us to use reflection to get the properties with the XmlElementAttribute as I don’t really want to parse the source code myself (this is where Roslyn would have come in).

Now you can see interspersed with our T4 blocks of code is the output text which creates the namespace, class and ShouldSerializeXXX methods.

So the code gets the properties on our MyModel object, then finds those with a string type and also with the XmlElementAttribute attribute applied and then creates the namespace, class and methods to match these properties. Writing output which looks something like the following

using System;

namespace DomainObjects
{
    public partial class MyModel
    {
        public bool ShouldSerializeName()
        {					
            return !String.IsNullOrEmpty(Name);
        }
        public bool ShouldSerializeAge()
        {					
            return !String.IsNullOrEmpty(Age);
        }
        public bool ShouldSerializeAddress()
        {					
            return !String.IsNullOrEmpty(Address);
        }
   }
}

And finally (for now)…

Right mouse click on the T4 .tt file and you will see a Debug T4 template. I actually only discovered this after searching for something else related to this post and came across T4 TEMPLATE DEBUGGING which talks about it.

You can now put break points against your T4 code and run the T4 debugger!