• 一个简易版的T4代码生成"框架"


    对于企业开发来说,代码生成在某种意义上可以极大地提高开发效率和质量。在众多代码生成方案来说,T4是一个不错的选择,今天花了点时间写了一个简易版本的T4代码生成的“框架”,该框架仅仅是定义了一些基本的基类以及其他与VS集成相关功能的类型而已。[源代码从这里下载]

    目录 
    一、T4模版的定义和代码文件的生成 
    二、TransformationContext与TransformationContextScope 
    三、Template 
    四、Generator 
    五、扩展方法RunCodeGenerator

    一、T4模版的定义和代码文件的生成

    我们先来看看最终的代码生成需要定义那些东西,以及T4模版应该如何定义。对于这个框架来说,代码结构的生成是通过继承自我们自定义基类Template的自定义类型实现的。作为演示,我们定义了如下一个DemoTemplate。从代码可以看出,DemoTemplate仅仅用于生成一个空类,类型名称在构造函数中指定。

       1:  public class DemoTemplate: Template
       2:  {
       3:      public string ClassName { get; private set; }
       4:      public DemoTemplate(string className)
       5:      {
       6:          this.ClassName = className;
       7:      }
       8:      public override string TransformText()
       9:      {
      10:          this.WriteLine("public class {0}",this.ClassName);
      11:          this.WriteLine("{");
      12:          this.WriteLine("}");
      13:          return this.GenerationEnvironment.ToString();
      14:      }
      15:  }

    代码的生成最终通过执行相应的Generator来实现,为此我们定义了如下一个DemoGenerator。DemoGenerator最终会生成三个.cs文件,而每个文件的代码最终由上面定义的DemoTemplate来生成。如下代码片段所示,继承自Generator的DemoGenerator重写了CreateTemplates方法,返回一个字典对象。字典的Key代表生成的文件名,而Value则表示相应的Template对象。

       1:  public class DemoGenerator : Generator
       2:  {
       3:      protected override IDictionary<string, Template> CreateTemplates()
       4:      {
       5:          Dictionary<string, Template> templates = new Dictionary<string, Template>();
       6:          templates.Add("Foo.cs", new DemoTemplate("Foo"));
       7:          templates.Add("Bar.cs", new DemoTemplate("Bar"));
       8:          templates.Add("Baz.cs", new DemoTemplate("Baz"));
       9:          return templates;
      10:      }
      11:  }

    最后我们在T4文件中以如下的方式执行DemoGenerator来生成我们需要的三个.cs文件。

       1:  <#@ template hostspecific="true" language="C#" #>
       2:  <#@ assembly name="$(TargetDir)Artech.CodeGeneration.dll" #>
       3:  <#@ import namespace="Artech.CodeGeneration" #>
       4:  <#@ output extension=".empty" #>
       5:  <#
       6:  this.RunCodeGenerator(this.Host, new DemoGenerator());
       7:  #>

    三个.cs文件(Foo.cs、Bar.cs和Baz.cs)最终会以如下的方式生成出来。

    Untitled

    二、TransformationContext与TransformationContextScope

    接下来我们来简单看看Generator最终是如何利用Template生成相应的文本文件的,不过在这之前我们先来了解一下TransformationContext与TransformationContextScope这两个类型。顾名思义,TransformationContext用于存储T4文本转换的上下文信息,而TransformationContextScope用于限制TransformationContext的作用范围,这与Transaction/TransactionScope的关系一样。

    TransformationContext定义如下,静态属性Current表示当前的TransformationContext,通过它可以得到当前的TextTransformation (即T4文件本身对应的那个TextTransformation 对象),当前的TextTemplatingEngineHost,以及针对T4文件的DTE和ProjectItem。

       1:  public class TransformContext
       2:  {
       3:      public static TransformContext Current { get; internal set; }
       4:      public TextTransformation Transformation{get; private set;}
       5:      public ITextTemplatingEngineHost Host {get; private set;}
       6:      public DTE Dte { get; private set; }
       7:      public ProjectItem TemplateProjectItem { get; private set; }
       8:   
       9:      internal TransformContext(TextTransformation transformation, ITextTemplatingEngineHost host)
      10:      {           
      11:          this.Transformation = transformation;
      12:          this.Host = host;
      13:          this.Dte = (DTE)((IServiceProvider)host).GetService(typeof(DTE));
      14:          this.TemplateProjectItem = this.Dte.Solution.FindProjectItem(host.TemplateFile);
      15:      }
      16:   
      17:      public static void EnsureContextInitialized()
      18:      {
      19:          if (null == Current)
      20:          {
      21:              throw new TransformationException("TransformContext is not initialized.");
      22:          }
      23:      }
      24:  }

    TransformationContext的构造函数是Internal的,所以不能在外部直接构建,我们通过具有如下定义的TransformationContextScope来创建它并将其作为当前的TransformationContext。TransformationContextScope实现了IDisposable接口,在实现的Dispose方法中当前的TransformationContext被设置为Null。

       1:  public class TransformContextScope: IDisposable
       2:  {
       3:      public TransformContextScope(TextTransformation transformation, ITextTemplatingEngineHost host)
       4:      {
       5:          TransformContext.Current = new TransformContext(transformation, host);
       6:      }
       7:   
       8:      public void Dispose()
       9:      {
      10:          TransformContext.Current = null;
      11:      }
      12:  }

    三、Template

    代码生成的逻辑实现在继承自具有如下定义的Template类型中,而它是TextTransformation的子类。Template的核心是Render和RenderToFile方法,前者指将生成的代码写入T4文件对应的生成文件中,后者则将内容写入某个指定的文件之中。Template生成的代码内容都是通过调用TransformText获取,在Render方法中直接通过当前TransformContext获取T4文件本身代表的TextTransformation对象,并调用其Wirte方法进行内容的写入。

    而RenderToFile方法由于涉及到生成新的文件,逻辑就相对复杂一些。它先通过当前TransformContext得到TextTemplatingEngineHost并计算出T4所在的目录,并最终解析出生成文件最终的路径。文件的创建和内容的写入通过调用CreateFile方法实现,如果涉及到Source Control,还需要执行Check Out操作。新创建的文件最终通过代表T4文件的ProjectItem对象添加到Project之中。

       1:  public abstract class Template: TextTransformation
       2:  {
       3:      private bool initialized;
       4:      public override void Initialize()
       5:      {
       6:          base.Initialize();
       7:          initialized = true;
       8:      }
       9:      internal void EnsureInitialized()
      10:      {
      11:          if (!initialized)
      12:          {
      13:              this.Initialize();
      14:          }
      15:      }
      16:      public virtual void Render()
      17:      { 
      18:          TransformContext.EnsureContextInitialized();
      19:          string contents = this.TransformText();
      20:          TransformContext.Current.Transformation.Write(contents);
      21:      }
      22:      public virtual void RenderToFile(string fileName)
      23:      {
      24:              TransformContext.EnsureContextInitialized();
      25:              string directory = Path.GetDirectoryName(TransformContext.Current.Host.TemplateFile);
      26:              fileName = Path.Combine(directory, fileName);
      27:              string contents = this.TransformText();
      28:              this.CreateFile(fileName, contents);
      29:              if (TransformContext.Current.TemplateProjectItem.ProjectItems.Cast<ProjectItem>().Any(item => item.get_FileNames(0) != fileName))
      30:              {
      31:                  TransformContext.Current.TemplateProjectItem.ProjectItems.AddFromFile(fileName);
      32:              }
      33:      }
      34:      protected void CreateFile(string fileName, string contents)
      35:      {
      36:          if (File.Exists(fileName) && File.ReadAllText(fileName) == contents)
      37:          {
      38:              return;
      39:          }
      40:          SourceControl sourceControl = TransformContext.Current.Dte.SourceControl;
      41:          if (null != sourceControl && sourceControl.IsItemUnderSCC(fileName) && !sourceControl.IsItemCheckedOut(fileName))
      42:          {
      43:              sourceControl.CheckOutItem(fileName);
      44:          }
      45:          File.WriteAllText(fileName, contents);
      46:      }
      47:  }

    四、Generator

    T4文件中最终是通过执行Generator对象来生成代码的,如下是这个抽象类型的定义。它定义了两个虚方法,其中CreateTemplates方法一组基于独立文件的Template对象,返回字典的Key代表生成文件名称;CreateTemplate返回直接生成在当前T4文件对应生成文件的Template对象。代码生成通过调用Run方法来完成,而最终的逻辑定义在虚方法RunCore中。

    在RunCore方法中,先便利通过CreateTemplates方法返回的Template对象并调用其RenderToFile进行独立文件的代码生成,然后调用CreateTemplate方法返回的Template对象的Render方法将代码生成于默认的代码文件中。最终执行RemoveUnusedFiles用于生成无用的文件。比如T4文件原来生成Foo.cs文件,现在修改T4文件内容使之生成Bar.cs文件,之前的文件应该在T4文件执行之后被删除。

       1:  public abstract class Generator
       2:  {
       3:      protected virtual IDictionary<string, Template> CreateTemplates()
       4:      {
       5:          return new Dictionary<string, Template>();
       6:      }
       7:      protected virtual Template CreateTemplate()
       8:      {
       9:          return null;
      10:      }
      11:      public void Run()
      12:      {
      13:          this.RunCore();
      14:          RemoveUnusedFiles();
      15:      }
      16:      protected virtual void RunCore()
      17:      {
      18:          foreach (var item in this.CreateTemplates())
      19:          {
      20:              item.Value.RenderToFile(item.Key);
      21:          }
      22:          Template template = this.CreateTemplate();
      23:          if (null != template)
      24:          {
      25:              template.EnsureInitialized();
      26:              template.Render();
      27:          }                       
      28:              
      29:      }
      30:      protected virtual void RemoveUnusedFiles()
      31:      {
      32:          List<string> codeFiles = new List<string>();
      33:          string directory = Path.GetDirectoryName(TransformContext.Current.Host.TemplateFile);
      34:          foreach (var item in this.CreateTemplates())
      35:          {
      36:              codeFiles.Add(Path.Combine(directory, item.Key));
      37:          }
      38:          var projectItems = TransformContext.Current.TemplateProjectItem.ProjectItems.Cast<ProjectItem>().ToArray();
      39:          foreach (ProjectItem projectItem in projectItems)
      40:          {
      41:              string fileName = projectItem.get_FileNames(0);
      42:              if (!codeFiles.Contains(fileName))
      43:              {
      44:                  projectItem.Delete();
      45:              }
      46:          }
      47:      }
      48:  }

    五、扩展方法RunCodeGenerator

    在我们的实例演示中,T4文件中执行Generator是通过调用方法RunCodeGenerator来实现的,这是一个针对TextTransformation的扩展方法。如下面的代码片段所示,方法先根据指定的TextTransformation 和TextTemplatingEngineHost 创建当前TransformContext,对Generator 的Run方法的调用是在当前TransformContext中完成的。

       1:  public static class TextTransformationExtensions
       2:  {
       3:      public static void RunCodeGenerator(this TextTransformation transformation, ITextTemplatingEngineHost host, Generator generator)
       4:      {
       5:          using (TransformContextScope contextScope = new TransformContextScope(transformation, host))
       6:          {
       7:              generator.Run();
       8:          }
       9:      }
      10:  }
    作者:Artech
    出处:http://artech.cnblogs.com/
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
     
  • 相关阅读:
    Vue:不同页面之间的传递参数(二)---query
    Vue:不同页面之间的传递参数--params
    Vue:将px转化为rem,适配移动端
    CSS变量:声明全局变量,让编写更快捷 --root
    前端:viewport-fit解决iphoneX的“刘海”问题
    css小技巧---:not()
    分析Array.apply(null, { length: 5 })
    对比jquery获取属性的方法props、attr、data
    在浏览器里点击input输入框输入,会展示默认的历史下拉菜单
    记录下 Markdown 语法
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/2920158.html
Copyright © 2020-2023  润新知