從數據到代碼—基于T4的代碼生成方式

作者: Artech  來源: 博客園  發布時間: 2010-10-24 22:39  閱讀: 4258 次  推薦: 1   原文鏈接   [收藏]  

  在之前寫一篇文章《從數據到代碼》(上篇下篇)中,我通過基于CodeDOM+Custom Tool的代碼生成方式實現了將一個XML表示的消息列表轉換成了相應的C#代碼,從而達到了強類型編程的目的。實際上,我們最常用的代碼生成當時不是CodeDOM,而是T4,這是一個更為強大,并且適用范圍更廣的代碼生成技術。今天,我將相同的例子通過T4的方式再實現一次,希望為那些對T4不了解的讀者帶來一些啟示。同時這篇文章將作為后續文章的引子,在此之后,我將通過兩篇文章通過具體實例的形式講述如果在項目將T4為我所用,以達到提高開發效率和保證質量的目的。[這里有T4相關的資料][文中的例子可以從這里下載]。

  一、我們的目標是:從XML文件到C#代碼

  再次重申一下我們需要通過“代碼生成”需要達到的目的。無論對于怎么樣的應用,我們都需要維護一系列的消息。消息的類型很多,比如驗證消息、確認消息、日志消息等。我們一般會將消息儲存在一個文件或者數據庫中進行維護,并提供一些API來獲取相應的消息項。這些API一般都是基于消息的ID來獲取的,換句話說,消息獲取的方式是以一種“弱類型”的編程方式實現的。如果我們能夠根據消息存儲的內容動態地生成相應的C#或者VB.NET代碼,那么我們就能夠以一種強類型的方式來獲取相應的消息項了。

  比如說,現在我們定義了如下一個MessageEntry類型來表示一個消息條目。為了簡單,我們盡量簡化MessageEntry的定義,僅僅保留三個屬性Id、Value和Category。Category表示該消息條目所屬的類型,你可以根據具體的需要對其分類(比如根據模塊名稱或者Severity等)。Value是一個消息真實的內容,可以包含一些占位符({0},{1},…{N})。通過指定占位符對用的值,最中格式化后的文本通過Format返回。

   1: public class MessageEntry
   2: {
   3:     public string Id { get; private set; }
   4:     public string Value { get; private set; }
   5:     public string Category { get; private set; }
   6:  
   7:     public MessageEntry(string id, string value, string category)
   8:     {
   9:         this.Id         = id;
  10:         this.Value      = value;
  11:         this.Category   = category;
  12:     }
  13:     public string Format(params object[] args)
  14:     {
  15:         return string.Format(this.Value, args);
  16:     }
  17: }

  現在我們所有的消息定義在如下一個XML文件中,<message>XML元素代碼一個具體的MessageEntry,相應的屬性(Attribute)和MessageEntry的屬性(Property)相對應。

   1: <?xml version="1.0" encoding="utf-8" ?>
   2: <messages> 
   3:   <message id="MandatoryField" value="The {0} is mandatory."  category="Validation"/> 
   4:   <message id="GreaterThan" value="The {0} must be greater than {1}."  category="Validation"/> 
   5:   <message id="ReallyDelete" value="Do you really want to delete the {0}."  category="Confirmation"/>  
   6: </messages>

  在上面的XML中,定義了兩個類別(Validation和Confirmation)的三條MessageEntry。我們需要通過我們的代碼生成工具生成一個包含如下C#代碼的CS文件。

   1: public static class Messages
   2: {
   3:     public static class Validation
   4:     {
   5:         public static MessageEntry MandatoryField = new MessageEntry("MandatoryField", "The {0} is mandatory.", "Validation");
   6:         public static MessageEntry GreaterThan = new MessageEntry("GreaterThan", "The {0} must be greater than {1}.", "Validation");
   7:     }
   8:     public static class Confirmation
   9:     {
  10:         public static MessageEntry ReallyDelete = new MessageEntry("ReallyDelete", "Do you really want to delete the {0}.", "Confirmation");
  11:     }
  12: }

  那么如何通過T4的方式來實現從“數據”(XML)到“代碼”的轉換呢?在投入到這個稍微復雜的工作之前,我們先來弄個簡單的。

  二、從Hello World講起

  我們之前一直在講T4,可能還有人不知道T4到底代表什么。T4是對“Text Template Transformation Toolkit”(4個T)的簡稱。T4直接包含在VS2008和VS2010中,是一個基于文本文件轉換的工具包。T4的核心是一個基于“文本模板”的轉換引擎(以下簡稱T4引擎),我們可以通過它生成一切類型的文本型文件,比如我們常用的代碼文件類型包括:C#、VB.NET、T-SQL、XML甚至是配置文件等。

  對于需要通過T4來進行代碼生成工作的我們來說,需要做的僅僅是根據轉換源(Transformation Source),比如數據表、XML等(由于例子簡單,HelloWord模板沒有輸入源)和目標文本(比如最終需要的C#或者T-SQL代碼等)定義相應的模板。T4模板作用就相當于進行XML轉化過程中使用的XSLT。

  T4模板的定義非常簡單,整個模板的內容包括兩種形式:靜態形式和動態動態。前者就是直接寫在模板中作為原樣輸出的文本,后者是基于某種語言編寫代碼,T4引擎會動態執行它們。這和我們通過內聯的方式編寫的ASP.NET頁面很相似:HTML是靜態的,以C#或者VB.NET代碼便寫的動態執行的代碼通過相應的標簽內嵌其中。為了讓讀者對T4模板有一個直觀的認識,我們先來嘗試寫一個最簡單的。假設我們需要通過代碼生成的方式生成如下一段簡單的C#代碼:

   1: using System;
   2:  
   3: namespace Artech.CodeGeneration
   4: {
   5:     class Program
   6:     {
   7:         static void Main(string[] args)
   8:         {
   9:             Console.WriteLine("Hello, {0}", "Foo");
  10:             Console.WriteLine("Hello, {0}", "Bar");
  11:             Console.WriteLine("Hello, {0}", "Baz");
  12:         }
  13:     }
  14: }
  現在我們直接通過VS來創建一個T4模板來生成我們期望的C#代碼。右擊項目文件,選擇"Add"|"New Item",在模板列表中選擇"Text Template"。指定文件名后確定,一個后綴名為.tt的文件會被創建,然后在該文件中編寫如下的代碼。
 
1: <#@ template debug="false" hostspecific="false" language="C#" #>
2: <#@ assembly name="System.Core.dll" #>
3: <#@ import namespace="System" #>
4: <#@ output extension=".cs" #>
5: using System;
6:
7: namespace Artech.CodeGeneration
8: {
9: class Program
10: {
11: static void Main(string[] args)
12: {
13: <#
14: foreach(var person in this.InitializePersonList())
15: {
16: #>Console.WriteLine("Hello, {0}","<#= person#>");
17: <# } #>
18: }
19: }
20: }
21:
22: <#+
23: public string[] InitializePersonList()
24: {
25: return new string[]{"Foo","Bar","Baz"};
26: }
27: #>
保存該文件后,一個.cs文件將會作為該TT文件的附屬文件被添加(如右圖所示的HelloWorld.cs)。上述的這個TT文件雖然簡單,卻包含了構成一個T4模板的基本元素。在解讀該T4模板之前,我們有必要先來了解一個完整的T4模板是如何構成的。

  三、T4模板的基本結構

  假設我們用“塊”(Block)來表示構成T4模板的基本單元,它們基本上可以分成5類:指令塊(Directive Block)、文本塊(Text Block)、代碼語句塊(Statement Block)、表達式塊(Expression Block)和類特性塊(Class Feature Block)。

  1、指令塊(Directive Block)

  和ASP.NET頁面的指令一樣,它們出現在文件頭,通過<#@…#>表示。其中<#@ template …#>指令是必須的,用于定義模板的基本屬性,比如編程語言、基于的文化、是否支持調式等等。比較常用的指令還包括用于程序集引用的<#@ assembly…#>,用于導入命名空間的<#@ import…#>等等。

  2、文本塊(Text Block)

  文本塊就是直接原樣輸出的靜態文本,不需要添加任何的標簽。在上面的模板文件中,處理定義在<#… #>、<#+… #>和<#=… #>中的文本都屬于文本塊。比如在指令塊結束到第一個“<#”標簽之間的內容就是一段靜態的文本塊。

   1: using System;
   2:  
   3: namespace Artech.CodeGeneration
   4: {
   5:     class Program
   6:     {     
   7:         static void Main(string[] args)
   8:         {    
   9:             

  3、代碼語句塊(Statement Block)

  代碼語句塊通過<#Statement#>的形式表示,中間是一段通過相應編程語言編寫的程序調用,我們可以通過代碼語句快控制文本轉化的流程。在上面的代碼中,我們通過代碼語句塊實現對一個數組進行遍歷,輸出重復的Console.WriteLine(“Hello, {0}”, “Xxx”)語句。

   1: <#
   2: foreach(var person in this.InitializePersonList()) 
   3: {
   4: #>
   5:     Console.Write("Hello, {0}","<#=  person#>");
   6: <#
   7: } 
   8: #>

  4、表達式塊(Expression Block)

  表達式塊以<#=Expression#>的形式表示,通過它之際上動態的解析的字符串表達內嵌到輸出的文本中。比如在上面的foreach循環中,每次迭代輸出的人名就是通過表達式塊的形式定義的(<#=  person#>)

  5、類特性塊(Class Feature Block)

  如果文本轉化需要一些比較復雜的邏輯,我們需要寫在一個單獨的輔助方法中,甚至是定義一些單獨的類,我們就是將它們定義在類特性塊中。類特性塊的表現形式為<#+ FeatureCode #>,對于Hello World模板,得到人名列表的InitializePersonList方法就定義在類特性塊中。

   1: <#+ 
   2:     public string[] InitializePersonList()
   3:     {
   4:         return new string[]{"Foo","Bar","Baz"};
   5:     }
   6: #>

  了解T4模板的“五大塊”之后,相信讀者對定義在HelloWord.tt中的模板體現的文本轉化邏輯應該和清楚了吧。

  四、通過T4模板實現從“數據到代碼”的轉變

  現在我們來完成我們開篇布置得任務:如何將一個已知結構的表示消息列表的XML轉換成C#代碼,使得我們可以一強類型的編程方式獲取和格式化相應的消息條目。我們的T4模板定義如下:

 
1: <#@ template debug="false" hostspecific="true" language="C#" #>
2: <#@ assembly name="System.Core.dll" #>
3: <#@ assembly name="System.Xml" #>
4: <#@ import namespace="System" #>
5: <#@ import namespace="System.Xml" #>
6: <#@ import namespace="System.Linq" #>
7: <#@ output extension=".cs" #>
8:
9: namespace MessageCodeGenrator
10: {
11: public static class Messages
12: {
13: <#
14: XmlDocument messageDoc = new XmlDocument();
15: messageDoc.Load(this.Host.ResolvePath("Messages.xml"));
16:
17: var messageEntries = messageDoc.GetElementsByTagName("message").Cast<XmlElement>();
18: var categories = (from element in messageEntries
19: select element.Attributes["category"].Value).Distinct();
20: foreach (var category in categories)
21: {
22: #>
23: public static class <#= category#>
24: {
25: <#
26: foreach (var element in messageDoc.GetElementsByTagName("message").Cast<XmlElement>().Where(element => element.Attributes["category"].Value == category))
27: {
28: string id = element.Attributes["id"].Value;
29: string value = element.Attributes["value"].Value;
30: string categotry = element.Attributes["category"].Value;
31: #>
32: public static MessageEntry <#= id #> = new MessageEntry("<#= id #>","<#= value#>","<#= categotry#>");
33: <# } #>
34: }
35: <# } #>
36: }
37: }

  模板體現出來的轉化流程就是:加載XML文件(Messages.xml),然后獲取所有的消息類別,為每個消息類別創建一個內嵌于靜態類Messages中的以類別命名的類。然后遍歷每個類別下的所有消息條目,定義類型為MessageEntry的靜態熟悉。

  在這里有一點需要特別指出的是:整個代碼生成的輸入,即XML文件Messages.xml和模板文件位于相同的目錄下,但是我們需要通過Host屬性的ResolvePath方法去解析文件的物理路徑。對ResolvePath方法的調用,需要模板<#@ template …#>指令中的hostspecific設置為true。

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

  五、T4的文本轉化的實現

  和我之前采用的代碼生成方式(CodeDOM+Custom Tool)一樣,對于T4模板的代碼生成,VS最終還是通過Custom Tool來完成的。如果你查看TT文件的屬性,你會發現Custom Tool會自動設置成:TextTemplatingFileGenerator。

image  當TextTemplatingFileGenerator被觸發后(修改后的文件被保存,或者認為執行Custom Tool),會通過T4引擎完成文本的轉換和輸出工作。具體來講,T4引擎的文本轉化和輸出機制可以通過下圖來表示。T4引擎首先對模板的靜態內容和動態內容進行解析,最終生成一個繼承自Microsoft.VisualStudio.TextTemplating.TextTransformation的類,所有的文本轉化邏輯被放入被重寫的Transformation方法中。然后動態創建該對象,執行該方法并將最終的類型以附加文件的形式輸出來。

T4 Template Transformation Process

1
0
 
標簽:.NET T4
 
 

文章列表

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()