System.CodeDom.Compiler.CompilerError();
error.ErrorText = e.ToString();
error.FileName = "C:\\dev\\Chapter 14\\Chapter 14\\Finance.tt";
this.Errors.Add(error);
}
return this.GenerationEnvironment.ToString();
}
void WriteFinancialNumber(decimal amount)
{
if( amount < 0 )
Write("({0:#0.00})", System.Math.Abs(amount) );
else
Write("{0:#0.00}", amount);
}
}
}
Vb
Imports System
Namespace Microsoft.VisualStudio.TextTemplating2739DD4202E83EF5273E1D1376F8FC4E
Public Class GeneratedTextTransformation
Inherits Microsoft.VisualStudio.TextTemplating.TextTransformation
Public Overrides Function TransformText() As String
Try
Me.Write(""&Global.Microsoft.VisualBasic.ChrW(13) _
& Global.Microsoft.VisualBasic.ChrW(10) _
& "Financial Sample Data" _
& Global.Microsoft.VisualBasic.ChrW(13) _
& Global.Microsoft.VisualBasic.ChrW(10)) _
For i as Integer = -5 To 5
WriteFinancialNumber(i)
WriteLine( "" )
Next
Me.Write("End of Sample Data" _
& Global.Microsoft.VisualBasic.ChrW(13) _
& Global.Microsoft.VisualBasic.ChrW(10) _
& Global.Microsoft.VisualBasic.ChrW(13) _
& Global.Microsoft.VisualBasic.ChrW(10)&" ")
Catch e As System.Exception
Dim [error] As System.CodeDom.Compiler.CompilerError = _
New System.CodeDom.Compiler.CompilerError()
[error].ErrorText = e.ToString
[error].FileName = "C:\\dev\\Chapter 14\\Chapter 14\\Finance.tt"
Me.Errors.Add([error])
End Try
Return Me.GenerationEnvironment.ToString
continues
274 .
chaPter 14 code generATion WiTh T4
(continued)
End Function
Sub WriteFinancialNumber(amount as Decimal)
If amount < 0 Then
Write("(${0:#0.00})", System.Math.Abs(amount) )
Else
Write("${0:#0.00}", amount)
End If
End Sub
End Class
End Namespace
Note a few things of interest in this code. First, the template is executed by running the
TransformText() method. The contents of this method run within the context of a try - catch block
where all errors are captured and stored. Visual Studio 2010 knows how to retrieve these errors and
displays them in the normal errors tool window.
The next interesting thing is the use of Write(). You can see that each Text block has been
translated into a single string, which is passed to the Write() method. Under the covers this is
added to the GenerationEnvironment property, which is then converted into a string and returned
to the T4 engine.
The Statement blocks and the Class Feature blocks are copied verbatim into the generated class. The
difference is in where they end up. Statement blocks appear inside the TransformText() method
but Class Feature blocks appear after it and exist at the same scope. This should give you some idea
as to the kinds of things you could declare within a Class Feature block.
Finally, Expression blocks are evaluated and the result is passed into Microsoft.VisualStudio
.TextTemplating.ToStringHelper.ToStringWithCulture(). This method returns a string,
which is then passed back into Write() as if it were a Text block. Note that the ToStringHelper
takes a specific culture into account when producing a string from an expression. This culture can
be specified as an attribute of the template directive.
When the TransformText() method finishes execution it passes a string back to the host environment,
which in this case is Visual Studio 2010. It is up to the host to decide what to do with it. Visual Studio
uses the output directive for this task. Directives are the subject of the next section.
Before moving on, the previous paragraph implied that T4 does not need to run
inside Visual Studio. There is a command - line tool called TextTransform.exe,
which you can find in the %CommonProgramFiles%\microsoft shared\
TextTemplating\10.0\ folder (C:\Program Files(x86)\Common Files\
microsoft shared\TextTemplating\10.0\ on 64-bit machines). Although
you can use this to generate files during a build process, T4 itself relies on the
presence of certain libraries that are installed with Visual Studio to run. This
means that if you have a separate build machine you will need to install Visual
Studio on it. Within Visual Studio, files with the .tt extension are processed
with a custom tool referred to as TextTemplatingFileGenerator.
T4 Directives .
275
t4 directiVes
A T4 template can communicate with its execution environment by using directives. Each directive
needs to be on its own line and is denoted with
standard directives.
template directive
The template directive controls a number of diverse options about the template itself. It contains
the following attributes:
.
language: Defines the .NET language used throughout the template inside of Expression,
Statement, and Class Feature blocks. Valid values are C# and VB.
.
inherits: Determines the base class of the generated class used to produce the output
file. This can be overridden to provide additional functionality from within template files.
Any new base class must derive from Microsoft.VisualStudio.TextTemplating
.TextTransformation, which is the default value for the attribute.
tags. This section discusses the five # > and @#<
If you want to inherit from a different base class, you will need to use an
assembly directive (see the “Assembly Directive” section later in this chapter) to
make it available to the T4 template.
.
culture: Selects a localization culture for the template to be executed within. Values should
be expressed using the standard xx-XX notation (en-US, ja-JP, and so on). The default value
is a blank string that specifies the Invariant Culture.
.
debug: Turns on Debug mode. This causes the code file containing the generator class to be
dumped into the temporary folder of the system. Can be set to true or false. Defaults to
false.
.
hostspecific: Indicates that the template file is designed to work within a specific host. If
set to true, a Host property is exposed from within the template. When running in Visual
Studio 2010 this property is of type Microsoft.VisualStudio.TextTemplating.VSHost
.TextTemplatingService. Defaults to false. It is beyond the scope of this book but you
can write your own host for T4 and use it to execute template files.
output directive
The output directive is used to control the file that is generated by the template. It contains two
properties.
.
extension: The extension that will be added to the generator name to create the filename
of the output file. The contents of this property basically replace .tt in the template filename.
By default, this is .cs but it may contain any sequence of characters that the underlying
file system will allow.
276
.
chaPter 14 code generATion WiTh T4
.
encoding: Controls the encoding of the generated file. This can be the result of any of the
encodings returned by System.Text.Encoding.GetEncodings(); that is, UTF-8, ASCII,
and Unicode. The default, value is Default, which makes the encoding equal to the current
ANSI code page of the system the template is being run on.
assembly directive
The assembly directive is used to give code within the template file access to classes and types
defined in other assemblies. It is similar to adding a reference to a normal .NET project. It has a
single attribute called name, which should contain one of the following items:
.
The filename of the assembly: The assembly will be loaded from the same directory as the
T4 template.
.
The absolute path of the assembly: The assembly will be loaded from the exact path provided.
.
The relative path of the assembly: The assembly will be loaded from the relative location
with respect to the directory in which the T4 template is located.
.
The strong name of the assembly: The assembly will be loaded from the Global Assembly
Cache (CAG).
import directive
The import directive is used to provide easy access to items without specifying their full namespace
qualified type name. It works in the same way as the Import statement in VB or the using
statement from C#. It has a single attribute called namespace. By default, the System namespace
is already imported for you. The following example shows a small Statement block both with and
without an import directive.
c#
<#
var myList = new System.Collections.Generic.List<string>();
var myDictionary = new System.Collections.Generic.Dictionary<string,
System.Collections.Generic.List <string>>();
#>
Code snippet WithoutImport.tt
Vb
<#
Dim myList As New System.Collections.Generic.List(Of String)
Dim myDictionary As New System.Collections.Generic.Dictionary(Of System.String,
System.Collections.Generic.List(Of String))
#>
Code snippet WithImport.tt
T4 Directives .
277
c#
< #@ import namespace="System.Collections.Generic" # >
#<
>#
var myList = new List < string > ();
var myDictionary = new Dictionary < string, List < string > > ();
Code snippet WithImport.tt
Vb
< #@ import namespace=”System.Collections.Generic” #>
<#
Dim myList As New List(Of String)
Dim myDictionary As New Dictionary(Of String, List(Of String))
>#
Code snippet WithImport.tt
The code that benefits from the import and assembly directives is the code that
is executed when the T4 template is run, not the code that is contained within
the final output file. If you want to access resources in other namespaces in the
generated output file, you must include using or Import statements of your own
into the generated file and add references to your project as normal.
include directive
The include directive allows you to copy the contents of another file directly into your template
file. It has a single attribute called file, which should contain a relative or absolute path to the
file to be included. If the other file contains T4 directives or blocks, they are executed as well. The
following example inserts the BSD License into a comment at the top of a generated file.
' Copyright (c) < #=DateTime.Now.Year# > , < #=CopyrightHolder# >
' All rights reserved.
' Redistribution and use in source and binary forms, with or without
...
Code snippet License.txt
c#
< #@ template debug="false" hostspecific="false" language="C#" # >
< #@ output extension=".generated.cs" # >
< # var CopyrightHolder = "AdventureWorks Inc."; # >
/ *
continues
278 .
chaPter 14 code generATion WiTh T4
(continued)
<#@ include file="License.txt" #>
*/
namespace AdventureWorks {
// ...
}
Code snippet IncludeSample.tt
Vb
<#@ template debug="false" hostspecific="false" language="VB" #>
<#@ output extension=".vb" #>
<# Dim CopyrightHolder = "AdventureWorks Inc." #>
<#@ include file="License.txt" #>
Namespace AdventureWorks
' ...
End Namespace
Code snippet IncludeSample.tt
troubleshootinG
As template files get bigger and more complicated, the potential for errors grows significantly.
This is not helped by the fact that errors might occur at several main stages, and each needs
to be treated slightly differently. Remember that even though T4 runs these processes one at a
time, any might occur when a template file is executed, which occurs every time the file is saved.
When making any changes to T4 template files it is highly recommended that you take small steps to
regenerate often and immediately reverse out any change that breaks things.
design-time errors
The first place where errors might occur is when
Visual Studio attempts to read a T4 template
and use it to create the temporary .NET class.
In Figure 14-5 there is a missing hash symbol
in the opening tag for the Expression block.
The resulting template is invalid. The Error List
window at the bottom of Figure 14-5 shows
Visual Studio identifying this sort of issue quite
easily. It is even able to correctly determine the line number where the error occurs.
The other type of error that is commonly encountered at design time relates to directive issues.
In many cases when a problem arises with an attribute of a directive a warning is raised and the
default value is used. When there are no sensible defaults, such as with the import, include, and
assembly directives, an error is raised instead.
fiGure 14-5
compiling transformation errors
The next step in the T4 pipeline where an error might occur is when the temporary .NET code fi le
containing the code generator class is compiled into an assembly. Errors that occur here typically
result from malformed code inside Expression, Statement, or Class Feature blocks. Again, Visual
Studio does a good a job of fi nding and exposing these errors but the fi le and line number references
point to the generated fi le. Each error that is found by the engine at this point is prefi xed with the
string Compiling Transformation which make them easy to identify.
The fi rst step to fi xing these errors is to turn Debug mode on in the template directive. This forces
the engine to dump copies of the fi les that it is using to try and compile the code into the temporary
folder. When these fi les are dumped out, double - clicking the error line in the Error List window
opens the temporary fi le and you can see what is happening. Because this fi le will be a .cs or .vb
fi le Visual Studio is able to provide syntax highlighting and IntelliSense to help isolate the problem
area. Once the general issue has been discovered it is then much easier to fi nd and update the