Редактиране

Споделяне чрез


Design-Time Code Generation by using T4 Text Templates

Design-time T4 text templates let you generate program code and other files in your Visual Studio project. Typically, you write the templates so that they vary the code that they generate according to data from a model. A model is a file or database that contains key information about your application's requirements.

For example, you could have a model that defines a workflow, either as a table or a diagram. From the model, you can generate the software that executes the workflow. When your users' requirements change, it's easy to discuss the new workflow with the users. Regenerating the code from the workflow is more reliable than updating the code by hand.

Note

A model is a data source that describes a particular aspect of an application. It can be any form, in any kind of file or database. It doesn't have to be in any particular form, such as a UML model or Domain-Specific Language model. Typical models are in the form of tables or XML files.

You're probably already familiar with code generation. When you define resources in a .resx file in your Visual Studio solution, a set of classes and methods is generated automatically. The resources file makes it much easier and more reliable to edit the resources than it would be if you had to edit the classes and methods. With text templates, you can generate code in the same manner from a source of your own design.

A text template contains a mixture of the text that you want to generate, and program code that generates variable parts of the text. The program code allows you to repeat or conditionally omit parts of the generated text. The generated text can itself be program code that will form part of your application.

Create a Design-Time T4 Text Template

  1. Create a new Visual Studio project, or open an existing one.

  2. Add a text template file to your project and give it a name that has the extension .tt.

    To do this, in Solution Explorer, on the shortcut menu of your project, choose Add > New Item. In the Add New Item dialog box, select Text Template from the middle pane.

    Notice that the Custom Tool property of the file is TextTemplatingFileGenerator.

  3. Open the file. It will already contain the following directives:

    <#@ template hostspecific="false" language="C#" #>
    <#@ output extension=".txt" #>
    

    If you added the template to a Visual Basic project, the language attribute will be "VB".

  4. Add some text at the end of the file. For example:

    Hello, world!
    
  5. Save the file.

    You might see a Security Warning message box that asks you to confirm that you want to run the template. Click OK.

  6. In Solution Explorer, expand the template file node and you'll find a file that has the extension .txt. The file contains the text generated from the template.

    Note

    If your project is a Visual Basic project, you must click Show All Files in order to see the output file.

Regenerate the code

A template will be executed, generating the subsidiary file, in any of the following cases:

  • Edit the template and then change focus to a different Visual Studio window.

  • Save the template.

  • Click Transform All Templates in the Build menu. This will transform all the templates in the Visual Studio solution.

  • In Solution Explorer, on the shortcut menu of any file, choose Run Custom Tool. Use this method to transform a selected subset of templates.

You can also set up a Visual Studio project so that the templates are executed when the data files that they read have changed. For more information, see Regenerating the code automatically.

Generate Variable Text

Text templates let you use program code to vary the content of the generated file.

  1. Change the content of the .tt file:

    <#@ template hostspecific="false" language="C#" #>
    <#@ output extension=".txt" #>
    <#int top = 10;
    
    for (int i = 0; i<=top; i++)
    { #>
       The square of <#= i #> is <#= i*i #>
    <# } #>
    
  2. Save the .tt file, and inspect the generated .txt file again. It lists the squares of the numbers from 0 to 10.

    Notice that statements are enclosed within <#...#>, and single expressions within <#=...#>. For more information, see Writing a T4 Text Template.

    If you write the generating code in Visual Basic, the template directive should contain language="VB". "C#" is the default.

Debugging a Design-Time T4 Text Template

To debug a text template:

  • Insert debug="true" into the template directive. For example:

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

  • Set breakpoints in the template, in the same way that you would for ordinary code.

  • Choose Debug T4 Template from the shortcut menu of the text template file in Solution Explorer.

    The template runs and stops at the breakpoints. You can examine variables and step through the code in the usual way.

Tip

debug="true" makes the generated code map more accurately to the text template, by inserting more line numbering directives into the generated code. If you leave it out, breakpoints might stop the run in the wrong state.

But you can leave the clause in the template directive even when you're not debugging. This causes only a very small drop in performance.

Generating Code or Resources for Your Solution

You can generate program files that vary, depending on a model. A model is an input such as a database, configuration file, UML model, DSL model, or other source. You usually generate several program files from the same model. To achieve this, you create a template file for each generated program file, and have all the templates read the same model.

To generate program code or resources

  1. Change the output directive to generate a file of the appropriate type, such as .cs, .vb, .resx, or .xml.

  2. Insert code that will generate the solution code that you require. For example, if you want to generate three integer field declarations in a class:

    
    <#@ template debug="false" hostspecific="false" language="C#" #>
    <#@ output extension=".cs" #>
    <# var properties = new string [] {"P1", "P2", "P3"}; #>
    // This is generated code:
    class MyGeneratedClass {
    <# // This code runs in the text template:
      foreach (string propertyName in properties)  { #>
      // Generated code:
      private int <#= propertyName #> = 0;
    <# } #>
    }
    
  3. Save the file and inspect the generated file, which now contains the following code:

    class MyGeneratedClass {
      private int P1 = 0;
      private int P2 = 0;
      private int P3 = 0;
    }
    

Generating Code and Generated Text

When you generate program code, it's most important to avoid confusing the generating code that executes in your template, and the resulting generated code that becomes part of your solution. The two languages don't have to be the same.

The previous example has two versions. In one version, the generating code is in C#. In the other version, the generating code is Visual Basic. But the text generated by both of them is the same, and it's a C# class.

In the same way, you could use a Visual C# template to generate code in any language. The generated text doesn't have to be in any particular language, and it doesn't have to be program code.

Structuring text templates

As a matter of good practice, we tend to separate the template code into two parts:

  • A configuration or data-gathering part, which sets values in variables, but doesn't contain text blocks. In the previous example, this part is the initialization of properties.

    This is sometimes called the "model" section, because it constructs an in-store model, and typically reads a model file.

  • The text-generation part (foreach(...){...} in the example), which uses the values of the variables.

    This isn't a necessary separation, but it's a style which makes it easier to read the template by reducing the complexity of the part that includes text.

Reading files or other sources

To access a model file or database, your template code can use assemblies such as System.XML. To gain access to these assemblies, you must insert directives such as these:

<#@ assembly name="System.Xml.dll" #>
<#@ import namespace="System.Xml" #>
<#@ import namespace="System.IO" #>

The assembly directive makes the specified assembly available to your template code, in the same manner as the References section of a Visual Studio project. You don't need to include a reference to System.dll, which is referenced automatically. The import directive lets you use types without using their fully qualified names, in the same manner as the using directive in an ordinary program file.

For example, after importing System.IO, you could write:


<# var properties = File.ReadLines("C:\\propertyList.txt");#>
...
<# foreach (string propertyName in properties) { #>
...

Opening a file with a relative pathname

To load a file from a location relative to the text template, you can use this.Host.ResolvePath(). To use this.Host, you must set hostspecific="true" in the template:

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

Then you can write, for example:

<# string filename = this.Host.ResolvePath("filename.txt");
  string [] properties = File.ReadLines(filename);
#>
...
<#  foreach (string propertyName in properties { #>
...

You can also use this.Host.TemplateFile, which identifies the name of the current template file.

The type of this.Host (in VB, Me.Host) is Microsoft.VisualStudio.TextTemplating.ITextTemplatingEngineHost.

Getting data from Visual Studio

To use services provided in Visual Studio, set the hostspecific attribute and load the EnvDTE assembly. Import Microsoft.VisualStudio.TextTemplating, which contains the GetCOMService() extension method. You can then use IServiceProvider.GetCOMService() to access DTE and other services. For example:

<#@ template hostspecific="true" language="C#" #>
<#@ output extension=".txt" #>
<#@ assembly name="EnvDTE" #>
<#@ import namespace="Microsoft.VisualStudio.TextTemplating" #>
<#
  IServiceProvider serviceProvider = (IServiceProvider)this.Host;
  EnvDTE.DTE dte = (EnvDTE.DTE) serviceProvider.GetCOMService(typeof(EnvDTE.DTE));
#>

Number of projects in this VS solution:  <#= dte.Solution.Projects.Count #>

Tip

A text template runs in its own app domain, and services are accessed by marshaling. In this circumstance, GetCOMService() is more reliable than GetService().

Regenerating the code automatically

Typically, several files in a Visual Studio solution are generated with one input model. Each file is generated from its own template, but the templates all refer to the same model.

If the source model changes, you should re-run all the templates in the solution. To do this manually, choose Transform All Templates on the Build menu.

If you have installed the Visual Studio Modeling SDK, you can have all the templates transformed automatically whenever you perform a build. To do this, edit your project file (.csproj or .vbproj) in a text editor and add the following lines near the end of the file, after any other <import> statements. In an SDK-style project, it can go anywhere in the project file.

Note

The Text Template Transformation SDK and the Visual Studio Modeling SDK are installed automatically when you install specific features of Visual Studio.

<Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v17.0\TextTemplating\Microsoft.TextTemplating.targets" />
<PropertyGroup>
   <TransformOnBuild>true</TransformOnBuild>
   <!-- Other properties can be inserted here -->
</PropertyGroup>

For more information, see Code Generation in a Build Process.

Error reporting

To place error and warning messages in the Visual Studio error window, you can use these methods:

Error("An error message");
Warning("A warning message");

Converting an existing file to a template

A useful feature of templates is that they look very much like the files that they generate, together with some inserted program code. This suggests a useful method of creating a template. First create an ordinary file as a prototype, such as a Visual C# file, and then gradually introduce generation code that varies the resulting file.

To convert an existing file to a design-time template

  1. To your Visual Studio project, add a file of the type that you want to generate, such as a .cs, .vb, or .resx file.

  2. Test the new file to make sure that it works.

  3. In Solution Explorer, change the file name extension to .tt.

  4. Verify the following properties of the .tt file:

    Property Setting
    Custom Tool = TextTemplatingFileGenerator
    Build Action = None
  5. Insert the following lines at the beginning of the file:

    <#@ template debug="false" hostspecific="false" language="C#" #>
    <#@ output extension=".cs" #>
    

    If you want to write the generating code of your template in Visual Basic, set the language attribute to "VB" instead of "C#".

    Set the extension attribute to the file name extension for the type of file that you want to generate, for example .cs, .resx, or .xml.

  6. Save the file.

    A subsidiary file is created, with the specified extension. Its properties are correct for the type of file. For example, the Build Action property of a .cs file would be Compile.

    Verify that the generated file contains the same content as the original file.

  7. Identify a part of the file that you want to vary. For example, a part that appears only under certain conditions, or a part that is repeated, or where the specific values vary. Insert generating code. Save the file and verify that the subsidiary file is correctly generated. Repeat this step.

Guidelines for Code Generation

Please see Guidelines for Writing T4 Text Templates.