Visual Studio DSL 入門 13---結(jié)合T4生成代碼
在前面的幾節(jié)里,我們已經(jīng)完成了一個(gè)簡單的狀態(tài)機(jī)設(shè)計(jì)器,通過這個(gè)狀態(tài)機(jī)可以設(shè)計(jì)出一個(gè)狀態(tài)流,但是如果只是這樣,我們直接使用UML設(shè)計(jì)工具就行了,何必自己開發(fā)呢? 我們走的是模型驅(qū)動(dòng)開發(fā)路線,呵呵,注意哥說的是開發(fā),不是設(shè)計(jì).這一節(jié)就和我們的開發(fā)聯(lián)系起來,生成符合我們要求的代碼.
結(jié)合vs.net dsl生成代碼有以下幾種方式:
直接硬編碼,在代碼里面利用模型拼接生成的代碼,我記得activewriter就是這樣做的生成nhibernate代碼.
結(jié)合模板引擎,你可以使用xslt或者t4(text template transformation toolkit),或者是codesmith等.
在這里我們使用T4來生成,vs.net已經(jīng)內(nèi)置支持T4引擎(dsl和linq等都是使用t4來生成的), 即使這樣,vs.net也沒有內(nèi)置對(duì)T4文件的編輯器,在開始下面之前,需要從這里下載免費(fèi)的Community版本安裝.
1.直接運(yùn)行我們的項(xiàng)目,可以發(fā)現(xiàn)在Debugging項(xiàng)目下面有兩個(gè)tt文件,這兩個(gè)文件就是生成簡單代碼的一個(gè)例子,直接打開LanguageSmReport.tt
-
<#@Import Namespace="System" #>
-
<#@template inherits="Microsoft.VisualStudio.TextTemplating.VSHost.ModelingTextTransformation" language="C#" #>
-
<#@output extension=".txt" #>
-
<#@ LanguageSm processor="LanguageSmDirectiveProcessor" requires="fileName='Test.mydsl5'" #>
-
<# -
foreach (State state in StateMachine.States) {
-
#> -
<#=state.Name #>
-
<# -
} -
#>
2.運(yùn)行自定義工具,生成的文件就是附屬的txt文件:
-
Draft -
NewOrder -
Cancelled -
Confirmed
3. 對(duì)應(yīng)的我們的狀態(tài)機(jī)是我建立的一個(gè)簡單的訂單狀態(tài)流轉(zhuǎn):
4.回頭過來再看一下這個(gè)t4模板文件,看起來其實(shí)很象aspx頁面:
(1).通過Import引用需要的命名空間,事先所在的dll一定要添加到項(xiàng)目中.
(2).第二行指定模板繼承自Microsoft.VisualStudio.TextTemplating.VSHost.ModelingTextTransformation,指定模板語言使用C#,注意,如果你需要使用framework 3.5,這里需要設(shè)置成C#3.5.
(3).通過output指令設(shè)置生成文件的后綴名和編碼.
(4).聲明我們的指令處理器以及需要加載的模型文件.
(5).模板的正文很容易理解,只需要記住它的幾個(gè)控制塊的類型.
<#….#>標(biāo)準(zhǔn)控制塊,里面放控制語句,就是我們普通的C#或者VB代碼組成的控制語句.
<#+..#>類特性控制塊,里面可以添加方法,屬性,域或者內(nèi)嵌類,在這里一般放一些重用性高的代碼.
<#=…#>表達(dá)式控制塊,計(jì)算里面包含的表達(dá)式的值并輸出.
5.但是這個(gè)T4文件又是怎么樣的運(yùn)行解析機(jī)制呢,其實(shí)它和我們的aspx頁面很類似,我們來看一下它生成的轉(zhuǎn)換類:
-
public class GeneratedTextTransformation : Microsoft.VisualStudio.TextTemplating.VSHost.ModelingTextTransformation {
-
public override string TransformText() {
-
try {
-
this.Write("\r\n");
-
this.Write("\r\n");
-
this.Write("\r\n");
-
this.Write("\r\n\r\n");
-
foreach (State state in StateMachine.States) {
-
this.Write("\r\n\t");
-
this.Write(Microsoft.VisualStudio.TextTemplating.ToStringHelper.ToStringWithCulture(
-
state.Name -
)); -
this.Write("\r\n");
-
} -
this.Write("\r\n");
-
} catch (System.Exception e) {
-
System.CodeDom.Compiler.CompilerError error = new System.CodeDom.Compiler.CompilerError();
-
error.ErrorText = e.ToString(); -
error.FileName = "template2.t4";
-
this.Errors.Add(error);
-
} -
return this.GenerationEnvironment.ToString();
-
} -
private Company.LanguageSm.StateMachine statemachineValue;
-
private Company.LanguageSm.StateMachine StateMachine {
-
get {
-
return this.statemachineValue;
-
} -
} -
protected override void Initialize() {
-
this.AddDomainModel(typeof(Microsoft.VisualStudio.Modeling.Diagrams.CoreDesignSurfaceDomainModel));
-
this.AddDomainModel(typeof(Company.LanguageSm.LanguageSmDomainModel));
-
base.Initialize();
-
if ((this.Errors.HasErrors == false)) {
-
Microsoft.VisualStudio.Modeling.Transaction statemachineTransaction = null;
-
try {
-
Microsoft.VisualStudio.Modeling.SerializationResult serializationResult = new Microsoft.VisualStudio.Modeling.SerializationResult();
-
statemachineTransaction = this.Store.TransactionManager.BeginTransaction("Load", true);
-
this.statemachineValue = Company.LanguageSm.LanguageSmSerializationHelper.Instance.LoadModel(serializationResult, this.Store, "Test.mydsl5", null, null);
-
if (serializationResult.Failed) {
-
throw new Microsoft.VisualStudio.Modeling.SerializationException(serializationResult);
-
} -
statemachineTransaction.Commit(); -
} finally {
-
if ((statemachineTransaction != null)) {
-
statemachineTransaction.Dispose(); -
} -
} -
} -
} -
}
通過Write方法輸出我的內(nèi)容,然后其實(shí)是對(duì)于我們的模型的根域類屬性,并重寫Initialize()方法進(jìn)行了初始化.
6.回過頭來,我們要根據(jù)上面3中的訂單狀態(tài)圖,生成我們的代碼,最重要也是最基本的一點(diǎn),在你打算用T4生成代碼時(shí),你一定要對(duì)你想生成的代碼了如指掌.如果你連自己要什么都不知道,更不可能達(dá)到了. 我們以最基本的一個(gè)例子,雖然這樣寫代碼可能并不合理,不過在這里我們?yōu)榱耸箚栴}盡量簡單化:
-
/// <summary> -
/// 訂單狀態(tài)
-
/// </summary> -
public enum OrderStateEnum
-
{ -
Draft, -
NewOrder, -
Cancelled, -
Confirmed, -
} -
/// <summary> -
/// 訂單生成
-
/// </summary> -
public partial class Order
-
{ -
public OrderStateEnum OrderState
-
{ -
get;
-
set;
-
} -
public Order()
-
{ -
} -
protected void SaveOrder(Order order)
-
{ -
if(order.OrderState == OrderStateEnum.Draft)
-
order.OrderState = OrderStateEnum.NewOrder;
-
} -
}
(1).我們需要為我們的所有的狀態(tài)生成到我們的枚舉類型OrderStateEnum中(狀態(tài)名就是枚舉名,值不考慮).
(2).在我們的Order類中,有一個(gè)固定的OrderStateEnum類型的屬性O(shè)rderState,表示當(dāng)前訂單的狀態(tài).
(3).需要為我們的每一個(gè)轉(zhuǎn)移Transigion生成一個(gè)方法到我們的Order類中,方法名就是Transition的Event的值,方法體就是當(dāng)訂單的狀態(tài)為Transigion的發(fā)起者Predecessor時(shí),將訂單的狀態(tài)置為目標(biāo)Successor.也就是說在SaveOrder內(nèi),判斷如果訂單的狀態(tài)是Draft時(shí),就把訂單的狀態(tài)置為NewOrder.
7.在明白了目標(biāo)代碼后,我們來寫我們的T4文件,首先需要添加一個(gè)公共的方法來獲取StateMachine里的所有的Transition.我們使用<#+#>來完成這個(gè)方法,注意這個(gè)方法需要放在整個(gè)模板文件的最下面.
-
<#+ -
System.Collections.Generic.IEnumerable<Transition> GetAllTransitions() {
-
foreach (State s in StateMachine.States)
-
foreach (Transition t in Transition.GetLinksToSuccessors(s))
-
yield return t;
-
} -
#>
8.剩下的工作就更簡單了,我們只需要遍歷這些Transition,對(duì)于每個(gè)Transition,對(duì)于它的Predessor和Successor進(jìn)行如上所說的判斷和賦值即可,而對(duì)于固定的部分,我們只需要以文本的形式寫出來就可以了:
-
/// <summary> -
/// 訂單狀態(tài) -
/// </summary> -
public enum OrderStateEnum -
{ -
<# -
PushIndent(" ");
-
foreach (State state in StateMachine.States)
-
WriteLine(" {0},", state.Name);
-
PopIndent();
-
#> -
} -
/// <summary> -
/// 訂單生成 -
/// </summary> -
public partial class Order -
{ -
public OrderStateEnum OrderState -
{ -
get; -
set; -
} -
public Order() -
{ -
} -
public void Save(Order order){ -
// to save order -
} -
<# -
foreach (Transition transition in GetAllTransitions()) {
-
#> -
protected void <#=transition.Event#>
-
{ -
if(order.OrderState == OrderStateEnum.<#=transition.Predecessor.Name#>)
-
order.OrderState = OrderStateEnum.<#=transition.Successor.Name#>;
-
Save(order); -
} -
<# -
} -
#> -
}
9.轉(zhuǎn)換模板,就可以看到我們生成的代碼了,雖然在這個(gè)例子中并沒有顯現(xiàn)出T4的強(qiáng)大,不過對(duì)于復(fù)雜的規(guī)范性的系統(tǒng)來說,能夠通過Dsl進(jìn)行設(shè)計(jì),然后結(jié)合T4生成那些代碼還是能夠極大的提高生產(chǎn)率的.
-
/// <summary> -
/// 訂單狀態(tài)
-
/// </summary> -
public enum OrderStateEnum
-
{ -
Draft, -
NewOrder, -
Cancelled, -
Confirmed, -
} -
/// <summary> -
/// 訂單生成
-
/// </summary> -
public partial class Order
-
{ -
public OrderStateEnum OrderState
-
{ -
get;
-
set;
-
} -
public Order()
-
{ -
} -
public void Save(Order order){
-
// to save order -
} -
protected void SaveOrder(Order order)
-
{ -
if(order.OrderState == OrderStateEnum.Draft)
-
order.OrderState = OrderStateEnum.NewOrder;
-
Save(order); -
} -
protected void CancelOrder(Order order)
-
{ -
if(order.OrderState == OrderStateEnum.NewOrder)
-
order.OrderState = OrderStateEnum.Cancelled;
-
Save(order); -
} -
protected void ConfirmOrder(Order order)
-
{ -
if(order.OrderState == OrderStateEnum.NewOrder)
-
order.OrderState = OrderStateEnum.Confirmed;
-
Save(order); -
} -
}
10.需要注意的是,結(jié)合dsl和t4也不可能使你的一個(gè)項(xiàng)目不用手寫代碼了,它只能是能夠生成你比較固定的代碼部分,能夠抽象出來的,目前t4還不能夠解決生成代碼中允許直接簽入自定義代碼,所以現(xiàn)在一般以文件為分隔,通過partial機(jī)制,由t4生成的cs文件中專門存儲(chǔ)這些抽象出來的可生成部分,而這部分是不允許修改的,因?yàn)樵谀阈薷耐昴P秃笙麓芜€會(huì)重新生成,而你需要擴(kuò)展的部分,都以partial類的機(jī)制在另外一個(gè)類中.
代碼下載
參考資源
1. Visual Stuido DSL 工具特定領(lǐng)域開發(fā)指南
2. DSL Tools Lab http://code.msdn.microsoft.com/DSLToolsLab 系列教程 [本系列的入門案例的主要參考]
作者:孤獨(dú)俠客(似水流年)
出處:http://lonely7345.cnblogs.com/
本文版權(quán)歸作者和博客園共有,歡迎轉(zhuǎn)載,但未經(jīng)作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責(zé)任的權(quán)利。

浙公網(wǎng)安備 33010602011771號(hào)