使用 T4 文字範本在執行階段產生文字

您可以使用 Visual Studio 執行階段文字模板,在應用程式中產生文字字串。 執行應用程式的電腦不需要有 Visual Studio。 執行階段範本有時稱為「已前置處理的文字模板」,因為在編譯時間,範本會產生在執行接電執行的程式碼。

每個範本都是文字的混合,因為它會出現在產生的字串和程式碼片段中。 程式片段會提供字串變數組件的值,同時控制條件式和重複組件。

例如,下列範本可用於建立 HTML 報表的應用程式。

<#@ template language="C#" #>
<html><body>
<h1>Sales for Previous Month</h2>
<table>
    <# for (int i = 1; i <= 10; i++)
       { #>
         <tr><td>Test name <#= i #> </td>
             <td>Test value <#= i * i #> </td> </tr>
    <# } #>
 </table>
This report is Company Confidential.
</body></html>

請注意,範本是 HTML 頁面,其中變數組件已取代為程式碼。 您可以撰寫 HTML 頁面的靜態原型,開始設計這類頁面。 然後,您可以使用程式碼取代資料表和其他變數組件,以產生每次情況都不同的內容。

在應用程式中使用範本可讓您更輕鬆地查看輸出的最終形式,例如,一連串的寫入陳述式。 對輸出形式進行變更會更容易且更可靠。

在任何應用程式中建立執行階段文字範本

若要建立執行階段文字範本

  1. 請在 [方案總管] 中,於專案的捷徑功能表上依序選擇 [新增]>[新項目]

  2. 在 [新增信木] 對話方塊中,選取 [執行階段文字範本]。 (在 Visual Basic 中查看 [一般項目]>[一般] 底下。)

  3. 輸入範本檔案的名稱。

    注意

    範本檔案名稱將做為產生程式碼中的類別名稱。 因此,它不應該有空格或標點符號。

  4. 選擇新增

    系統會建立副檔名為 .tt 的新檔案。 其 [自訂工具] 屬性設定為 [TextTemplatingFilePreprocessor]。 其中包含下列幾行:

    <#@ template language="C#" #>
    <#@ assembly name="System.Core" #>
    <#@ import namespace="System.Linq" #>
    <#@ import namespace="System.Text" #>
    <#@ import namespace="System.Collections.Generic" #>
    
  5. 新增 NuGet 套件 System.CodeDom 的參考。

將現有的檔案轉換成執行階段範本

建立範本的好方法是轉換輸出的現有範例。 例如,如果您的應用程式會產生 HTML 檔案,您可以從建立純文字 HTML 檔案開始。 請確定其運作正常,且其外觀正確。 然後將它納入 Visual Studio 專案,並將其轉換成範本。

若要將現有的文字檔轉換為執行階段範本

  1. 將檔案包含在 Visual Studio 專案中。 在 [方案總管] 中的專案捷徑功能表上,選擇 [加入]>[現有項目]

  2. 將檔案的 [自訂工具] 屬性設定為 [TextTemplatingFilePreprocessor]。 在 [方案總管] 中的捷徑功能表上,選擇 [屬性]

    注意

    如果已設定屬性,請確定它是 [TextTemplatingFilePreprocessor] ,而不是 [TextTemplatingFileGenerator]。 如果您包含副檔名為 .tt 的檔案,可能會發生這種情況。

  3. 將副檔名變更為 .tt。 雖然此步驟是選擇性的,但它可協助您避免在不正確的編輯器中開啟檔案。

  4. 從檔案名稱的主要部分移除任何空格或標點符號。 例如,「My Web Page.tt」是不正確的,但「MyWebPage.tt」是正確的。 檔案名稱將做為產生程式碼中的類別名稱。

  5. 在檔案開頭,插入下列這行。 如果您在 Visual Basic 專案中工作,請將「C#」取代為「VB」。

    <#@ template language="C#" #>

  6. 新增 NuGet 套件 System.CodeDom 的參考。

執行階段範本的內容

範本指示詞

保留範本的第一行,就像建立檔案時一樣:

<#@ template language="C#" #>

語言參數將取決於您專案的語言。

純文字內容

編輯 .tt 檔案,以包含您想要應用程式產生的文字。 例如:

<html><body>
<h1>Sales for January</h2>
<!-- table to be inserted here -->
This report is Company Confidential.
</body></html>

內嵌程式碼

您可以在 <##> 之間插入程式碼。 例如:

<table>
    <# for (int i = 1; i <= 10; i++)
       { #>
         <tr><td>Test name <#= i #> </td>
             <td>Test value <#= i * i #> </td> </tr>
    <# } #>
 </table>

請注意,陳述式會在 <# ... #> 之間插入,以及運算式會在 <#= ... #> 之間插入。 如需詳細資訊,請參閱撰寫 T4 文字範本

使用範本

從範本建置的程式碼

當您儲存 .tt 檔案時,會產生附屬的 .cs.vb 檔案。 若要查看 [方案總管] 中的這個檔案,請展開 [.tt] 檔案節點。 在 Visual Basic 專案中,先選擇 [方案總管] 工具列中的 [顯示所有檔案]

請注意,附屬檔案包含部分類別,其中包含稱為 TransformText() 的方法。 您可以從應用程式呼叫此方法。

在執行階段產生文字

在應用程式程式碼中,您可以使用如下所示的呼叫來產生範本的內容:

MyWebPage page = new MyWebPage();
String pageContent = page.TransformText();
System.IO.File.WriteAllText("outputPage.html", pageContent);

若要將產生的類別放在特定命名空間中,請設定文字範本檔案的 [自訂工具命名空間] 屬性。

偵錯執行階段文字範本

以與一般程式碼相同的方式偵錯及測試執行階段文字範本。

您可以在文字範本中設定中斷點。 如果您從 Visual Studio 以偵錯模式啟動應用程式,您可以逐步執行程式碼,並以一般方式評估監看運算式。

在建構函式中傳遞參數

範本通常必須從應用程式的其他部分匯入一些資料。 為了讓這一點變得簡單,範本所建置的程式碼是部分類別。 您可以在專案中的另一個檔案中,建立相同類別的另一個部分。 該檔案可以包含具有參數、屬性和函式的建構函式,這些函式可由內嵌在範本中的程式碼和應用程式的其餘部分存取。

例如,您可以建立個別的 [MyWebPageCode.cs] 檔案:

partial class MyWebPage
{
    private MyData m_data;
    public MyWebPage(MyData data) { this.m_data = data; }}

在範本檔案 [MyWebPage.tt]中,您可以撰寫:

<h2>Sales figures</h2>
<table>
<# foreach (MyDataItem item in m_data.Items)
   // m_data is declared in MyWebPageCode.cs
   { #>
      <tr><td> <#= item.Name #> </td>
          <td> <#= item.Value #> </td></tr>
<# } // end of foreach
#>
</table>

若要在應用程式中使用此範本:

MyData data = ...;
MyWebPage page = new MyWebPage(data);
String pageContent = page.TransformText();
System.IO.File.WriteAllText("outputPage.html", pageContent);

Visual Basic 中的建構函式參數

在 Visual Basic 中,個別檔案 [MyWebPageCode.vb] 包含:

Namespace My.Templates
  Partial Public Class MyWebPage
    Private m_data As MyData
    Public Sub New(ByVal data As MyData)
      m_data = data
    End Sub
  End Class
End Namespace

範本檔案可能包含:

<#@ template language="VB" #>
<html><body>
<h1>Sales for January</h2>
<table>
<#
    For Each item In m_data.Items
#>
    <tr><td>Test name <#= item.Name #> </td>
      <td>Test value <#= item.Value #> </td></tr>
<#
    Next
#>
</table>
This report is Company Confidential.
</body></html>

範本可以藉由在建構函式中傳遞參數來叫用:

Dim data = New My.Templates.MyData
    ' Add data values here ....
Dim page = New My.Templates.MyWebPage(data)
Dim pageContent = page.TransformText()
System.IO.File.WriteAllText("outputPage.html", pageContent)

在範本屬性中傳遞資料

將資料傳遞至範本的替代方式是將公用屬性新增至部分類別定義中的樣板類別。 您的應用程式可以在叫用 TransformText() 之前設定屬性。

您也可以在部分定義中將欄位新增至範本類別。 這可讓您在範本的後續執行之間傳遞資料。

針對程式碼使用部分類別

許多開發人員偏好避免在範本中撰寫大型程式碼主體。 相反地,您可以在與範本檔案同名的部分類別中定義方法。 從範本呼叫這些方法。 如此一來,範本會更清楚地顯示目標輸出字串的外觀。 有關結果外觀的討論可以與建立其顯示之資料的邏輯區隔。

元件和參考

如果您想要讓範本程式碼參考 .NET 或其他元件,例如 [System.Xml.dll],請以一般方式將它新增至專案的 [參考]

如果您想要以與 using 陳述式相同的方式匯入命名空間,您可以使用 import 指示詞來執行此動作:

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

這些指示詞必須放在檔案的開頭,緊接在 <#@template 指示詞之後。

共用內容

如果您有數個範本之間共用的文字,您可以將它放在個別的檔案中,並將它包含在應該出現的每個檔案中:

<#@include file="CommonHeader.txt" #>

包含的內容可以包含程式碼和純文字的任何混合,也可以包含其他 include 指示詞和其他指示詞。

include 指示詞可以在範本檔案或內含檔案的文字內的任何位置使用。

執行階段文字範本之間的繼承

您可以撰寫基底類別範本,以在執行階段範本之間共用內容,這可以是抽象的。 使用 <@#template#> 指示詞的 inherits 參數來參考另一個執行階段範本類別。

繼承模式:基底方法中的片段

在下列範例中使用的模式中,請注意下列幾點:

  • 基底類別 SharedFragments 會定義類別功能區塊 <#+ ... #> 內的方法。

  • 基底類別不包含任意文字。 相反地,其所有文字區塊都會發生在類別功能方法內。

  • 衍生類別會叫用 SharedFragments 中定義的方法。

  • 應用程式會呼叫衍生類別的 TextTransform() 方法,但不會轉換基底類別 SharedFragments

  • 基底和衍生類別都是執行階段文字範本; 也就是說, [自訂工具] 屬性會設定為 [TextTemplatingFilePreprocessor]

SharedFragments.tt:

<#@ template language="C#" #>
<#+
protected void SharedText(int n)
{
#>
   Shared Text <#= n #>
<#+
}
// Insert more methods here if required.
#>

MyTextTemplate1.tt:

<#@ template language="C#" inherits="SharedFragments" #>
begin 1
   <# SharedText(2); #>
end 1

MyProgram.cs:

...
MyTextTemplate1 t1  = new MyTextTemplate1();
string result = t1.TransformText();
Console.WriteLine(result);

產生的輸出:

begin 1
    Shared Text 2
end 1

繼承模式:基底本文中的文字

在這個使用範本繼承的替代方法中,基本範本中會定義大部分的文字。 衍生範本會提供適合基底內容的資料和文字片段。

AbstractBaseTemplate1.tt:

<#@ template language="C#" #>

Here is the description for this derived template:
  <#= this.Description #>

Here is the fragment specific to this derived template:
<#
  this.PushIndent("  ");
  SpecificFragment(42);
  this.PopIndent();
#>
End of common template.
<#+
  // State set by derived class before calling TextTransform:
  protected string Description = "";
  // 'abstract' method to be defined in derived classes:
  protected virtual void SpecificFragment(int n) { }
#>

DerivedTemplate1.tt:

<#@ template language="C#" inherits="AbstractBaseTemplate1" #>
<#
  // Set the base template properties:
  base.Description = "Description for this derived class";

  // Run the base template:
  base.TransformText();

#>
End material for DerivedTemplate1.

<#+
// Provide a fragment specific to this derived template:

protected override void SpecificFragment(int n)
{
#>
   Specific to DerivedTemplate1 : <#= n #>
<#+
}
#>

應用程式程式碼:

...
DerivedTemplate1 t1 = new DerivedTemplate1();
string result = t1.TransformText();
Console.WriteLine(result);

產生的輸出:

Here is the description for this derived template:
  Description for this derived class

Here is the fragment specific to this derived template:
     Specific to DerivedTemplate1 : 42
End of common template.
End material for DerivedTemplate1.

設計階段範本:如果您想要使用範本來產生成為應用程式一部分的程式碼,請參閱使用 T4 文字範本在設計階段產生程式碼

執行階段範本可用於編譯時間決定範本及其內容的任何應用程式中。 但是,如果您想要撰寫從執行階段變更之範本產生文字的 Visual Studio 延伸模組,請參閱 叫用 VS 延伸模組中的文字轉換