14.7 Runtime Text Template

Text Template Transformation是一个强大的技术,不应该局限于设计活动。Visual Studio 2015很容易利用T4引擎创建自己的文本模板生成器,在自己的项目中使用。这些生成器称为Runtime Text Template(运行时文本模板)。

要创建新的Runtime Text Template,可以打开Add New Item对话框,选择General页面,再从项列表中选择Runtime Text Template。新建文件的扩展名也是.tt,与正常的T4模板文件一样,它包含大量T4指令。

C#

      <#@ template language="C#" #>
      <#@ assembly name="System.Core" #>
      <#@ import namespace="System.Linq" #>
      <#@ import namespace="System.Text" #>
      <#@ import namespace="System.Collections.Generic" #>

VB

      <#@ template language="VB" #>
      <#@ assembly name="System.Core" #>
      <#@ import namespace="System.Linq" #>
      <#@ import namespace="System.Text" #>
      <#@ import namespace="System.Collections.Generic" #>

注意,这里没有output指令。生成的文件与模板文件同名,但用.vb或.cs (取决于项目语言)替代.tt。保存这个文件时,会生成如下所示的输出文件。

C#

      // ----------------------------------------------------------------------------
      // <auto-generated>
      //     This code was generated by a tool.
      //     Runtime Version: 14.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
      {
        using System;
        using System.Linq;
        using System.Text;
        using System.Collections.Generic;
        public partial class NewTemplate
        {
          // region Fields
          // region Properties
          // region Transform-time helpers
          public virtual string TransformText()
          {
            return this.GenerationEnvironment.ToString();
          }
        }
      }

VB

      Imports System
      Imports System.Linq
      Imports System.Text
      Imports System.Collections.Generic
      '------------------------------------------------------------------------------
      '<auto-generated>
      '    This code was generated by a tool.
      '    Runtime Version: 14.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

这非常类似于T4为普通模板生成的临时代码文件。生成的类现在仅是项目中的一个类,这意味着可以实例化它,填充其属性,对它调用TransformText()方法。

与正常的Text Template一样,Visual Studio也使用Custom Tool来生成Runtime Text Template的输出文件。Runtime Text Template不使用TextTemplatingFileGenerator定制工具,而使用TextTemplatingFilePreprocessor定制工具进行转换,该工具会在项目中添加代码生成器类,而不是添加执行代码生成器的结果。

14.7.1 使用Runtime Text Template

为了演示如何在自己的代码中使用Runtime Text Template,本节介绍一个简单的例子。该项目需要在新会员加入AdventureWorks Cycle俱乐部时,给他们发送一封标准的欢迎信。下面的Runtime Text Template包含要生成的基本信件。

C#

      <#@ template language="C#" #>
      Dear <#=Member.Salutation#> <#=Member.Surname#>,
        Welcome to our Bike Club!
      Regards,
      The AdventureWorks Team
      <#= Member.DateJoined.ToShortDateString() #>
      <#+ public ClubMember Member { get; set; } #>

VB

      <#@ template language="VB" #>
      Dear <#=Member.Salutation#> <#=Member.Surname#>,
        Welcome to our Bike Club!
      Regards,
      The AdventureWorks Team
      <#= Member.DateJoined.ToShortDateString() #>
      <#+ Public Member as ClubMember #>

这个文件生成了一个类WelcomeLetter,它依赖下面的简单数据类,该类通过其Member属性传递给模板。

C#

      public class ClubMember
      {
        public string Salutation { get; set; }
        public string Surname { get; set; }
        public DateTime DateJoined { get; set; }
      }

VB

      Public Class ClubMember
          Public Surname As String
          Public Salutation As String
          Public DateJoined As Date
      End Class

最后,为创建信件,需要实例化一个WelcomeLetter对象,把Member属性设置为一个ClubMember对象,并调用TransformText()方法。

C#

      // ...
      var member = new ClubMember
      {
        Surname = "Fry",
        Salutation = "Mr",
        DateJoined = DateTime.Today
      };
      var letterGenerator = new WelcomeLetter();
      letterGenerator.Member = member;
      var letter = letterGenerator.TransformText();
      // ...

VB

      ' ...
      Dim NewMember As New ClubMember
      With NewMember
        .Surname = "Fry"
        .Salutation = "Mr"
        .DateJoined = Date.Today
      End With
      Dim LetterGenerator As New WelcomeLetter
      LetterGenerator.Member = NewMember
      Dim Letter = LetterGenerator.TransformText()
      ' ...

这看起来有点笨拙,但WelcomeLetter是一个部分类,所以可以把API修改为自己需要的内容。通常把生成器的构造函数设置为私有,再创建几个静态方法来处理生成器实例的创建和使用。

C#

      public partial class WelcomeLetter
      {
        private WelcomeLetter() { }
        public static string Create(ClubMember member)
        {
          return new WelcomeLetter { Member = member }.TransformText();
        }
      }

VB

      Namespace My.Templates
        Partial Public Class WelcomeLetter
          Private Sub New()
          End Sub
          Public Shared Function Create(ByVal Member As ClubMember) As String
            Dim LetterGenerator As New WelcomeLetter()
            LetterGenerator.Member = Member
            Return LetterGenerator.TransformText()
          End Function
        End Class
      End Namespace

生成器包含一个StringBuilder,在执行TransformText时,在内部使用该String-Builder建立输入。运行TransformText方法时,不清理这个StringBuilder,这意味着每次运行它时,结果都会追加到上一次执行的结果上。因此上面的Create方法每次会创建一个新的WelcomeLetter对象,而不是把一个对象保存在static (Shared)变量中,并重用它。

14.7.2 Runtime Text Template与标准T4模板的区别

除了在项目中包含生成过程的那些方面之外,Runtime Text Template和标准的T4模板之间还存在其他几个重要区别。首先,Runtime Text Template是完全独立的类,它们默认没有继承基类,因此不依赖Visual Studio来执行。生成器类的TransformText()方法也不在try/catch块中运行,所以在执行生成器时,需要自己捕获和处理错误。

在Runtime Text Template中,并不是所有的T4指令都有效,T4指令的某些特性也不再那么有效。下面进行总结。

template指令仍然可以使用,但并不是所有特性都有效。culture和language特性得到了完全的支持,language特性必须匹配所包含的语言,否则就不能编译生成器类。debug特性被忽略,因为可以像处理其他类那样,通过设置项目配置来控制生成器类的调试状态。

inherits特性得到了支持,并且对生成的类有重要影响。如果没有指定基类,那么生成的文件将是完全独立的,包含所有辅助函数(如Write和Error)的实现代码。如果指定了基类,则基类就负责指定这些实现代码,生成的类将依赖这些实现代码执行生成工作。

hostspecific特性得到了支持,并在生成器类上生成一个Host属性。这个属性的类型是Microsoft. VisualStudio.TextTemplating.ITextTemplatingEngineHost,该类型位于Microsoft.VisualStudio.TextTemplating.10.0程序集中。编程人员应负责在项目中添加对这个程序集的引用,并提供一个合适类型的成员,之后才能调用TransformText方法。

import指令能正常工作。引用的名称空间使用C#中的using语句和VB中的Import语句,包含在生成器代码文件中。include指令也得到了完全的支持。

output指令和assembly指令被忽略。要给模板添加程序集,只需要像往常那样给项目添加一个引用。输出的文件名根据模板文件名和所选的语言来选择。

最后,可以在模板文件的Properties窗口中设置生成器类的名称空间,如图14-5所示。名称空间一般基于项目默认的名称空间和模板文件在项目文件夹结构中的位置。

图14-5