left-icon

T4 Succinctly®
by Nick Harrison

Previous
Chapter

of
A
A
A

CHAPTER 2

Getting Started

Getting Started


Two Types of Templates

We have two types of templates: text templates and runtime text templates. Text templates will produce output directly controlled by the template, while runtime text templates will produce a class that is called by your code to produce the output described in your template.

This chapter will focus on text templates, but everything that we will say about the structure and syntax for a T4 template will hold true for both types. The difference will come in the types of output produced, and how they are used.

When you select Add New Item, you can filter to show the template items.

Add New Item Templates

Adding Templates to Projects

Let’s start with a Console Application project. From the Add New Item dialog, select TextTemplate. For our first template, we will keep the unimaginative name, TextTemplate.tt. The initial code in the template will be very basic.

<#@ template debug="true" hostSpecific="true" #>

<#@ output extension=".cs" #>

<#@ Assembly Name="System.Core" #>

<#@ Assembly Name="System.Windows.Forms" #>

<#@ import namespace="System" #>

<#@ import namespace="System.IO" #>

<#@ import namespace="System.Diagnostics" #>

<#@ import namespace="System.Linq" #>

<#@ import namespace="System.Collections" #>

<#@ import namespace="System.Collections.Generic" #>

<#

#>

Initial Basic Template

Saving this template will run the Custom Tool, which will process the template. You can also run the Custom Tool by selecting Run Custom Tool in the context menu for the template in Solution Explorer.

Solution Explorer Context Menu

After running the Custom Tool, you will find a new file nested under the template in the Solution Explorer. This file will have the same name as the template, but with a .cs extension. This will be the output of running the template. At this point the output will be blank because we haven’t actually told our template to do anything yet.

Add the following bit of code to the template, and let’s look at the output now.

<#@ template debug="true" hostSpecific="true" #>

<#@ output extension=".cs" #>

<#@ Assembly Name="System.Core" #>

<#@ Assembly Name="System.Windows.Forms" #>

<#@ import namespace="System" #>

using System;

namespace book

{

 class Program

 {

  static void Hello(string[] args)

  {

     Console.WriteLine("Hello World");

     Console.ReadKey();

  }

 }

}

Hard-Coded Template

The output is fairly straightforward, if a little uninspired, but we will work on that. Let’s now see what the output looks like.

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

namespace book

{

 class Program

 {

  static void Hello(string[] args)

  {

     Console.WriteLine("Hello World");

     Console.ReadKey();

  }

 }

}

Template Output

This showcases the beauty and appeal of T4. Looking at the text in the template, it’s easy to see how the output will look.

Now let’s turn our attention to syntax and the components of this template.

Anatomy of a Template

Templates are composed of directives, text blocks, code blocks, and feature blocks. We will explore each of these in turn.

Directives

Directives are marked with <#@ #> delimiters.

The general syntax is:

<#@ DirectiveName [AttributeName = "AttributeValue"] ... #>

Directives should be at the top of the template file. They cannot be included in a code block or after any feature blocks.

In the template that we have been looking at, the following directives are used:

<#@ template debug="true" hostSpecific="true" #>

<#@ output extension=".cs" #>

<#@ Assembly Name="System.Core" #>

<#@ Assembly Name="System.Windows.Forms" #>

<#@ import namespace="System" #>

Directives for out Template

We will now look at the details for each of these directives.

Template Directive

This is a key directive. Every template will include a template directive, and can only include this directive once. If your template has more than one template directive, you will get a warning message like this:

Multiple template directives were found in the template. All but the first one will be ignored. Multiple parameters to the template directive should be specified within one template directive.

There are no warning or error messages given if you do not have a template directive, but there are some key attributes that you will generally want to set.

Attribute

Description

language

You can specify C# or VB. The default value will be C#. This will be the language that the code blocks and expression blocks are written in, not the language generated by the template.

compilerOptions

Any valid compiler option. These options are passed to the compiler when the template is compiled. This is ignored for Run Time template.

culture

Specifies the culture to use to evaluate expression blocks as they are converted to text. This defaults to InvariantCulture.

debug

Specifies whether or not to keep the intermediate files created to compile the template. This is useful to get better error messages and to navigate to the source of the problem if there are problems compiling the template.

hostspecific

Can be true, false, or trueFromBase. The default value is false. If set to true, the base class for the template will include a property Host that will give you access to the hosting engine. This attribute is not applicable for Run Time Templates.

inherits

For text templates, this is the base class for the intermediate class used to create the template when the Custom Tool is run. This can be any class derived from Microsoft.VisualStudio.TextTemplating.TextTransformation. We will discuss the implications of this attribute in the context of run time templates in Chapter 3.

visibility

This attribute is applicable only with run time templates. This specifies the visibility for the class generated to create the run time template. The default value is public. You can also set it to internal to limit who can use the template.

linePragmas

This attribute can be true or false, and will determine whether or not linePragma directives are included in the generated code used to create the template. This allows you to get better error messages if there are problems compiling the template.

Output Directive

This directive is applicable only for text templates, and will control the file extension and encoding for the generated file.

File Extension

Description

extension

Specifies the extension for the generated file. The default value is “.cs”. This does not influence the language for the generated file, just the extension for the file name. It is ignored for run time templates in that it does not change the file extension of the related nested file, but will not cause any errors if it is provided anyway.

encoding

Specifies the encoding for the generated file. The default value is 0 for system default. Valid values can be any of the WebName or CodePage values from calling Encoding.GetEncodings().

Assembly Directive

This directive has a similar effect to adding a reference to a Visual Studio project. There is only one attribute: the name. You can specify the full name as recorded in the GAC, the fully qualified assembly name, or the absolute path to the assembly.

You can also reference Visual Studio variables with syntax like this:

<#@ assembly name="$(SolutionDir)\BaseTemplate\bin\debug\BaseTemplate.dll" #>

Or

<#@ assembly name="%LibraryDirectory%\Library\%Version%\BaseTemplate.dll" #>

Import Directive

This directive is similar to a using directive in C# or an Imports directive in VB. Any namespace that you specify must be found in one of the assemblies mentioned in the Assembly directives. This directive has a single attribute—the name of the namespace to import.

Include Directive

This directive is similar to the #include directive from classic ASP. The contents of another file are inserted into the template being worked on.

file

The file path can be a relative or an absolute path to the file to be included. The path can also include any of the variables discussed earlier for the assembly directive.

once

Can be set to true to ensure that the specified file is included only once

Text Blocks

Text blocks are created from straight text that is entered in a template and copied directly to the generated file. This allows us to write template code that looks like the output that will be produced.

The text block for our original template is marked in the dotted border in the following code:

<#@ template debug="true" hostSpecific="true" #>

<#@ output extension=".cs" #>

<#@ Assembly Name="System.Core" #>

<#@ Assembly Name="System.Windows.Forms" #>

<#@ import namespace="System" #>

<#@ import namespace="System.IO" #>

<#@ import namespace="System.Diagnostics" #>

<#@ import namespace="System.Linq" #>

<#@ import namespace="System.Collections" #>

<#@ import namespace="System.Collections.Generic" #>

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

namespace book

{

 class Program

 {

  static void Hello(string[] args)

  {

     Console.WriteLine("Hello World");

     Console.ReadKey();

  }

 }

}

Template Highlighting the Text Block

Expression Blocks

Expression blocks are inserted in text blocks. With expression blocks, you can inject values from metadata or the results of evaluating an expression into the generated code. Before this value is injected into the generated code, the template engine will run the value through the ToString function, taking into account the culture specified in the Template attribute.

The syntax for an expression looks familiar to anyone who has done ASP programming.

<#= DateTime.Today #>

This expression will insert today’s date into the generated code. Any valid expression can be used in the expression block, but not a statement. Behind the scenes, the expression will get wrapped in a call to this.Write() in the generated class used to create the template.

Let’s add an expression to the original template and see how that changes the generated code.

<#@ template debug="true" hostSpecific="true" #>

<#@ output extension=".cs" #>

<#@ Assembly Name="System.Core" #>

<#@ Assembly Name="System.Windows.Forms" #>

<#@ import namespace="System" #>

<#@ import namespace="System.IO" #>

<#@ import namespace="System.Diagnostics" #>

<#@ import namespace="System.Linq" #>

<#@ import namespace="System.Collections" #>

<#@ import namespace="System.Collections.Generic" #>

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

namespace book

{

 // Generated on <#= DateTime.Today #>

 class Program

 {

  static void Hello(string[] args)

  {

     Console.WriteLine("Hello World");

     Console.ReadKey();

  }

 }

}

Simple Template with an Expression Block

The output will now look like this:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

namespace book

{

 // Generated on 04/23/2015 00:00:00

 class Program

 {

  static void Hello(string[] args)

  {

     Console.WriteLine("Hello World");

     Console.ReadKey();

  }

 }

}

Output from the Expression Block

If we change the culture, we can see the impact of this attribute to the template directive:

Let’s change the template directive like this:

<#@ template debug="true" hostSpecific="true" culture="en-GB"#>

The output now looks like the following, with the British format for the date:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

namespace book

{

 // Generated on 23/04/2015 00:00:00

 class Program

 {

  static void Hello(string[] args)

  {

     Console.WriteLine("Hello World");

     Console.ReadKey();

  }

 }

}

Output Using the Great Britain Culture

Code Blocks

Beyond simple expressions, we may want to execute code statements with more complex logic.

To demonstrate this, let’s create a new template that will loop through and list out the valid cultures that could be passed to the culture attribute of the template directive.

In our solution, let’s add a new template and call it culture.tt.

Add the following code to this template:

<#@ template debug="false" hostspecific="false" language="C#" #>

<#@ assembly name="System.Core" #>

<#@ import namespace="System.Linq" #>

<#@ import namespace="System.Text" #>

<#@ import namespace="System.Collections.Generic" #>

<#@ import namespace="System.Globalization" #>

<#@ output extension=".txt" #>

<#

     var cultures = 

           CultureInfo.GetCultures(CultureTypes.SpecificCultures);

     foreach (var culture in cultures )

      {

#>

     // <#= culture.DisplayName #>

          template debug="false" hostspecific="false" language="C#" culture="<#=culture.Name #>"

           

<#

     }

#>

Simple Template with a Code Block

The code block is delimited by <# and #>

Inside a code block, we can declare variables, have conditional statements, loop statements, call methods, etc. Anything that can go in the body of a method can go inside a code block.

If we look at culture.txt, the output will look like this:

// Arabic (Saudi Arabia)

template debug="false" hostspecific="false" language="C#" culture="ar-SA"

           

// Bulgarian (Bulgaria)

template debug="false" hostspecific="false" language="C#" culture="bg-BG"

           

// Catalan (Catalan)

template debug="false" hostspecific="false" language="C#" culture="ca-ES"

           

// Chinese (Traditional, Taiwan)

template debug="false" hostspecific="false" language="C#" culture="zh-TW"

           

// Czech (Czech Republic)

template debug="false" hostspecific="false" language="C#" culture="cs-CZ"

           

// Danish (Denmark)

template debug="false" hostspecific="false" language="C#" culture="da-DK"

. . .

// Chinese (Traditional, Hong Kong S.A.R.)

template debug="false" hostspecific="false" language="C#" culture="zh-HK"

           

// Chinese (Traditional, Macao S.A.R.)

template debug="false" hostspecific="false" language="C#" culture="zh-MO"

           

// Chinese (Simplified, Singapore)

template debug="false" hostspecific="false" language="C#" culture="zh-SG"

           

// Chinese (Traditional, Taiwan)

template debug="false" hostspecific="false" language="C#" culture="zh-TW"

           

// isiZulu (South Africa)

template debug="false" hostspecific="false" language="C#" culture="zu-ZA"

This creates a handy reference for any specific culture you may need. This also showcases that the output of the T4 can be any type of text—not just code.

Feature Blocks

Feature blocks allow us to create new features like methods, properties, and even classes.

There are a couple of rules that we have to follow when dealing with feature blocks:

  • A feature block cannot be defined within a code block or a text block.
  • Feature blocks must be the last thing in a template file. You cannot define a feature block and then add a new code block or text block.
  • Feature blocks can contain text blocks, which can include an expression block.

Feature blocks are delimited by the <#+ #> characters.

Let’s go back to the Template1.tt that we were working on earlier, and add a feature block. Add the following code to Template1.tt:

<#@ template debug="true" hostSpecific="true" #>

<#@ output extension=".cs" #>

<#@ Assembly Name="System.Core" #>

<#@ Assembly Name="System.Windows.Forms" #>

<#@ import namespace="System" #>

<#@ import namespace="System.IO" #>

<#@ import namespace="System.Diagnostics" #>

<#@ import namespace="System.Linq" #>

<#@ import namespace="System.Collections" #>

<#@ import namespace="System.Collections.Generic" #>

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

namespace book

{

 class Program

 {

  static void Hello(string[] args)

  {

     Console.WriteLine("Hello World");

     Console.ReadKey();

  }

 }

}

<#+

 private void HelloWorld()

 {

#>

   Hello World at <#= DateTime.Now #>

<#+

 }

#>

Feature Block added to Template1.tt

Here we have a new feature block that defines the method HelloWorld. This feature block includes a text block that simply writes out the common phrase “Hello World”. The text block also includes an expression block that will write out the current time.

If we try to include anything after the closing delimiter for the feature block, we will get the following error message:

This is a helpful error message.

A better message would be:

A Template containing a class feature must contain only class features once the class feature is defined.

Take a look at the following code:

<#+

 private void HelloWorld()

 {

#>

  Hello World at <#= DateTime.Now #>

<#+

 }

#>

Hello world

<#+

 private void GoodBye()

 {

#>

  Bye Bye World at <#= DateTime.Now #>

<#+

 }

#>

Feature Block with an Error

If your template looks like this at the bottom, you will get a slew of error messages that won’t make any sense. To understand what is going on, we need to look at the intermediary class that is created to support the template. As long as you have debug set to true in your template directive, Visual Studio will keep the intermediary files for you; you just have to find them.

The intermediary file will be located in the %TEMP% directory. By default, this will be in your AppData/Local/Temp directory. Navigate to this directory and sort the contents by Date Modified. Look for the most recently modified .cs file. This will be the class that the Custom Tool created to implement your template.

Open this file, and we will take a peek under the hood at what is happening.

Text Templates behind the Scenes

When you open the file, it will look like this:

namespace Microsoft.VisualStudio.TextTemplating9A2BB878B08A3E9F2B95519

{

 using System;

 using System.IO;

 using System.Diagnostics;

 using System.Linq;

 using System.Collections;

 using System.Collections.Generic;

 

 /// <summary>

 /// Class to produce the template output

 /// </summary>

 

 #line 1 "E:\t4\T4\project\book\book\Template1.tt"

 public class GeneratedTextTransformation : Microsoft.VisualStudio.TextTemplating.TextTransformation

 {

#line hidden

  /// <summary>

  /// Create the template output

  /// </summary>

  public override string TransformText()

  {

  try

  {

   this.Write(@"

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

namespace book

{

 class Program

 {

  static void Hello(string[] args)

  {

  Console.WriteLine(""Hello World"");

  Console.ReadKey();

  }

 }

}

");

  }

  catch (System.Exception e)

  {

   e.Data["TextTemplatingProgress"] = this.GenerationEnvironment.ToString();

   throw new System.Exception("Template runtime error", e);

  }

  return this.GenerationEnvironment.ToString();

  }

  private global::Microsoft.VisualStudio.TextTemplating.ITextTemplatingEngineHost hostValue;

  /// <summary>

  /// The current host for the text templating engine

  /// </summary>

  public virtual global::Microsoft.VisualStudio.TextTemplating.ITextTemplatingEngineHost Host

  {

  get

  {

   return this.hostValue;

  }

  set

  {

   this.hostValue = value;

  }

  }

 

  #line 34 "E:\t4\T4\project\book\book\Template1.tt"

 private void HelloWorld()

 {

 

  #line default

  #line hidden

 

  #line 37 "E:\t4\T4\project\book\book\Template1.tt"

this.Write("  Hello World at ");

 

  #line default

  #line hidden

 

  #line 38 "E:\t4\T4\project\book\book\Template1.tt"

this.Write(Microsoft.VisualStudio.TextTemplating.ToStringHelper.ToStringWithCulture(DateTime.Now));

 

  #line default

  #line hidden

 

  #line 38 "E:\t4\T4\project\book\book\Template1.tt"

this.Write("\r\n");

 

  #line default

  #line hidden

 

  #line 39 "E:\t4\T4\project\book\book\Template1.tt"

 }

 

  #line default

  #line hidden

 

  #line 41 "E:\t4\T4\project\book\book\Template1.tt"

this.Write("\r\nHello world\r\n\r\n\r\n");

 

  #line default

  #line hidden

 

  #line 46 "E:\t4\T4\project\book\book\Template1.tt"

 private void GoodBye()

 {

 

  #line default

  #line hidden

 

  #line 49 "E:\t4\T4\project\book\book\Template1.tt"

this.Write("  Bye Bye World at ");

 

  #line default

  #line hidden

 

  #line 50 "E:\t4\T4\project\book\book\Template1.tt"

this.Write(Microsoft.VisualStudio.TextTemplating.ToStringHelper.ToStringWithCulture(DateTime.Now));

 

  #line default

  #line hidden

 

  #line 50 "E:\t4\T4\project\book\book\Template1.tt"

this.Write("\r\n");

 

  #line default

  #line hidden

 

  #line 51 "E:\t4\T4\project\book\book\Template1.tt"

 }

 

  #line default

  #line hidden

 }

 

 #line default

 #line hidden

}

Intermediary Class Created by T4 to Implement the Template

Some of this should look very familiar based on the template that we have been working on, but some of it will look strange.

Don’t worry about the scary-looking namespace. It will change every time, and is only needed for T4 internally.

Behind the scenes, T4 has created a new class called GeneratedTextTransformation that is derived from Microsoft.VisualStudio.TextTemplating.TextTransformation.

Note that this generated class overrides the TransformText method. More importantly, the implementation of this method outputs all of the text blocks of our template and executes the code in any code blocks as part of the TransformText method.

The feature blocks are treated differently; once we get a feature block, we are no longer working in the context of the TransformText method. Instead, we are now working in the context of the methods defined in the feature block.

This explains the problem reported in the error messages with compiling the template.

The “Invalid token ‘this’ in class, struct, or interface member declaration” error message refers to line 113 of the generated intermediary file.

Text blocks get converted to calls to the Write method, which makes sense in the context of the TransformText method, or the context of a method being defined in a feature block, but does not make sense outside the context of a method. Now the error message makes sense.

This peek behind the scenes shows us a couple of guiding principles that are helpful to keep in mind when writing templates:

  • Text blocks are converted to calls to the Write method from the TextTransformation class.
  • Expression blocks are converted to calls to the ToStringWithCulture method of the ToStringHelper class.
  • Code blocks are evaluated directly in whatever method they are defined in. If a feature block has not been defined, they are evaluated in the override of the TransformText method.

Now that we know our templates are actually defining a new class derived from the TextTransformation class, let’s turn our attention to what utility functions this class provides.

Utility Methods

In looking at the intermediary class created to implement our template, we have seen a couple of these utility methods as they were used in the generated class. Let’s explore these methods a bit further.

Output Functions

Write

Writes the specified text out to the generated text output. There is an overload of this method that behaves like string.Format for handling more complex string concatenation. This is the only overload. Every parameter you pass to this method must already be a string. There are no other overrides to handle other data types.

WriteLine

WriteLine behaves just like Write, except that it adds a carriage return and line feed to the end of the output.

Error-Reporting Functions

Warning

Adds a new warning to an internal list of errors that will be displayed in the output window. This does not affect execution of the template, but simply logs a warning message.

Error

Works just like the Warning method, except the message that you pass in will be flagged as an error instead of a warning in the Error List window. This also does not affect execution of the template; it simply logs an error message. If you want to halt the execution of the template, you will need to add a return statement to exit out of the method you are in. Usually this will be the overridden TransfromText method.

Formatting Functions

ClearIndents

Resets the CurrentIndent property to blank.

PushIndent

Appends the specified value to the CurrentIndent property. In general this should be a string with the number of spaces to indent

PopIndent

Removes the most recently added text for the CurrentIndent property

Output Functions

Depending on what you are trying to output, it may be easier to simply use Write and WriteLine in a code block instead of creating a new text block.

Let’s go back and look at the HelloWorld and GoodByeWorld methods that we added to Template1 when we were discussing feature blocks.

These methods could be written to look like this:

<#+

 private void HelloWorld()

 {

     this.WriteLine(" Hello World at {0}",

     ToStringHelper.ToStringWithCulture(DateTime.Now ));

 }

 private void GoodBye()

 {

     this.WriteLine ("Bye Bye World at {0}",

     ToStringHelper.ToStringWithCulture(DateTime.Now ));

 }

#>

Feature Methods Written using WriteLine

Depending on what you are trying to output, this may be much easier to read than adding the text blocks and expression blocks that we originally had.

Error Reporting

You will often encounter conditions that you want to warn the developer using the template about, or error conditions that you need to complain about. This is where the Error and Warning functions come in handy.

To showcase this, let’s go back to the culture.tt template that we created earlier. Change the template code block to look like this:

<#

     var cultures = CultureInfo.GetCultures(CultureTypes.SpecificCultures);

     Warning("There were " + cultures.Count() + " cultures returned");

     foreach (var culture in cultures )

      {

#>

// <#= culture.DisplayName #>

template debug="false" hostspecific="false" language="C#" culture="<#=culture.Name #>"

           

<#

     }

#>

Code Blocking Showing the Warning Method

When you run this template, you will get a warning that there were 524 cultures returned.

Output from the Call to the Warning Function

Formatting Functions

We can also take advantage of the output helpers to make this template more readable.

<#

     var cultures = CultureInfo.GetCultures 

            (CultureTypes.SpecificCultures);

     Warning("There were " + cultures.Count() + " cultures returned");

     foreach (var culture in cultures )

      {

         WriteLine ("// " + culture.DisplayName);

         WriteLine (" template debug=\"false\" hostspecific=\"false\" language=\"C#\" culture=\"{0}\" ",  culture.Name );

     }

#>

Culture Template Rewritten to use Helper Functions to Improve Formatting

A Simple Example

Now let’s put all the pieces we have learned together into something useful.

Let’s load the config file and find the appSettings. We will then create a class that exposes a property for each appSetting that we find.

Start by adding a new text template and naming it appSettings.tt.

First we have a couple of non-T4 related tasks to do. Let’s start by finding the config file. Depending on the type of project you use this template in, it could be an app.config or a web.config. The first thing we need to do is find the template and then look in the root folder for the containing project for both an app.Config and a web.Config. If we don’t find one, we want to log an error.

Once we find the config file, we will load it as an XmlDocument and parse it to find the elements under appSettings. We will then output a read only property for each element found that will return the current value from the config file.

To find the config file, we will use the EnvDte object. “Dte” stands for Developer Tool Extensibility. We will touch on this object briefly here, and explore it more thoroughly in Chapter 8.

Let’s start with the FindConfigFile method. Add the following code to the bottom of the appSettings.tt file.

<#+

public string FindConfigFile ()

{

 var visualStudio = (this.Host as

 IServiceProvider).GetService(typeof(EnvDTE.DTE))

 as EnvDTE.DTE;

 var project =

 visualStudio.Solution.FindProjectItem

 (this.Host.TemplateFile).ContainingProject

   as EnvDTE.Project;

 foreach (EnvDTE.ProjectItem item in project.ProjectItems)

 {

    if (item.FileNames[0].EndsWith(".config"))

    {

       return item.FileNames[0];

    }

  }

  return "";

}

#>

FindConfigFile Method

This method will return the full path to either the web.config or the app.config, or an empty string if it cycles through every item in the project without finding a config file.

Now that we can get to the config file, we are ready to parse. First off, we will log an error if we don’t find the config file, and then we are ready to parse the file that we did find.

Add the following code to the template above the feature block.

<#@ template debug="true" hostspecific="true" language="C#" #>

<#@ assembly name="System.Core" #>

<#@ assembly name="EnvDte" #>

<#@ assembly name="System.Xml"#>

<#@ output extension=".cs" #>

<#@ import namespace="System.Xml" #>

using System;

namespace book

{

 public class AppSettings

 {

<#

 PushIndent (" ");

 var xml = new XmlDocument();

 var configFile = FindConfigFile();

 if (string.IsNullOrEmpty(configFile))

 {

  Error("No config file found");

  return "";

 }

 xml.Load(configFile);

 var element = xml.SelectSingleNode

  ("/configuration/appSettings");

 for (int i=0; i<element.ChildNodes.Count; i++)

 {var key = element.ChildNodes[i].Attributes["key"].Value;

   var value = this.CurrentIndent

      + element.ChildNodes[i].Attributes["value"].Value.Trim();

   WriteLine(this.CurrentIndent + "public string "

      + key + "{get { return \""

      + value +"\";}}");

 }

 PopIndent();

#>

 }

}

appSettings.tt

There are a couple of key items to note in this code. This example showcases how to use the CurrentIndent and PushIndent with the PopIndent to format the generated code nicely.

We also see a great example of using the Error method to log an error, as well as an explicit return from the TransformText method to halt processing.

In your project, add an app.config with the following configuration settings:

 <appSettings>

 <add key="PreserveLoginUrl" value="true" />

 <add key="ClientValidationEnabled" value="true" />

 <add key="UnobtrusiveJavaScriptEnabled" value="true" />

 </appSettings>

Sample AppSettings

This sample appSettings will generate a class that looks like this:

using System;

namespace book

{

 public class AppSettings

 {

  public string PreserveLoginUrl{get { return " true";}}

  public string ClientValidationEnabled{get { return " true";}}

  public string UnobtrusiveJavaScriptEnabled{get { return " true";}}

 }

}

Output from the appSettings.tt Template

Summary

We’ve covered a lot of material in this chapter—we’ve covered the main components that go into making a text template, and explored all of the directives and attributes for these directives. This section will make a handy quick reference for the rest of this book.

We also explored the various sections of a template, paying close attention to what goes into text blocks, expression blocks, code blocks, and feature blocks. We explored the different roles that each of these blocks play, and how they relate to each other.

Especially when dealing with feature blocks, we explored what can go wrong when compiling the template. This prompted our peek behind the scenes to explore the intermediary class that T4 generates to implement the template.

Seeing this intermediary class gave us our first peek at utility functions provided by the base class for the template. We looked at utility functions to help create the output, report errors, and format the output.

Finally, we built our first useful template to generate a class that will expose a read-only property for every configuration setting defined in the appSettings of the configuration file. This also gave us our first peek at the EnvDte object that we will explore further in Chapter 4.

Now that we have explored text templates, let’s turn our attention to how text templates are different from run time templates.

Scroll To Top
Disclaimer
DISCLAIMER: Web reader is currently in beta. Please report any issues through our support system. PDF and Kindle format files are also available for download.

Previous

Next



You are one step away from downloading ebooks from the Succinctly® series premier collection!
A confirmation has been sent to your email address. Please check and confirm your email subscription to complete the download.