T4 templates
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.
This was originally posted here.

Like
Report
*This post is locked for comments