添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

适用范围: yes Visual Studio no Visual Studio for Mac no Visual Studio Code

使用文本模板,可以创建基于域特定语言模型的报表文件、源代码文件和其他文本文件。 有关文本模板的基本信息,请参阅 代码生成和 T4 文本模板 。 调试 DSL 时,文本模板将在实验模式下工作,并且还可在已部署 DSL 的计算机上工作。

创建 DSL 解决方案时,调试项目中会生成示例文本模板 *.tt 文件。 更改域类的名称时,这些模板将不再工作。 尽管如此,它们包括你需要的基本指令,并提供可更新以匹配 DSL 的示例。

从文本模板访问模型:

  • 将模板指令的 inherit 属性设置为 Microsoft.VisualStudio.TextTemplating.VSHost.ModelingTextTransformation 。 这将提供对 Store 的访问。

  • 为要访问的 DSL 指定指令处理器。 这会加载 DSL 程序集,以便在文本模板的代码中使用其域类、属性和关系。 它还会加载你指定的模型文件。

    从 DSL 最小语言模板创建新 Visual Studio 解决方案时,将在调试项目中创建类似于以下示例的 .tt 文件。

    <#@ template inherits="Microsoft.VisualStudio.TextTemplating.VSHost.ModelingTextTransformation" #>
    <#@ output extension=".txt" #>
    <#@ MyLanguage processor="MyLanguageDirectiveProcessor" requires="fileName='Sample.myDsl1'" #>
    This text will be output directly.
    This is the name of the model: <#= this.ModelRoot.Name #>
    Here is a list of elements in the model:
      // When you change the DSL Definition, some of the code below may not work.
      foreach (ExampleElement element in this.ExampleModel.Elements)
    <#= element.Name #>
    

    请注意有关此模板的以下几点:

  • 模板可以使用在 DSL 定义中定义的域类、属性和关系。

  • 模板加载你在 requires 属性中指定的模型文件。

  • this 中的属性包含根元素。 代码可以在其中导航到模型的其他元素。 属性名称通常与 DSL 的根域类相同。 在此示例中,它是 this.ExampleModel

  • 虽然编写代码片段的语言是 C#,但你可以生成任何类型的文本。 或者,可以通过将 language="VB" 属性添加到 template 指令,在 Visual Basic 中编写代码。

  • 若要调试模板,请将 debug="true" 添加到 template 指令。 如果发生异常,则模板将在 Visual Studio 的另一个实例中打开。 如果希望在代码中的特定点中断调试器,请插入语句 System.Diagnostics.Debugger.Break();

    有关详细信息,请参阅调试 T4 文本模板

    关于 DSL 指令处理器

    模板可以使用在 DSL 定义中定义的域类。 这是通过通常出现在模板开头附近的指令来进行的。 在上面的示例中,它如下所示。

    <#@ MyLanguage processor="MyLanguageDirectiveProcessor" requires="fileName='Sample.myDsl1'" #>
    

    指令名称(在本例中为 MyLanguage)派生自 DSL 名称。 它调用作为 DSL 的一部分生成的指令处理器。 可以在 Dsl\GeneratedCode\DirectiveProcessor.cs 中找到它的源代码。

    DSL 指令处理器执行两个主要任务:

  • 它有效地将程序集和导入指令插入到引用 DSL 的模板中。 这允许你在模板代码中使用域类。

  • 它加载你在 requires 参数中指定的文件,并在 this 中设置一个引用所加载模型的根元素的属性。

    在运行模板之前验证模型

    可以在执行模板之前对模型进行验证。

    <#@ MyLanguage processor="MyLanguageDirectiveProcessor" requires="fileName='Sample.myDsl1';validation='open|load|save|menu'" #>
    
  • filenamevalidation 参数用“;”分隔,并且不能有其他分隔符或空格。

  • 验证类别的列表确定将执行哪些验证方法。 应该用“|”分隔多个类别,并且不得有其他分隔符或空格。

    如果发现错误,将在错误窗口中报告该错误,结果文件将包含错误消息。

    从文本模板访问多个模型

    此方法使你可以读取同一模板中的多个模型,但不支持 ModelBus 引用。 若要读取由 ModelBus 引用链接的模型,请参阅在文本模板中使用 Visual Studio ModelBus

    如果要从同一文本模板访问多个模型,则必须为每个模型调用一次生成的指令处理器。 必须在 requires 参数中指定每个模型的文件名。 必须在 provides 参数中指定要用于根域类的名称。 必须为每个指令调用中的 provides 参数指定不同值。 例如,假设有三个名为 Library.xyz、School.xyz 和 Work.xyz 的模型文件。 若要从同一文本模板访问它们,必须编写三个类似于下面的指令调用。

    <#@ ExampleModel processor="<YourLanguageName>DirectiveProcessor" requires="fileName='Library.xyz'" provides="ExampleModel=LibraryModel" #>
    <#@ ExampleModel processor="<YourLanguageName>DirectiveProcessor" requires="fileName='School.xyz'" provides="ExampleModel=SchoolModel" #>
    <#@ ExampleModel processor="<YourLanguageName>DirectiveProcessor" requires="fileName='Work.xyz'" provides="ExampleModel=WorkModel" #>
    

    此代码示例适用于基于最小语言解决方案模板的语言。

    若要访问文本模板中的模型,你现在可以编写类似于以下示例代码的代码。

    foreach (ExampleElement element in this.LibraryModel.Elements) foreach (ExampleElement element in this.SchoolModel.Elements) foreach (ExampleElement element in this.WorkModel.Elements) For Each element As ExampleElement In Me.LibraryModel.Elements For Each element As ExampleElement In Me.SchoolModel.Elements For Each element As ExampleElement In Me.WorkModel.Elements

    动态加载模型

    如果要在运行时确定要加载的模型,可以在程序代码中动态加载模型文件,而不是使用特定于 DSL 的指令。

    但是,特定于 DSL 的指令的功能之一是导入 DSL 命名空间,以便模板代码可以使用该 DSL 中定义的域类。 由于未使用指令,因此必须为可能加载的所有模型添加 <assembly> 和 <import> 指令。 如果可以加载的不同模型都是同一 DSL 的所有实例,那么这很简单。

    若要加载该文件,最有效的方法是使用 Visual Studio ModelBus。 在典型场景中,文本模板将使用特定于 DSL 的指令以常规方式加载第一个模型。 该模型将包含对另一个模型的 ModelBus 引用。 可以使用 ModelBus 打开引用的模型并访问特定元素。 有关详细信息,请参阅在文本模板中使用 Visual Studio ModelBus

    在不太常见的情况下,你可能想要打开一个只具有文件名且不在当前 Visual Studio 项目中的模型文件。 在这种情况下,可以使用操作说明:在程序代码中从文件打开模型中所述的方法来打开文件。

    从模板生成多个文件

    如果要生成几个文件(例如,为模型中的每个元素生成一个单独的文件),则有几种可能的方法。 默认情况下,每个模板文件仅生成一个文件。

    拆分长文件

    在此方法中,你将使用一个模板生成单个文件,并用分隔符分隔。 然后,将文件拆分为其各个部分。 有两个模板,一个用于生成单个文件,另一个用于拆分。

    LoopTemplate.t4 生成单个长文件。 请注意,其文件扩展名为“.t4”,因为当你单击“转换所有模板”时不应直接处理。 此模板采用参数,该参数指定分隔段的分隔符字符串:

    <#@ template ninherits="Microsoft.VisualStudio.TextTemplating.VSHost.ModelingTextTransformation" #>
    <#@ parameter name="delimiter" type="System.String" #>
    <#@ output extension=".txt" #>
    <#@ MyDSL processor="MyDSLDirectiveProcessor" requires="fileName='SampleModel.mydsl1';validation='open|load|save|menu'" #>
      // Create a file segment for each element:
      foreach (ExampleElement element in this.ExampleModel.Elements)
        // First item is the delimiter:
    <#= string.Format(delimiter, element.Id) #>
       Element: <#= element.Name #>
       // Here you generate more content derived from the element.
    

    LoopSplitter.tt 调用 LoopTemplate.t4,然后将生成的文件拆分为其各个段。 请注意,此模板不必是建模模板,因为它不会读取模型。

    <#@ template hostspecific="true" language="C#" #>
    <#@ output extension=".txt" #>
    <#@ import namespace="Microsoft.VisualStudio.TextTemplating" #>
    <#@ import namespace="System.Runtime.Remoting.Messaging" #>
    <#@ import namespace="System.IO" #>
      // Get the local path:
      string itemTemplatePath = this.Host.ResolvePath("LoopTemplate.t4");
      string dir = Path.GetDirectoryName(itemTemplatePath);
      // Get the template for generating each file:
      string loopTemplate = File.ReadAllText(itemTemplatePath);
      Engine engine = new Engine();
      // Pass parameter to new template:
      string delimiterGuid = Guid.NewGuid().ToString();
      string delimiter = "::::" + delimiterGuid + ":::";
      CallContext.LogicalSetData("delimiter", delimiter + "{0}:::");
      string joinedFiles = engine.ProcessTemplate(loopTemplate, this.Host);
      string [] separateFiles = joinedFiles.Split(new string [] {delimiter}, StringSplitOptions.None);
      foreach (string nameAndFile in separateFiles)
         if (string.IsNullOrWhiteSpace(nameAndFile)) continue;
         string[] parts = nameAndFile.Split(new string[]{":::"}, 2, StringSplitOptions.None);
         if (parts.Length < 2) continue;
     Generate: [<#= dir #>] [<#= parts[0] #>]
         // Generate a file from this item:
         File.WriteAllText(Path.Combine(dir, parts[0] + ".txt"), parts[1]);
    
  •