relevant area of the template.
One interesting exception to the way that Visual Studio handles invalid
directives is the extension attribute of the output directive. If the value supplied
is invalid in any way, a warning is raised but the generated fi le is not produced
at all. If you have other code that depends on the contents of the generated fi le,
the background compilation process will quickly fi nd a cascade of errors, which
can be overwhelming. Check to see if the fi le is being generated at all before
attempting to fi x the template by temporarily removing all the contents of the
template fi le except for the template and output directives.
One of the other fi les generated by turning debugging on is a .cmdline fi le,
which contains arguments that are passed to csc.exe or vbc.exe when
T4 compiles the template. You can use this fi le to re - create the compilation
process. There is also a fi le with the .out extension, which contains the
command line call to the compiler and its results.
executing transformation errors
The fi nal step in the T4 pipeline that might generate errors is when the code generator is actually
instantiated and executed to produce the contents of the generated fi le. This stage is essentially
running arbitrary .NET code and is the most likely to encounter trouble with environmental
conditions or faulty logic. Like Compiling Transformation errors, errors found during this stage
have a prefi x of Executing Transformation, which makes them easy to spot.
The best way of handling Executing Transformation errors is to code defensively. From within the
T4 template, if you can detect an error condition such as a fi le missing or being unable to connect
to a database, you can use the Error() method to notify the engine of the specifi c problem. These
Troubleshooting . 279
280 .
chaPter 14 code generATion WiTh T4
errors will appear as Executing Transformation errors just like all of the others, only they’ll have a
more contextual, and hence, more useful message associated with them:
if( !File.Exists(fileName) ) {
this.Error("Cannot find file");
}
In addition to Error() there is an equivalent Warning() method to raise warnings.
If the T4 template encounters an error that is catastrophic, such as not being able to connect to the
database that it gets its data from, it is able to throw an exception to halt the execution process. The
details about the exception are gathered and included in the Error List tool window.
Generated code errors
Although not technically a part of the T4 process, the generated file can just as easily contain
compile-time or run time errors. In the case of compile-time errors, Visual Studio is simply able
to detect these as normal. For run time errors it is probably a good idea to unit test complex types
anyway, even those that have been generated.
Now that you know what to do when things go wrong, it is time to look at a larger example.
GeneratinG code assets
When you develop enterprise applications, you will frequently come across reference data that
rarely changes and is represented in code as an enumeration type. The task of keeping the data
in the database and the values of the enumerated type in sync is time-consuming and repetitive,
which makes it a perfect candidate to automate with a T4 template. The template presented in this
section connects to the AdventureWorks example database and creates an enumeration based on the
contents of the Sales.ContactType table.
c#
<<#@ template debug=”false” hostspecific=”false” language=”C#” #>
<#@ output extension=”.generated.cs” #>
<#@ assembly name=”System.Data” #>
<#@ import namespace=”System.Data.SqlClient” #>
<#@ import namespace=”System.Text.RegularExpressions” #>
<#
var connectionString = “Data Source=.\\SQLEXPRESS; Initial Catalog=AdventureWorks;”
+ “Integrated Security=true;”;
var sqlString = “SELECT ContactTypeID, [Name] FROM [Person].[ContactType]”;
#>
// This code is generated. Please do not edit it directly
// If you need to make changes please edit ContactType.tt instead
namespace AdventureWorks {
Generating Code assets .
281
public enum ContactType {
<#
using(var conn = new SqlConnection(connectionString))
using(var cmd = new SqlCommand(sqlString, conn))
{
conn.Open();
var contactTypes = cmd.ExecuteReader();
while( contactTypes.Read() )
{
#>
<#= ValidIdentifier( contactTypes[1].ToString() ) #> = <#=contactTypes[0]#>,
<#}
conn.Close();
}
#>
}
}
<#+
public string ValidIdentifier(string input)
{
return Regex.Replace(input, @”[^a-zA-Z0-9]”, String.Empty );
}
#>
Code snippet ContactTypes.tt
Vb
<#@ template debug=”false” hostspecific=”false” language=”VB” #>
<#@ output extension=”.generated.vb” #>
<#@ assembly name=”System.Data” #>
<#@ import namespace=”System.Data.SqlClient” #>
<#@ import namespace=”System.Text.RegularExpressions” #>
<#
Dim ConnectionString as String = “Data Source=.\SQLEXPRESS; “ _
& “Initial Catalog=AdventureWorks; Integrated Security=true;”
Dim SqlString as String = “SELECT ContactTypeID,[Name] FROM [Person].[ContactType]”
#>
‘ This code is generated. Please do not edit it directly
‘ If you need to make changes please edit ContactType.tt instead
Namespace AdventureWorks
Enum ContactType
<#
Using Conn As New SqlConnection(ConnectionString), _
continues
282 .
chaPter 14 code generATion WiTh T4
(continued)
Cmd As New SqlCommand(SqlString, Conn)
Conn.Open()
Dim ContactTypes As SqlDataReader = Cmd.ExecuteReader()
While ContactTypes.Read()
#>
<#= ValidIdentifier( contactTypes(1).ToString() ) #> = <#=contactTypes(0)#>
<#
End While
Conn.Close()
End Using
#>
End Enum
End Namespace
<#+
Public Function ValidIdentifier(Input as String) As String
Return Regex.Replace(Input, “[^a-zA-Z0-9]”, String.Empty )
End Function
#>
Code snippet ContactTypes.tt
The first section is made up of T4 directives. The first two specify the language for the template and
the extension of the output file. The third attaches an assembly to the generator (to provide access
to the System.Data.SqlClient namespace), and the final two import namespaces into the template
that the template code requires.
The next section is a T4 Statement block. It contains some variables that the template will be using.
Putting them at the top of the template file makes them easier to find later on in case they need to change.
After the variable declarations there is a T4 Text block containing some explanatory comments
along with a namespace and an enumeration declaration. These are copied verbatim into the
generated output file. It’s usually a good idea to provide a comment inside the generated file
explaining where they come from and how to edit them. This prevents nasty accidents when changes
are erased after a file is regenerated.
The bulk of the rest of the template is taken up by a Statement block. This block creates and opens a
connection to the AdventureWorks database using the variables defined in the first Statement block.
It then queries the database to retrieve the desired data with a data reader.
For each record retrieved from the database a Text block is produced. This Text block consists
of two Expression blocks separated by an equals sign. The second expression merely adds the
ID of the Contact Type to the generated output file. The first one calls a helper method called
ValidIdentifier, which is defined in a Class Feature block that creates a valid identifier for each
contact type by removing all invalid characters from the Contact Type Name.
The generated output file is shown in the following listing. The end result looks fairly simple in
comparison to the script that is used to generate it, but this is a little deceiving. The T4 template can
remain the same as rows of data are added to and removed from the ContactType table. In fact, the
Generating Code assets .
283
items in the database can be completely re-ordered and your code will still compile. With a little
modification this script can even be used to generate enumerated types from a number of different
tables at once.
c#
// This code is generated. Please do not edit it directly
// If you need to make changes please edit ContactType.tt instead
namespace AdventureWorks {
public enum ContactType {
AccountingManager = 1,
AssistantSalesAgent = 2,
AssistantSalesRepresentative = 3,
CoordinatorForeignMarkets = 4,
ExportAdministrator = 5,
InternationalMarketingManager = 6,
MarketingAssistant = 7,
MarketingManager = 8,
MarketingRepresentative = 9,
OrderAdministrator = 10,
Owner = 11,
OwnerMarketingAssistant = 12,
ProductManager = 13,
PurchasingAgent = 14,
PurchasingManager = 15,
RegionalAccountRepresentative = 16,
SalesAgent = 17,
SalesAssociate = 18,
SalesManager = 19,
SalesRepresentative = 20,
}
}
Code snippet ContactTypes.generated.cs
Vb
' This code is generated. Please do not edit it directly
' If you need to make changes please edit ContactType.tt instead
Namespace AdventureWorks
Enum ContactType
AccountingManager = 1
AssistantSalesAgent = 2
AssistantSalesRepresentative = 3
CoordinatorForeignMarkets = 4
ExportAdministrator = 5
InternationalMarketingManager = 6
MarketingAssistant = 7
MarketingManager = 8
MarketingRepresentative = 9
OrderAdministrator = 10
Owner = 11
OwnerMarketingAssistant = 12
continues
284 .
chaPter 14 code generATion WiTh T4
(continued)
ProductManager = 13
PurchasingAgent = 14
PurchasingManager = 15
RegionalAccountRepresentative = 16
SalesAgent = 17
SalesAssociate = 18
SalesManager = 19
SalesRepresentative = 20
End Enum
End Namespace
Code snippet ContactTypes.generated.vb
PreProcessed text teMPlates
Text Template Transformation is a powerful technique and it shouldn’t be restricted to a design-time
activity. Visual Studio 2010 makes it easy to take advantage of the T4 engine to create your own
text template generators to use in your own projects. These generators are called Preprocessed Text
Templates.
To create a new Preprocessed Text Template, open the Add New Item dialog, select the General
page, and select Preprocessed Text Template from the list of items. The newly created file has the
same .tt extension as normal T4 template files and contains a single T4 directive:
c#
<#@ template language="C#" #>
Vb
<#@ template language="VB" #>
Note that there is no output directive. The generated file will have the same filename as the
template file but the .tt will be replaced with .vb or .cs depending on your project language.
When this file is saved, it generates an output file like the following.
c#
// ---------------------------------------------------------------------------//
<auto-generated>
// This code was generated by a tool.
// Runtime Version: 10.0.0.0
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
// ---------------------------------------------------------------------------namespace
Chapter_14
{
Preprocessed Text Templates .
285
using System;
public partial class NewTemplate
{
// region Fields
// region Properties
// region Transform-time helpers
public virtual string TransformText()
{
return this.GenerationEnvironment.ToString();
}
}
}
Code snippet NewTemplate.cs
Vb
Imports System
'-----------------------------------------------------------------------------'
< auto-generated >
' This code was generated by a tool.
' Runtime Version: 10.0.0.0
'
' Changes to this file may cause incorrect behavior and will be lost if
' the code is regenerated.
' < /auto-generated >
'------------------------------------------------------------------------------
Namespace My.Templates
Partial Public Class NewTemplate
' Region "Fields"
' Region "Properties"
' Region "Transform-time helpers"
Public Overridable Function TransformText() As String
Return Me.GenerationEnvironment.ToString
End Function
End Class
End Namespace
Code snippet NewTemplate.vb
This is very much like the interim code file that is produced by T4 for a normal template. This
generated class is now just a class inside the project, which means you can instantiate it, fill in its
properties, and call TransformText() on it.
Just as with a normal Text Template, Visual Studio uses a Custom Tool to
generate the output file of a Preprocessed Text Template. Instead of using the
TextTemplatingFileGenerator custom tool, Preprocessed Text Templates are
transformed using the TextTemplatingFilePreprocessor custom tool, which
adds the code generator class to your project instead of the results of executing
the code generator.
286 .
chaPter 14 code generATion WiTh T4
using Preprocessed text templates
To demonstrate how to use a Preprocessed Text Template within your own code, this section
presents a simple scenario. The project needs to be able to send a standard welcome letter to new
club members when they join the AdventureWorks Cycle club. The following Preprocessed Text
Template contains the basic letter that is to be produced.
c#
<#@ template language="C#" #>
Dear <#=Member.Salutation#> <#=Member.Surname#>,