Saturday, June 16, 2012

Tutorial (part 2)

In the first post of this tutorial, we created a very simple T4 text template and generated a textual output containing the string Hello World!. In this second part I will show how you can add behavior to your T4 text template by using C#.

We will start by using the T4 text template from part 1 of this tutorial.

<#@ template language="C#" #>
Hello World!

Additionally to saying “hello” to the world, we want to say hello to our friends Mary, Paul, and Peter. The simplest way to do this, would be just adding some new lines to the text template, but we would have to add a new line for each new friend. Therefore we need some logic inside the template that is able to process parameters passed to the T4 text template generator.


In the C# code create a new list containing the names of our friends and pass this list to the Generate method of the T4 text template generator.

public static void Generate()
{
IGenerator generator = new Generator();

var stream = new MemoryStream();
var textWriter = new StreamWriter( stream );

IList<string> friends = new List<string> {"Mary", "Peter", "Paul"};
generator.Generate(textWriter, Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"Part2\Part2.t4"), friends);

stream.Seek( 0, SeekOrigin.Begin );
var textReader = new StreamReader( stream );

var generatedText = textReader.ReadToEnd();
}

To read the parameters, the template needs a parameter directive for each parameter we pass to the T4 text template generator. Because we want to pass a list of names, the data type for the parameter is IList<string>. The generic IList interface is not contained in the System namespace, thus we need an additional import directive for the  System.Collections.Generic namespace. The import directive is the equivalent to the using statement in C# or the imports in VB.

<#@ template language="C#" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ parameter type="IList<string>" name="friends"#>
Hello World!

To use control logic in a template or to generate dynamic text output, you have to add a control block. A control block is a part of a T4 text template that contains code. The code can be either C# or VB. The language must be specified in the language attribute of the template directive. T4 knows three different types of control blocks: Standard Control Block, Expression Control Block, and Feature Control Block. A standard control block is delimited by the T4 symbols <# ... #> and can contain any valid C# or VB statements.

<#  foreach(var friend in friends)
{
Write ( "Hello " + friend + "!\r\n" );
}
#>

In the above code we use the generator’s Write method to create the textual output. If the text becomes more complex, it would be better to use text blocks for the static parts of the output text and expression control blocks for the dynamic part. An expression control block is delimited by the T4 symbols <#= ... #> and can contain C# or VB expressions.

<#  foreach(var friend in friends)
{
#>
Hello <#= friend #>!
<# }
#>

Finally we want to greet our friends in an alphabetical order. Therefore we add a feature control block containing a helper function that sorts out friends names.
A feature control block can contain properties or methods and is delimited by <#+ ... #>. Typically feature control blocks contain helper functions that can be used within a standard or expression control block. In our example, we will add a method to sort the names of our friends.

<#+ private IEnumerable<string> SortFriends(IEnumerable<string> friends)
{
return (from friend in friends
orderby friend ascending
select friend);
}
#>

The implementation of the SortFriends method uses LINQ so we must add a new import directive for the System.Linq namespace. Further, LINQ is provided by the System.Core .NET assembly which is not referenced by default. To use LINQ, you must also add an assembly directive.

<#@ template language="C#" #>
<#@ assembly name="System.Core.dll" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Linq" #>
<#@ parameter type="IList<string>" name="friends"#>

To use the SortFriends method, modify the foreach statement as described below.

<#    foreach(var friend in SortFriends(friends))

If you now run the generator with the modified T4 text template, you will get the following textual output:

Hello World!

Hello Mary!
Hello Paul!
Hello Peter!

Multiple parameters

The parameters parameter of the Generate method is marked with the params keyword, so you can pass a variable number of objects. You must pass these objects in the same sequence as they appear in parameter directives of the T4 text template.
Be aware that the parameter list is not type safe but you can create an extension method that contains exactly the parameters you need.
public static class GeneratorExtension
{
public static void Generate(this IGenerator generator, TextWriter output, string template, IList<string> friends)
{
generator.Generate( template, output, friends );
}
}

Wednesday, June 13, 2012

Tutorial (part 1)

In one of my projects the customer had the requirement, that the application must be able to generate C files used in a test environment for electronic control units (ECU). I tried to generate the C code by using preprocessed text templates integrated in Visual Studio. This worked well but the the customer had further requirements: The template for the C code had to be customizable because a change in the test environment should not necessarily lead to an modification in the application. That was the point were I decided to write my own T4 text template generator that could parse T4 text templates at run-time. Because I had other projects were I needed similar functionality, including the open source project NHibernate.BusinessObjects, I decided to make the T4 Text Template Generator available to the community.

In the first part of this tutorial I will show you how to generate textual output by using a simple T4 text template. The examples are written in C# but you can do the same with VB. To create the T4 text templates, I use Visual Studio 2010 and the Tangible T4 Editor.

The first step is to create a T4 text template file. Therefore, create a new text file in your Visual Studio project and rename the extension to .t4. You should not create a new file based on the "Text Template” template because Visual Studio automatically assigns the TextTemplatingFileGenerator to the CustomTool property and creates a subsidiary text file. In this tutorial, you need to set the Copy to Output Directory attribute to either Copy if newer or Copy always.

The template directive must be the first directive in a template file. In the language attribute specify the language (either C# or VB) used in the template. In the next line add the text you want to generate.

<#@ template language="C#" #>
Hello World!

The next step is to use the template in your application. Therefore, create an instance of the OMS.Ice.T4Generator.Generator class and a stream writer. On the generator call the Generate() method and pass the path to the template and a stream writer as parameters. After the T4 template has been processed, the stream writer contains the generated Hello World! text.

namespace OMS.Ice.T4Generator.Tutorial
{
internal class Part1
{
public static void Generate()
{
IGenerator generator = new Generator();
var stream = new MemoryStream();
var textWriter = new StreamWriter( stream );
generator.Generate( Path.Combine( AppDomain.CurrentDomain.BaseDirectory, @"Part1\Part1.t4" ), textWriter );

stream.Seek( 0, SeekOrigin.Begin );
var textReader = new StreamReader( stream );
var generatedText = textReader.ReadToEnd();
}
}
}

In the second part of this tutorial, I will show you how to use C# in your text template.

Wednesday, June 6, 2012

Code and Text Generation by using T4 Text Templates

Using T4 text templates in the software development process is a common task. Visual Studio supports design-time code generation and run-time text generation by using preprocessed T4 text templates. However, there’s a third use case which is not supported by Visual Studio: Run-time text generation by using T4 text templates.

Design-Time Code Generation

If you need to generate code or other textual files within your Visual Studio project, you typically write a template that can be used to generate code or text from a model. If the model changes, you just need to regenerate the code.

The design-time code generation is well integrated into the Visual Studio. For more Information see the MSDN article about design-time code generation.

Run-time Text Generation by using Preprocessed T4 Text Templates

To generate text output at run-time, you can add a preprocessed text template to your Visual Studio project. Visual Studio automatically generates a code-behind file for the template. The generated code file contains the source code for a text generator class that can be used from your application by calling the TransformText method.  Whenever you change the template, Visual Studio regenerates the subsidiary code file. For more Information see the MSDN article about run-time text generation.

There is a drawback by using preprocessed T4 text templates. The code for the text generator will be created at design-time. At run-time you can only pass data to the generator and the text output has a fix structure. The user cannot customize the template to his own needs.

Run-time Text Generation by using T4 Text Templates

In applications where you need customizable T4 text templates or where the user shall be able to create its own templates, you cannot use preprocessed T4 text templates. In more complex scenarios you may even have a meta template that can be used to generate different T4 text templates. There are many other use cases where you need more flexibility than given with the Visual Studio T4 text generation.

The OMS.Ice – T4 Text Template Generator is all in one, a T4 text template parser, compiler and generator. At runtime, the generator parses a T4 text template, builds code in the selected language (C# or VB), compiles that code into memory, and finally executes the text generator.