Re-Using T4 template for multiple model diagrams
Date: Tuesday, September 3, 2013 11:37 AM
Posted by: Nico Ploner
Category: T4
For clarity reasons class models might be split up into multiple diagrams or several class diagrams contribute to the same data model. In this case one would like the T4 template that generates the data model to process all class diagrams at the same time. Unfortunately at the moment a T4 template can only access a single tangible ClassDiagram model using a <#@ TangibleClassDiagram #> directive, so one would have to create a duplicate T4 template for each ClassDiagram. This blog post covers how to process multiple diagrams without maintaining duplicate template code.
Info:
We are planning to include access to multiple diagrams in a future release of tangible T4 Editor and modeling tools.
The basic idea is to put the template code processing a ClassDiagram model file into a reusable T4 template file (.ttinclude), create a T4 template for each ClassDiagram and call the reusable template from within that T4 template. This results in a single file that needs to be altered in order to apply changes to how all diagrams are transformed. But still one would need to create a T4 template for each ClassDiagram that calls the reusable template. In order to avoid this we are going to use a “Meta”-Template: a T4 template that generates a T4 template for each contributing ClassDiagram using the previously created reusable .ttinclude.
Here is a step-by-step guide of how to implement this functionality:
1. The Scenario
2. Generating the reusable template
3. Create T4 template for each Model Diagram
4. Use a “Meta”-Template to generate templates
1. The scenario
Let’s start with a new Visual Studio project that contains two tangible ClassDiagrams that should contribute to the same data model, each one containing a single class:
2. Generating the reusable template
Add a new reusable T4 template to the project that will contain the template code transforming a ClassDiagram into data classes for the data model. Name this reusable template “Transformation.ttinclude”.
Since this template is going to be used from several other templates in order to achieve the same functionality, we are going to place as much code in here as possible. This avoids the need of maintaining lots of T4 templates – one for each ClassDiagram.
Add the necessary template directives to the reusable template like, output file extension, imports and assembly references:
<#@ IntelliSenseLanguage processor="tangibleT4Editor" language="C#" #>
<#@ output extension=".cs" #>
<#@ assembly name="tangible.Modelling.ClassDiagrams.Dsl.10.dll" #>
<#@ assemblyForIntellisenseOnly processor="tangibleT4Editor" name="System.Core" #>
<#@ import namespace="tangible.Modelling.ClassDiagrams" #>
To enable calling this template from within another T4 template the transformation logic needs to be put into a method inside a static CodeBlock of this .ttinclude. This method uses a parameter to gain access to the ClassDiagram model that needs to be transformed. Each T4 template will pass its referenced ClassDiagram here.
<#+
void ProcessModel(ModelRoot modelRoot)
{
// create namespaces, classes, methods etc. here
foreach(ModelType type in modelRoot.Types)
{
#> public class <#= type.Name #> { }
<#+ }
}
#>
Info:
In this example we’re only rendering a C# class with the proper model name and skip Properties or Methods. You can find a sample template generating complete data classes from a ClassDiagram in our template Gallery.
3. Create T4 template for each Model Diagram
Now we need a T4 template for each .tacls file in our solution that calls the ProcessTemplate method for its ClassDiagram. Of course you can use dependent templates that are transformed each time you save changes to the ClassDiagram (see http://t4-editor.tangible-engineering.com/blog/dependent-t4-templates.html). We are going to use standalone templates here.
Add a new Blank T4 Template to your solution for each ClassDiagram you would like to use. Here in the sample, we need two templates (ClassDiagram1.tt and ClassDiagram2.tt). Inside the template, use a <#@ include #> directive to reference the “Transformation.ttinclude” and a <#@ TangibleClassDiagram #> directive to reference the appropriate diagram file. Then call the ProcessModel() method for that ClassDiagram.
Here is what the template for ClassDiagram1 looks like:
<#@ template
inherits="Microsoft.VisualStudio.TextTemplating.VSHost.ModelingTextTransformation"
debug="true" hostSpecific="true" #>
<#@ TangibleClassDiagram processor="TangibleClassDiagramDirectiveProcessor"
provides="ModelRoot" requires="fileName='.\ClassDiagram1.tacls'" #>
<#@ include file=".\Transformation.ttinclude" #>
<#
ProcessModel(this.ModelRoot);
#>
In our example this Template would generate a file named “ClassDiagram1.cs” containing the following code:
public class ModelClass1 { }
Add another Blank T4 Template to the project containing the same T4 code snippet but change the file name to “ClassDiagram2.tacls” for the second ClassDiagram.
4. Use a “Meta”-Template to generate templates
We could stop right at this point, but maintaining a T4 template for each CodeModel still seems a bit unhandy, so let’s do another step. How about a template that generates everything we need for each ClassDiagram file we would like? Here is what you need:
Add the reusable Template “Template FileManager V2.1” from the category “tangible > includes” of the template gallery to your solution.
Then add a Blank T4 Template to your solution and name it “ModelGenerator.tt”. Let this template generate a text file (extension .txt) and reference the template filemanager. Inside this template, create a list of ClassModel files you would like to generate everything for:
<#@ template debug="true" hostSpecific="true" #>
<#@ output extension=".txt" #>
<#@ Assembly Name="System.Core" #>
<#@ import namespace="System" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Collections" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ include file=".\TemplateFileManagerV2.1.ttinclude" #>
<#
var modelFiles = new List<string>()
{
"ClassDiagram1.tacls",
"ClassDiagram2.tacls"
};
#>
Now use the FileManager to let the T4 template generate a new output file (which will be a T4 template) for each ClassDiagram. For each output file write the content of a T4 template of step 3 but replace the file name properly.
Tip:
You should remove the files “ClassDiagram1.tt” and “ClassDiagram2.tt” we created in step 3 now in order to avoid conflicts…
Continue the “ModelGenerator.tt” like this:
<#
TemplateFileManager manager = TemplateFileManager.Create(this);
foreach(var file in modelFiles)
{
// create a new output file
manager.StartNewFile(file.Split('.')[0] + ".tt");
// write content to output file but replace the proper file name
#>
\<#@ template
inherits="Microsoft.VisualStudio.TextTemplating.VSHost.ModelingTextTransformation"
debug="true" hostSpecific="true" \#>
\<#@ TangibleClassDiagram processor="TangibleClassDiagramDirectiveProcessor"
provides="ModelRoot" requires="fileName='.\\<#= file #>'" \#>
\<#@ include file=".\Transformation.ttinclude" \#>
\<#
ProcessModel(this.ModelRoot);
\#>
<# }
manager.Process();
#>
Saving this file will create two output files beneath “ModelGenerator.tt” in the Solution Explorer: “ClassDiagram1.tt” and “ClassDiagram2.tt” each one referencing the proper ClassDiagram file “ClassDiagram1.tacls” and “ClassDiagram2.tacls” respectively. When those templates are saved, they are automatically transformed and the C# code is being generated into “ClassDiagram1.cs” and “ClassDiagram2.cs” – neat, isn’t it?
This is what the Solution finally should look like: