從數據到代碼——基於T4的代碼生成方式
在之前寫一篇文章《從數據到代碼》(上篇、下篇)中,我通過基於的代碼生成方式實現了將一個XML表示的消息列表轉換成了相應的C#代碼,從而達到了的目的。實際上,我們最常用的代碼生成當時不是CodeDOM,而是,這是一個更為強大,並且適用範圍更廣的代碼生成技術。今天,我將相同的例子通過T4的方式再實現一次,希望為那些對T4不了解的讀者帶來一些啟示。同時這篇文章將作為後續文章的引子,在此之後,我將通過兩篇文章通過具體實例的形式講述如果在項目將T4為我所用,以達到提高開發效率和保證質量的目的。[這裏有T4相關的資料][文中的例子可以從這裏下載]
目錄
一、我們的目標是:從XML文件到C#代碼
二、從Hello World講起
三、T4模板的基本結構
四、通過T4模板實現從“數據到代碼”的轉變
五、T4的文本轉化的實現
再次重申一下我們需要通過“代碼生成”需要達到的目的。無論對於怎麼樣的應用,我們都需要維護一係列的消息。消息的類型很多,比如驗證消息、確認消息、日誌消息等。我們一般會將消息儲存在一個文件或者數據庫中進行維護,並提供一些API來獲取相應的消息項。這些API一般都是基於消息的ID來獲取的,換句話說,消息獲取的方式是以一種“”的編程方式實現的。。
比如說,現在我們定義了如下一個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。。
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)到“代碼”的轉換呢?在投入到這個稍微複雜的工作之前,我們先來弄個簡單的。
我們之前一直在講T4,可能還有人不知道T4到底代表什麼。T4是對“”(4個T)的簡稱。T4直接包含在VS2008和VS2010中,是一個基於文本文件轉換的工具包。T4的核心是一個基於“文本模板”的轉換引擎(以下簡稱),我們可以通過它生成一切類型的文本型文件,比如我們常用的代碼文件類型包括:C#、VB.NET、T-SQL、XML甚至是配置文件等。
對於需要通過T4來進行代碼生成工作的我們來說,需要做的僅僅是根據轉換源(Transformation Source),比如數據表、XML等(由於例子簡單,HelloWord模板沒有輸入源)和目標文本(比如最終需要的C#或者T-SQL代碼等)定義相應的模板。T4模板作用就。
T4模板的定義非常簡單,整個模板的內容包括兩種形式:和。前者就是直接寫在模板中作為的文本,後者是基於某種語言編寫代碼,T4引擎會動態執行它們。這和我們通過很相似: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: }
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: #>
17: Console.WriteLine("Hello, {0}","<#= person#>");
18: <#
19: }
20: #>
21: }
22: }
23: }
24:
25: <#+
26: public string[] InitializePersonList()
27: {
28: return new string[]{"Foo","Bar","Baz"};
29: }
30: #>
假設我們用“塊”(Block)來表示構成T4模板的基本單元,它們基本上可以分成5類:、、、和。
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)
代碼語句塊通過的形式表示,中間是一段通過相應編程語言編寫的程序調用,我們可以通過代碼語句快控製文本轉化的流程。在上麵的代碼中,我們通過代碼語句塊實現對一個數組進行遍曆,輸出重複的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)
表達式塊以的形式表示,通過它之際上動態的解析的字符串表達內嵌到輸出的文本中。比如在上麵的foreach循環中,每次迭代輸出的人名就是通過表達式塊的形式定義的(<#= person#>)
5、類特性塊(Class Feature Block)
如果文本轉化需要一些比較複雜的邏輯,我們需要寫在一個單獨的輔助方法中,甚至是定義一些單獨的類,我們就是將它們定義在類特性塊中。類特性塊的表現形式為對於Hello World模板,得到人名列表的InitializePersonList方法就定義在類特性塊中。
1: <#+
2: public string[] InitializePersonList()
3: {
4: return new string[]{"Foo","Bar","Baz"};
5: }
6: #>
了解T4模板的“五大塊”之後,相信讀者對定義在HelloWord.tt中的模板體現的文本轉化邏輯應該和清楚了吧。
現在我們來完成我們開篇布置得任務:如何將一個已知結構的表示消息列表的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和模板文件位於相同的目錄下,但是我們需要通過屬性的方法去解析文件的物理路徑。對ResolvePath方法的調用,需要模板<#@ template …#>指令中的設置為。
1: <#@ template debug="false" hostspecific="true" language="C#" #>
和我之前采用的代碼生成方式(CodeDOM+Custom Tool)一樣,對於T4模板的代碼生成,VS最終還是通過Custom Tool來完成的。如果你查看TT文件的屬性,你會發現Custom Tool會自動設置成:。
當TextTemplatingFileGenerator被觸發後(修改後的文件被保存,或者認為執行Custom Tool),會通過T4引擎完成文本的轉換和輸出工作。具體來講,T4引擎的文本轉化和輸出機製可以通過下圖來表示。T4引擎首先對模板的靜態內容和動態內容進行解析,最終生成一個繼承自Microsoft.VisualStudio.TextTemplating.TextTransformation的類,所有的文本轉化邏輯被放入被重寫的Transformation方法中。然後動態創建該對象,執行該方法並將最終的類型以附加文件的形式輸出來。
從數據到代碼——通過代碼生成機製實現強類型編程[上篇]
從數據到代碼——通過代碼生成機製實現強類型編程[下篇]
從數據到代碼——基於T4的代碼生成方式
創建代碼生成器可以很簡單:如何通過T4模板生成代碼?[上篇]
創建代碼生成器可以很簡單:如何通過T4模板生成代碼?[下篇]
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
最後更新:2017-10-27 14:04:38