May 2012

Volume 27 Number 05

T4 Templates - Managing Complexity in T4 Code-Generation Solutions

By Peter Vogel | May 2012

In my article, “Lowering the Barriers to Code Generation with T4” in the April issue of MSDN Magazine (msdn.microsoft.com/magazine/hh882448), I described how the Microsoft Text Template Transformation Toolkit (T4) makes it much easier for developers to create code-generation solutions (and how they can start recognizing code-generation opportunities). However, as with any programming environment, T4 solutions can grow into complex, monolithic solutions that can’t be maintained or extended. Avoiding that fate requires recognizing the various ways that code in T4 solutions can be refactored and integrated into code-generation solutions. And that requires some understanding of the T4 code-generation process.

T4 Code-Generation Process

The heart of the T4 code-generation process is the T4 engine, which accepts a T4 template consisting of boilerplate code, control blocks, class features and directives. From these inputs, the engine creates a temporary class (the “generated transformation class”) that inherits from the Microsoft TextTransformation class. An application domain is then created and, in that domain, the generated transformation class is compiled and executed to produce the output, which might be anything from an HTML page to a C# program.

The boilerplate text and the control blocks in the template are incorporated into a single method (called TransformText) in the generated transformation class. However, the code in class features (enclosed in <#+ … #> delimiters) are not placed in that method—class feature code is added to the generated transformation class outside of any methods the T4 process creates. In my previous article, for instance, I used a class feature block to add an ArrayList—declared outside of any method—to the generated transformation class. I then accessed that ArrayList in my code generation’s control blocks as part of the process of generating the code. In addition to adding fields, like the ArrayList, class features can also be used to add private methods that can be called from your code blocks.

The engine is the heart of the process, but two other components also participate in the T4 code-generation process: the directive processor and the host. Directives provide a parameter-based way to add code or otherwise control the process. For example, the T4 Import directive has a Namespace parameter for creating a using or Imports statement in the generated transformation class; the Include directive has a file parameter for retrieving text from another file and adding it to the generated transformation class. The default directive processor handles the directives that come with T4.

The T4 code-generation process also needs a host to integrate the process with the environment the engine is executing in. The host, for instance, provides a standard set of assemblies and namespaces to the engine so that not all the assemblies the generated transformation class code needs have to be specified in the template. When requested by the engine or the directive processor, the host adds references to assemblies; retrieves (and sometimes reads) files for the engine; and can even retrieve custom directive processors or provide default values for directives whose parameters have been omitted. The host also provides the AppDomain the generated transformation class executes in and displays any error and warning messages generated by the engine. Visual Studio can host the T4 engine (through the custom tool TextTemplatingFileGenerator), as can the TextTransform command-line utility that processes T4 templates outside of Visual Studio.

As an example of this process, take look at the T4 template with a combination of static code, control blocks and a class feature shown in Figure 1.

Figure 1 A Sample T4 Template

<#@ template language="VB" #>
public partial class ConnectionManager
{
<#
  For Each conName As String in Connections
#>
  private void <#= conName #>(){}
<#
  Next
#>
<#+
  Private Function GetFormattedDate() As String
    Return DateTime.Now.ToShortDateString()
  End Function
#>

The generated transformation class would look something like what’s shown in Figure 2.

Figure 2 The Generated Transformation Class

Public Class GeneratedTextTransformation
  Inherits Microsoft.VisualStudio.TextTemplating.TextTransformation
  Public Overrides Function TransformText() As String
    Me.Write("public partial class ConnectionManager{")
    For Each conName As String in Connections
      Me.Write("private void ")
      Me.Write(Me.ToStringHelper.ToStringWithCulture(conName))
      Me.Write("(){}")    
    Next
  End Function
  Private Function GetFormattedDate() As String
    Return DateTime.Now.ToShortDateString()
  End Fuction
End Class

Given this description of the T4 code-generation process, you can refactor potentially monolithic solutions into more maintainable components using any of these three options:

  1. Class features
  2. Extending the base TextTransformation class
  3. Custom directive processors

These mechanisms also allow you to reuse code across multiple code-generation solutions. To demonstrate these options I’ll use a trivially simple case study: adding a copyright notice to the generated code. The “meta-ness” of writing code to generate code creates enough complexity without picking a complicated case study—you can do that on your own.

There is, at least, one other option I’m not going to discuss: developing your own host so you can invoke host-specific features from your T4 template. Creating a new host is really necessary only if you intend to invoke T4 processing from outside Visual Studio and don’t want to use the TextTransform command-line tool.

Class Features

Class features provide the easiest way of reducing complexity in your T4 process and reusing code. Class features allow you to encapsulate parts of your code-generation process into methods that you can call from the TransformText method that forms the “mainline” of your code. You can also leverage the Include directive to reuse class features across multiple solutions.

The first step in using a class feature is to add a T4 file to your project containing the class features you want to reuse in multiple code-generation solutions, enclosed in the T4 <#+ … #> delimiters. This can include methods, properties and fields. Your template files can also contain T4-specific features such as directives. Because your file is still a T4 file, it will generate code that will be compiled into your application, so you should suppress code generation for the file. The easiest way to do this is to clear the Custom Tool property of your class feature files. This example defines a method called ReturnCopyright as a class feature, written in Visual Basic:

<#+
 Public Function ReturnCopyright() As String
   Return "Copyright by PH&V Information Services, 2012"
 End Function
#>

Once you’ve defined a class feature, you can add it to a template using the Include directive and use the feature from a control block in a T4 template. The next example, which assumes that the previous class feature was defined in a file called CopyrightFeature.tt, uses the ReturnCopyright method as an expression:

<#@ Template language="VB"   #>
<#@ Output extension=".generated.cs" #>
<#= ReturnCopyright() #>
<#@ Include file="CopyrightFeature.tt" #>

Alternatively, you can simply use the T4 normal boilerplate syntax to generate similar code:

<#+
  Public Function ReturnCopyright() As String
#>
  Copyright by PH&V Information Services, 2012
<#+
  End Function
#>

You can also use the Write and WriteLine methods within your class feature to generate code, as this class feature does:

<#+
  Public Sub WriteCopyright()
    Write("Copyright by PH&V Information Services, 2012")
  End Function
#>

The latter two approaches would use the following code to call the method once an Include directive added it to the template:

<# WriteCopyright() #>

You can parameterize class features using normal function arguments and daisy-chain features together by using the Include directive in T4 files that are themselves included in other T4 files to build up a well-structured, reusable library.

Compared to the other solutions, using class features has at least one benefit and one drawback. You experience the benefit as you develop your code-generation solution: class features (and Includes in general) don’t involve compiled assemblies. For performance reasons, the T4 engine may lock the assemblies it uses when it loads the generated transformation class into its application domain. This means that as you test and modify compiled code, you might find that you can’t replace the relevant assemblies without shutting down and restarting Visual Studio. This won’t happen with class features. Note that this has been addressed in Visual Studio 2010 SP1, which no longer locks assemblies.

Developers might encounter the drawback, however, when they use your code-generation solution: They must add not only your T4 template to their project, but also all your supporting T4 Include files. This is a scenario where you might consider creating a Visual Studio template containing all of the T4 files a developer needs for your code-generation solution so they can be added as a group. It would also make sense to segregate your Include files into a folder within the solution. The following example uses the Include directive to add a file containing class features from a folder called Templates:

<#@ Include file="Templates\classfeatures.tt" #>

You are not, of course, limited to just using class features in Include files—you can include any arbitrary set of control blocks and text that you want to be incorporated into the generated transformation class TransformText method. However, like avoiding GoTos, using well-defined members in Include files helps to manage the conceptual complexity of your solution.

Extending the TextTransformation Class

Replacing the TextTransformation class with a class of your own allows you to incorporate custom functionality into methods in that class that can be called in the generated transformation class. Extending the TextTransformation class is a good choice when you have code that will be used in many (or all) of your code-generation solutions: essentially, you factor that code out of your solutions and into the T4 engine.

The first step in extending the TextTransformation class is to create an abstract class that inherits from the class:

public abstract class PhvisT4Base:
  Microsoft.VisualStudio.TextTemplating.TextTransformation
{
}

If you don’t have the Microsoft.VisualStudio.TextTemplating DLL that contains the TextTransformation class, download the SDK for your version of Visual Studio before adding the reference.

Depending on your version of T4, you might have to provide an implementation of the TransformText method as part of inheriting from TextTransformation. If so, your method must return the string containing the output of the generated transformation class, which is held in the TextTransformation class Generation­Environment property. Your override of the TransformText method, if required, should look like this:

public override string TransformText()
{
  return this.GenerationEnvironment.ToString();
}

As with class features, you have two options for adding code to the generated transformation class. You can create methods that return string values, as this example does:

protected string Copyright()
{
  return @"Copyright PH&V Information Services, 2012";
}

This method can be used either in an expression or with the T4 Write or WriteLine methods, as in these examples:

<#= CopyRight() #>
<#  WriteLine(CopyRight()); #>

Alternatively, you can use the TextTransformation base class Write or WriteLine methods directly in the methods you add to the TextTransformation base class, as this example does:

protected void Copyright()
{
  base.Write(@"Copyright PH&V Information Services, 2012");
}

This method can then be called from a control block, like this:

<# CopyRight(); #>

The final step in replacing the default TextTransformation class is to specify in your T4 template that the generated transformation class is to inherit from your new class. You do that with the Inherits parameter of the Template attribute:

<#@ Template language="C#" inherits="PhvT4Utils.PhvisT4Base" #>

You must also add to your T4 template an assembly directive that references the DLL containing your base class, using the full physical path name to the DLL:

<#@ Assembly name="C:\T4Support\PhvT4Utils.dll" #>

Alternatively, you can add your base class to the global assembly cache (GAC).

In Visual Studio 2010, you can use environment and macro variables to simplify the path to the DLL. For instance, during development, you could use the $(ProjectDir) macro to reference the DLL containing your base class:

<#@ Assembly name="$(ProjectDir)\bin\PhvT4Utils.dll" #>

At run time, assuming you install your class files to the Program Files folder, you can use the environment variable %programfiles% on 32-bit versions of Windows or, on 64-bit versions, %ProgramW6432% for the Program Files folder, or %ProgramFiles(x86)% for the Program Files (x86) folder. This example assumes that the DLL has been placed in C:\Program Files\PHVIS\T4Tools on a 64-bit version of Windows:

<#@ Assembly name="%ProgramW6432%\PHVIS\T4Tools\PHVT4Utils.DLL" #>

As with other solutions with compiled code, Visual Studio might lock the DLL containing your code during execution of the generated transformation class. If this happens while you’re developing your TextTransformation class, you’ll need to restart Visual Studio—and recompile your code—before you can make more changes.

Any robust process provides feedback on its progress. In a class feature or when extending the TextTransformation class, you can report on problems in the execution of the generated transformation class by adding calls to the TextTransformation class Error and Warning methods. Both methods accept a single string input and pass the string to the Microsoft Text Template Transformation Toolkit (T4) host (the host is responsible for deciding how to display the messages). In Visual Studio, the messages appear in the Error List window.

This example reports on an error in a class feature:

<#= GetDatabaseData("") #>
<#+  private string GetDatabaseData(string ConnectionString)
{
    if (ConnectionString == "")
    {
      base.Error("No connection string provided.");
    }
  return "";
    }
...

In a class feature, Error and Warning can’t be used to report on problems in the T4 process, only on errors that occur when the generated transformation class executes. To put it another way, in a class feature, Error and Warning messages will appear only if the generated transformation class can be correctly assembled from your T4 file, compiled and executed.

If you’re creating a custom directive processor, you can still integrate with the T4 error reporting process by adding Compiler­Error objects to the class Errors property. The CompilerError object supports passing multiple pieces of information about the error (including line number and file name), but this example simply sets the ErrorText property:

System.CodeDom.Compiler.CompilerError err =  new 
System.CodeDom.Compiler.CompilerError();
err.ErrorText = "Missing directive parameter";
this.Errors.Add(err);

          —P.V.

Custom Directive Processors

The most powerful and flexible way to manage complexity in the code-generation process is to use custom directive processors. Among other features, directive processors can add references to the generated transformation class and insert code into methods that execute before and after the generated transformation class TransformText method. Directives are also easy for developers to use: they just have to provide values for the directive’s parameters and then use the members the directives make available.

The key issue with directive processors is that you must add a key in the Windows registry in order for a developer to use your processor. Here, again, it’s worthwhile to consider packaging up your T4 solution so that the registry entry can be made automatically. On 32-bit versions of Windows, the key must be:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\
visualstudioversion\TextTemplating\DirectiveProcessors

For 64-bit versions of Windows, the key is:

HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\
VisualStudio\10.0\TextTemplating\DirectiveProcessors

The name of your key is the name of your directive processor class, which, following Microsoft naming convention, should end with “DirectiveProcessor.” You’ll need the following subkeys:

  • Default: Empty or a description of your directive.
  • Class: The full name of your class in the format Namespace.ClassName.
  • Assembly/CodeBase: The name of your DLL if you’ve placed your directive’s DLL in the GAC (Assembly) or the full path to your directive’s DLL (CodeBase).

When developing your processor, if you don’t get this entry right, Visual Studio will generate an error that it was unable to find your directive processor or resolve its type. After you fix any errors in your registry entries, you might need to restart Visual Studio to pick up your changes.

To use your directive processor, a developer adds a directive with any name to a T4 template and ties the directive to your processor through the directive’s Processor parameter (which references the Windows registry key). When the T4 template executes, your directive processor is passed the name of the directive that the developer used along with any parameters the developer specified.

This example ties a directive named Copyright to a processor called CopyrightDirectiveProcessor and includes a parameter called Year:

<#@ Copyright Processor="CopyrightDirectiveProcessor" Year=”2012” #>

As with a class feature, the output from a directive processor is added to the generated transformation class outside of the TransformText method. As a result, you’ll use your processor to add new members to the generated transformation class that devel­opers can use in their template control blocks. The previous sample directive might have added a property or a string variable that a developer could use in an expression, like this:

<#= Copyright #>

Of course, the next step is to create a directive process to process this directive.

Creating a Custom Directive Processor

T4 directive processors inherit from the class Microsoft.VisualStudio.TextTemplating.DirectiveProcessor (download the Visual Studio SDK to get the TextTemplating library). Your directive processor must, from its GetClassCodeForProcessingRun method, return the code that will be added to the generated transformation class. However, before calling the GetClassCodeForProcessingRun method, the T4 engine will call the processor’s IsDirective­Supported method (passing the name of your directive) and the ProcessDirective method (passing the name of the directive and the values of its parameters). From the IsDirectiveSupported method, you should return false if your directive shouldn’t be executed, and true otherwise.

Because the ProcessDirective method is passed all the information about the directive, that’s where you usually build the code that the GetClassCodeForProcessingRun will return. You can extract the values of the parameters specified in the directive by reading them from the method’s second parameter (called arguments). This code, in the ProcessDirective method, looks for a parameter called Year and uses it to build a string containing a variable declaration. The string is then returned from GetClassCodeForProcessingRun:

string copyright = string.Empty;
public override void ProcessDirective(
  string directiveName, IDictionary<string, string> arguments)
{
  copyright = "string copyright " +
              "= \"Copyright PH&V Information Services, " +
              arguments["Year"] +"\";";
}
public override string GetClassCodeForProcessingRun()
{
  return copyright;
}

Your directive processor can also add references and using/­import statements to the generated transformation class to support the code added through GetClassCodeForProcessingRun. To add references to the generated transformation class, you just need to return the names of the libraries in a string array from the GetReferencesForProcessingRun method. If, for instance, the code being added to the generated transformation class needed classes from the System.XML namespace, you’d use code like this:

public override string[] GetReferencesForProcessingRun()
{
  return new string[] {"System.Xml"};
}

Similarly, you can specify namespaces to be added to the generated transformation class (as either using or Imports statements) by returning a string array from the GetImportsForProcessingRun method.

The generated code transformation class also includes pre- and post-initialization methods that are called before the TransformText method. You can return code that will be added to those methods from the GetPreInitializationCodeForProcessingRun and GetPostInitializationCodeForProcessingRun methods.

As you debug, remember that using Run Custom Tool does not cause Visual Studio to build your solution. As you make changes to your directive processor, you’ll need to build your solution to pick up your latest changes before executing your template. And, again, because T4 locks the assemblies it uses, you might find you have to restart Visual Studio and recompile your code before retesting.

T4 significantly lowers the barriers for integrating code generation into your toolkit. However, as with any other application, you need to consider how you’ll architect complete solutions to support maintainability and extensibility. The T4 code-generation process provides several places—each with its own costs and benefits—where you can refactor your code and insert it into the process. No matter which mechanism you use, you’ll ensure your T4 solutions are well-architected.

You can get a glimpse of what the generated transformation class looks like by taking advantage of Preprocessed Text Templates, which let you generate text at run time. Compiling or executing the results of Microsoft Text Template Transformation Toolkit (T4) code generation at run time is probably not something most developers would want to tackle. However, if you need several “similar but different” versions of an XML or an HTML document (or some other text), Preprocessed Text Templates let you use T4 to generate those documents at run time.

As with other T4 solutions, the first step in using T4 at run time is to add a T4 file to your project from the New Item dialog. But, instead of adding a Text Template file, you add a Preprocessed Text Template (also listed in the Visual Studio New Item dialog). A Preprocessed Text Template is identical to a standard T4 Text Template file except that the Custom Tool property is set to TextTemplatingFileProcessor instead of the usual TextTemplatingFileGenerator.

Unlike with a Text Template, the child file containing the generated code from a Preprocessed Text Template doesn’t contain the code output by the generated transformation class. Instead, the file holds something that looks very much like one of your generated transformation classes: a class with the same name as the Preprocessed Text Template file with a method called TransformText. Calling that TransformText method at run time returns, as a string, what you’d expect to find in the code file of a T4 template: your generated code. So, for a Preprocessed Text Template file called GenerateHTML, you’d retrieve its generated text using code like this at run time:

GenerateHTML HtmlGen = new GenerateHTML();
string html = HtmlGen.TransformText();

      —P.V.


Peter Vogel is a principal in PH&V Information Services. His last book was “Practical Code Generation in .NET” (Addison-Wesley Professional, 2010). PH&V Information Services specializes in facilitating the design of service-based architectures and in integrating .NET technologies into those architectures. In addition to his consulting practice, Vogel wrote Learning Tree International’s service-oriented architecture design course, taught worldwide.

Thanks to the following technical expert for reviewing this article: Gareth Jones