Text Template Transformation Toolkit, or T4, is a .NET technology for generating various text files, including source code files. It allows you to mix plain text and processing instructions (in a similar way like PHP, among others) and these processing instructions can contain usual C# or VB code. One example of its usage is the generating of entity classes from a database schema in ADO.NET Entity Framework.
A very simple template can look like this:
This file was generated at <#= DateTime.Now #>.
You can see some plain text here plus a usage of .NET class DateTime
and its property Now
, enclosed by some special brackets.
Because this blog is primarily about Dynamics AX, I’ll show one a bit more complex example in AX. It generates a new test class based on a definition of some other class existing in AOT. (The example uses Dynamics AX 2012 and Visual Studio 2010.)
At first, you have to create a Visual Studio project (class library), add it to AOT and create few AX proxy classes: SysDictClass
, SysDictMethod
, Set
and SetEnumerator
. Then add a new item of type Preprocessed Text Template. That’s one type of T4 templates.
Insert the following code to the template:
<#@ template language="C#" #> <#@ import namespace="System.Collections.Generic" #> <#@ import namespace="Microsoft.Dynamics.AX.ManagedInterop" #> Exportfile for AOT version 1.0 or later Formatversion: 1 ***Element: CLS ; Microsoft Dynamics AX Class: <#= ClassName #>Test unloaded ; -------------------------------------------------------------------------------- CLSVERSION 1 CLASS #<#= ClassName #>Test PROPERTIES Name #<#= ClassName #>Test ENDPROPERTIES METHODS SOURCE #classDeclaration #class <#= ClassName #>Test extends SysTestCase #{ #} ENDSOURCE <# foreach (SysDictMethod method in Methods){ #> SOURCE #test<#= Capitalize(method.name()) #> #public void test<#= Capitalize(method.name()) #>() #{ # //TODO: implement unit test #} ENDSOURCE<#}#> ENDMETHODS ENDCLASS ***Element: END <#+ public string ClassName {get; set;} private IEnumerable<SysDictMethod> Methods { get { SysDictClass dictClass = SysDictClass.newName(ClassName); SetEnumerator enumerator = dictClass.methods(true, true, false).getEnumerator(); while (enumerator.moveNext()) { yield return new SysDictMethod((Microsoft.Dynamics.AX.ManagedInterop.Object)enumerator.current()); } } } private string Capitalize(string s) { return s.Substring(0, 1).ToUpper() + s.Substring(1); }#>
As you can see, the template generates a usual .xpo file. Let’s take a closer look at some parts of it.
It begins with the template header and the declaration of some namespaces which we want to use in the template.
<#@ template language="C#" #> <#@ import namespace="System.Collections.Generic" #> <#@ import namespace="Microsoft.Dynamics.AX.ManagedInterop" #>
Then there is some plain text with occasional constructs <#= ClassName #>
. <#=
means that the output of the statement will be added as a text to the generated file. And ClassName
is a name of a property defined later in the template.
The next interesting section is this:
<# foreach (SysDictMethod method in Methods){ #>
You can easily recognize a usual C# code inside (Methods
is another property defined later). Notice that this part is introduced by <#
, not <#=
. It means that the context should be just run, without anything returned to the output. Here we iterate through all method in a collection and repeat the whole section (between foreach
and the closing brace in <#}#>
statement) for each method.
In the foreach
body we use the already known construct returning a value, this time calling some methods:
<#= Capitalize(method.name()) #>
At the end of the template, we have another special part introduced by <#+
. This section is used for declaring class members – properties ClassName
and Methods
and Capitalize()
method, in our case.
Summary: The template expects to get the name of an AX class and it generates a test class and one test method for each (non-inherited) method in the source class. (It would be more complex in a real implementation, but it’s good enough for the demonstration.)
Now let’s use it inside Dynamics AX. Build and deploy the project with T4 template, go to AX and create a job with a similar code:
str className = 'Stack'; str xpoText; TestClassGenerator.TestClassTemplate template; SysImportElements sysImportElements = new SysImportElements(); FileName fileName; TextIo io; //Create instance of T4 template (name depends on how you named project and template in VS) template = new TestClassGenerator.TestClassTemplate(); //Use template's property to set class name template.set_ClassName(className); //Let template generate .xpo file content xpoText = template.TransformText(); //Write .xpo to a temporary file fileName = WinAPI::getTempFilename(WinApi::getTempPath(), ''); io = new TextIo(fileName, 'w'); io.write(xpoText); //Import .xpo sysImportElements = new SysImportElements(); sysImportElements.newFile(fileName); sysImportElements.parmAddToProject(false); sysImportElements.parmImportAot(true); sysImportElements.import(); //Open newly created class TreeNode::findNode(strFmt(@'Classes\%1Test', className)).AOTnewWindow();
When you run the job, the following class should be generated:
As you can see, T4 templates can be really useful. Especially if the target file contain a large amount of static text, T4 is an excellent choice. You may not use it every day, but it’s good to know about it.