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 );
}
}