Visual Studio DSL 入门 13---结合T4生成代码

     在前面的几节里,我们已经完成了一个简单的状态机设计器,通过这个状态机可以设计出一个状态流,但是如果只是这样,我们直接使用UML设计工具就行了,何必自己开发呢? 我们走的是模型驱动开发路线,呵呵,注意哥说的是开发,不是设计.这一节就和我们的开发联系起来,生成符合我们要求的代码.
     结合vs.net dsl生成代码有以下几种方式:
     直接硬编码,在代码里面利用模型拼接生成的代码,我记得activewriter就是这样做的生成nhibernate代码.
     结合模板引擎,你可以使用xslt或者t4(text template  transformation toolkit),或者是codesmith等.

     在这里我们使用T4来生成,vs.net已经内置支持T4引擎(dsl和linq等都是使用t4来生成的), 即使这样,vs.net也没有内置对T4文件的编辑器,在开始下面之前,需要从这里下载免费的Community版本安装.
     1.直接运行我们的项目,可以发现在Debugging项目下面有两个tt文件,这两个文件就是生成简单代码的一个例子,直接打开LanguageSmReport.tt

隐藏行号 复制代码
  1. <#@ Import Namespace="System" #>
  2. <#@ template inherits="Microsoft.VisualStudio.TextTemplating.VSHost.ModelingTextTransformation" language="C#" #>
  3. <#@ output extension=".txt" #>
  4. <#@ LanguageSm processor="LanguageSmDirectiveProcessor" requires="fileName='Test.mydsl5'" #>
  5. <#
  6.       foreach (State state in StateMachine.States) {
  7. #>
  8.     <#=      state.Name #>
  9. <#    
  10.       }
  11. #>
    2.运行自定义工具,生成的文件就是附属的txt文件:
隐藏行号 复制代码
  1.     Draft
  2.     NewOrder
  3.     Cancelled
  4.     Confirmed

   3. 对应的我们的状态机是我建立的一个简单的订单状态流转:
 2010-3-12 23-10-41

      4.回头过来再看一下这个t4模板文件,看起来其实很象aspx页面:
       (1).通过Import引用需要的命名空间,事先所在的dll一定要添加到项目中.
       (2).第二行指定模板继承自Microsoft.VisualStudio.TextTemplating.VSHost.ModelingTextTransformation,指定模板语言使用C#,注意,如果你需要使用framework 3.5,这里需要设置成C#3.5.
       (3).通过output指令设置生成文件的后缀名和编码.
       (4).声明我们的指令处理器以及需要加载的模型文件.
 
       (5).模板的正文很容易理解,只需要记住它的几个控制块的类型.
          <#….#>标准控制块,里面放控制语句,就是我们普通的C#或者VB代码组成的控制语句.
          <#+..#>类特性控制块,里面可以添加方法,属性,域或者内嵌类,在这里一般放一些重用性高的代码.
          <#=…#>表达式控制块,计算里面包含的表达式的值并输出.

     5.但是这个T4文件又是怎么样的运行解析机制呢,其实它和我们的aspx页面很类似,我们来看一下它生成的转换类:

隐藏行号 复制代码
  1.  public class GeneratedTextTransformation : Microsoft.VisualStudio.TextTemplating.VSHost.ModelingTextTransformation {
  2.         public override string TransformText() {
  3.             try {
  4.                 this.Write("\r\n");
  5.                 this.Write("\r\n");
  6.                 this.Write("\r\n");
  7.                 this.Write("\r\n\r\n");
  8.                 foreach (State state in StateMachine.States) {
  9.                     this.Write("\r\n\t");
  10.                     this.Write(Microsoft.VisualStudio.TextTemplating.ToStringHelper.ToStringWithCulture(
  11.                           state.Name
  12.                     ));
  13.                     this.Write("\r\n");
  14.                 }
  15.                 this.Write("\r\n");
  16.             } catch (System.Exception e) {
  17.                 System.CodeDom.Compiler.CompilerError error = new System.CodeDom.Compiler.CompilerError();
  18.                 error.ErrorText = e.ToString();
  19.                 error.FileName = "template2.t4";
  20.                 this.Errors.Add(error);
  21.             }
  22.             return this.GenerationEnvironment.ToString();
  23.         }
  24.         private Company.LanguageSm.StateMachine statemachineValue;
  25.         private Company.LanguageSm.StateMachine StateMachine {
  26.             get {
  27.                 return this.statemachineValue;
  28.             }
  29.         }
  30.         protected override void Initialize() {
  31.             this.AddDomainModel(typeof(Microsoft.VisualStudio.Modeling.Diagrams.CoreDesignSurfaceDomainModel));
  32.             this.AddDomainModel(typeof(Company.LanguageSm.LanguageSmDomainModel));
  33.             base.Initialize();
  34.             if ((this.Errors.HasErrors == false)) {
  35.                 Microsoft.VisualStudio.Modeling.Transaction statemachineTransaction = null;
  36.                 try {
  37.                     Microsoft.VisualStudio.Modeling.SerializationResult serializationResult = new Microsoft.VisualStudio.Modeling.SerializationResult();
  38.                     statemachineTransaction = this.Store.TransactionManager.BeginTransaction("Load", true);
  39.                     this.statemachineValue = Company.LanguageSm.LanguageSmSerializationHelper.Instance.LoadModel(serializationResult, this.Store, "Test.mydsl5", null, null);
  40.                     if (serializationResult.Failed) {
  41.                         throw new Microsoft.VisualStudio.Modeling.SerializationException(serializationResult);
  42.                     }
  43.                     statemachineTransaction.Commit();
  44.                 } finally {
  45.                     if ((statemachineTransaction != null)) {
  46.                         statemachineTransaction.Dispose();
  47.                     }
  48.                 }
  49.             }
  50.         }
  51.     }
     通过Write方法输出我的内容,然后其实是对于我们的模型的根域类属性,并重写Initialize()方法进行了初始化.
        6.回过头来,我们要根据上面3中的订单状态图,生成我们的代码,最重要也是最基本的一点,在你打算用T4生成代码时,你一定要对你想生成的代码了如指掌.如果你连自己要什么都不知道,更不可能达到了. 我们以最基本的一个例子,虽然这样写代码可能并不合理,不过在这里我们为了使问题尽量简单化:        
隐藏行号 复制代码
  1. /// <summary>
  2.       /// 订单状态
  3.       /// </summary>
  4.        public  enum OrderStateEnum
  5.         {
  6.         Draft,
  7.         NewOrder,
  8.         Cancelled,
  9.         Confirmed,
  10.         }
  11.   
  12.   
  13.       /// <summary>
  14.       /// 订单生成
  15.       /// </summary>
  16.       public  partial  class Order
  17.       {
  18.          public OrderStateEnum  OrderState
  19.          {
  20.            get;
  21.            set;
  22.          }
  23.  
  24.         public Order()
  25.         {
  26.         
  27.         }
  28.                 
  29.         protected  void    SaveOrder(Order order)
  30.         {    
  31.        
  32.              if(order.OrderState ==  OrderStateEnum.Draft)
  33.                         order.OrderState =  OrderStateEnum.NewOrder;
  34.            
  35.         }
  36.       }

            (1).我们需要为我们的所有的状态生成到我们的枚举类型OrderStateEnum中(状态名就是枚举名,值不考虑).
            (2).在我们的Order类中,有一个固定的OrderStateEnum类型的属性OrderState,表示当前订单的状态. 
            (3).需要为我们的每一个转移Transigion生成一个方法到我们的Order类中,方法名就是Transition的Event的值,方法体就是当订单的状态为Transigion的发起者Predecessor时,将订单的状态置为目标Successor.也就是说在SaveOrder内,判断如果订单的状态是Draft时,就把订单的状态置为NewOrder.
      7.在明白了目标代码后,我们来写我们的T4文件,首先需要添加一个公共的方法来获取StateMachine里的所有的Transition.我们使用<#+#>来完成这个方法,注意这个方法需要放在整个模板文件的最下面.

隐藏行号 复制代码
  1. <#+
  2.        System.Collections.Generic.IEnumerable<Transition> GetAllTransitions() {
  3.            foreach (State s in StateMachine.States)
  4.                foreach (Transition t in Transition.GetLinksToSuccessors(s))
  5.                    yield return t;
  6.        }
  7. #>

       8.剩下的工作就更简单了,我们只需要遍历这些Transition,对于每个Transition,对于它的Predessor和Successor进行如上所说的判断和赋值即可,而对于固定的部分,我们只需要以文本的形式写出来就可以了:

隐藏行号 复制代码
  1.       /// <summary>
  2.       /// 订单状态
  3.       /// </summary>
  4.        public  enum OrderStateEnum
  5.         {
  6. <#
  7.     PushIndent("    ");
  8.    foreach (State state in StateMachine.States)
  9.        WriteLine("    {0},", state.Name);
  10.     PopIndent();
  11. #>
  12.         }
  13.       /// <summary>
  14.       /// 订单生成
  15.       /// </summary>
  16.       public  partial  class Order
  17.       {
  18.          public OrderStateEnum  OrderState
  19.          {
  20.            get;
  21.            set;
  22.          }
  23.  
  24.         public Order()
  25.         {
  26.         
  27.         }
  28.         
  29.         public   void Save(Order order){
  30.             // to save order
  31.         }
  32.         
  33.         <#
  34.               foreach (Transition transition in GetAllTransitions()) {
  35.         #>
  36.         
  37.         protected  void    <#=transition.Event#>
  38.         {    
  39.        
  40.              if(order.OrderState ==  OrderStateEnum.<#=transition.Predecessor.Name#>)
  41.                         order.OrderState =  OrderStateEnum.<#= transition.Successor.Name#>;
  42.            Save(order);
  43.         }
  44. <#      
  45.               } 
  46. #>   
  47.     }

       9.转换模板,就可以看到我们生成的代码了,虽然在这个例子中并没有显现出T4的强大,不过对于复杂的规范性的系统来说,能够通过Dsl进行设计,然后结合T4生成那些代码还是能够极大的提高生产率的.

隐藏行号 复制代码
  1.    /// <summary>
  2.       /// 订单状态
  3.       /// </summary>
  4.        public  enum OrderStateEnum
  5.         {
  6.         Draft,
  7.         NewOrder,
  8.         Cancelled,
  9.         Confirmed,
  10.         }
  11.       /// <summary>
  12.       /// 订单生成
  13.       /// </summary>
  14.       public  partial  class Order
  15.       {
  16.          public OrderStateEnum  OrderState
  17.          {
  18.            get;
  19.            set;
  20.          }
  21.  
  22.         public Order()
  23.         {
  24.         
  25.         }
  26.         
  27.         public   void Save(Order order){
  28.             // to save order
  29.         }
  30.         
  31.                 
  32.         protected  void    SaveOrder(Order order)
  33.         {    
  34.        
  35.              if(order.OrderState ==  OrderStateEnum.Draft)
  36.                         order.OrderState =  OrderStateEnum.NewOrder;
  37.            Save(order);
  38.         }
  39.         
  40.         protected  void    CancelOrder(Order order)
  41.         {    
  42.        
  43.              if(order.OrderState ==  OrderStateEnum.NewOrder)
  44.                         order.OrderState =  OrderStateEnum.Cancelled;
  45.            Save(order);
  46.         }
  47.         
  48.         protected  void    ConfirmOrder(Order  order)
  49.         {    
  50.        
  51.              if(order.OrderState ==  OrderStateEnum.NewOrder)
  52.                         order.OrderState =  OrderStateEnum.Confirmed;
  53.            Save(order);
  54.         }
  55.    
  56.     }

      10.需要注意的是,结合dsl和t4也不可能使你的一个项目不用手写代码了,它只能是能够生成你比较固定的代码部分,能够抽象出来的,目前t4还不能够解决生成代码中允许直接签入自定义代码,所以现在一般以文件为分隔,通过partial机制,由t4生成的cs文件中专门存储这些抽象出来的可生成部分,而这部分是不允许修改的,因为在你修改完模型后下次还会重新生成,而你需要扩展的部分,都以partial类的机制在另外一个类中.

代码下载

 

参考资源
      1. Visual Stuido DSL 工具特定领域开发指南
      2. DSL Tools Lab     http://code.msdn.microsoft.com/DSLToolsLab  系列教程  [本系列的入门案例的主要参考]

作者:孤独侠客似水流年
出处:http://lonely7345.cnblogs.com/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

标签: VS.net DSL, T4
posted @ 2010-03-14 20:50 孤独侠客 阅读(1831) 评论(4) 编辑 收藏

 回复 引用 查看   
#1楼[楼主]2010-03-14 21:16 | 孤独侠客      
不知道有人看没
 回复 引用 查看   
#2楼2010-03-14 23:41 | 明年我18      
引用孤独侠客:不知道有人看没


我看了,以后研究一下t4.

另外,楼主的博文样式是不是有点问题,我1024*768下看不全代码

 回复 引用 查看   
#3楼[楼主]2010-03-15 08:46 | 孤独侠客      
@明年我18
下面会出现滚动条的吧

 回复 引用 查看   
#4楼2010-03-15 08:57 | 明年我18      
引用孤独侠客:
@明年我18
下面会出现滚动条的吧


会有滚动条,但不是出现在代码段那里,回头我给你截个图看看,ie8,1024×768