從文字範本存取模型

您可以使用文字範本建立以特定領域語言模型為基礎的報告檔案、原始程式碼檔案和其他文字檔。 如需文字範本的詳細資訊,請參閱 程式碼產生和 T4文字範本。 當您偵錯 DSL 時,文字範本將會在實驗模式中運作,而且也會在您已部署 DSL 的電腦上運作。

注意

當您建立 DSL 解決方案時,會在偵錯專案中產生樣本文字範本 *.tt 檔案。 當您變更網域類別的名稱時,這些範本將無法再運作。 不過,它們包含您需要的基本指示詞,並提供您可以更新以符合 DSL 的範例。

若要從文字範本存取模型:

  • 將範本指示詞的繼承屬性設定為 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#,但您可以產生任何類型的文字。 您也可以在 Visual Basic 中撰寫程式碼,方法是將 屬性 language="VB" 新增至 template 指示詞。

  • 若要對範本進行偵錯,請將 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'" #>

請注意:

  1. filenamevalidation 參數會以「;」分隔,而且不能有其他分隔符號或空格。

  2. 驗證類別清單會決定要執行的驗證方法。 多個類別應該以「|」分隔,而且不能有其他分隔符號或空格。

    如果找到錯誤,則會在錯誤視窗中報告錯誤,而結果檔案將會包含錯誤訊息。

從文字範本存取多個模型

注意

這個方法可讓您在相同的範本中讀取多個模型,但不支援 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)
...
#>

動態載入模型

如果您想要在執行階段判斷要載入的模型,您可以在程式碼中動態載入模型檔案,而不是使用 DSL 特定的指示詞。

不過,DSL 特定指示詞的其中一個函式是匯入 DSL 命名空間,讓範本程式碼可以使用該 DSL 中定義的網域類別。 因為您不是使用指示詞,因此您必須為可能載入的所有模型新增 <組件><匯入> 指示詞。 如果可能載入的不同模型都是相同 DSL 的執行個體,那麽這會很容易。

若要載入檔案,最有效的方法是使用 Visual Studio ModelBus。 在一般案例中,您的文字範本會使用 DSL 特定指示詞,以一般的方式載入第一個模型。 該模型會包含另一個模型的 ModelBus 參考。 您可以使用 ModelBus 開啟參考的模型並存取特定元素。 如需詳細資訊,請參閱 在文字範本中使用 Visual Studio ModelBus

在較不平常的案例中,您可能想要開啟只有檔案名稱的模型檔案,而該檔案可能不在目前的 Visual Studio 專案中。 在此情況下,您可以使用 [如何: 在程式碼中開啟檔案的模型] 中所述的技術來開啟檔案。

從範本產生多個檔案

如果您想要產生數個檔案,例如,若要為模型中的每個元素產生個別的檔案,有幾個可能的方法。 根據預設,每個範本檔案只會產生一個檔案。

分割長檔案

您會透過這個方法使用範本來產生單一檔案,並以分隔符號分隔。 然後,您會將檔案分割成其組件。 有兩個範本,一個用來產生單一檔案,另一個用來分割它。

LoopTemplate.t4 會產生長單一檔案。 請注意,其副檔名為「.t4」,因為當您按一下 [轉換所有範本] 時,不應該直接處理它。 此範本會採用參數,指定分隔區段的分隔符號字串:

<#@ template inherits="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]);
  }
#>