迷你5207专属论坛

注册

 

发新话题 回复该主题

[DotNet] Visual Studio中的T4代码生成工具 [复制链接]

发表者
微软产品中有两款主要的代码生成工具--CodeDOM和T4。CodeDOM十分难读懂而且对大多数商业应用程序来说并不适用。T4则代表了Text Transformation Templating Toolkit。T4是ASP.NET样式的句法,它拥有直接的文档意义输出功能,嵌入式表达和代码逻辑。  在本文中,我们将学习如何使用微软T4建模语言创建和调试模板。
  如果大家使用的是VS2008,那么可以直接在里面找到T4,如果使用的是VS2005,那你也可以下载DSL工具包。T4可以生成任意文本类型,不足之处是对这一功能的支持并不完整且VS缺少关键功能。不过,好消息是大家可以在环境中添加两个免费工具以便为生成代码建立牢固的基础。稍后将告诉大家如何创建和调试T4模板以及运行T4模板的三种方法,包括托管扩展框架的利用,该架构在代码生成的演化中起到了生态系统的作用。
  T4模板以一个模板指令开始。指令由< #@#>标签指定。除了初始化模板以外,模板指令还可以指示模板语言并授令调试。T4模板包括.NET代码,而语言属性指示了模板中运行代码的语言。默认情况下,T4使用2.0版本语言,但是你也可以在语言属性中添加v3.5的方式改变语言。
  按照下面的模板指令,你可以用< # #>标签将任何文本,附有语块的表达以及代码这三者的结合体包含其中。
  
< #@ template language="C#" debug="true" #>   < #
  int hour = DateTime.Now.Hour;
  if(hour < 12)
  {#>
  Good Morning World!< #
  }
  else if(hour > 12 && hour < 17)
  {#>
  Good Afternoon World!< #
  }
  else
  {#>
  Good Evening World!< #
  }#>
  在这个示例模板中,语块指示输出问候语。VS不会提供着色,这为读取模板增加了难度。笔者推荐大家从Clarius Consulting下载编辑器。Clarius有免费社区版本和更复杂的版本可供使用。
  了解了足够的T4模板相关背景。现在可以打开VS创建任意类型的新项目。添加一个新项目然后选择TextFile类型。用扩展.TT命名文件。在先前的或类似的示例中输入代码。VS将.TT扩展映射到名为TextTemplatingFileGenerator的自定义工具上。该自定义工具为运行简单的模板和学习T4提供默认机制。当你保存模板的时候,自定义工具会运行,调用T4引擎并输出代码至依赖文件。扩展模板文件上的插件符号时,你会发现带有.CS扩展的输出包含了Good Morning World!,Good Afternoon World!或者Good Evening World!信息。
如果你使用VB项目,可能会对生成文件的扩展为.CS而感到惊讶。自定义工具默认.CS,但是你可以通过指定输出指令中的扩展对这一问题进行修复:
  < #@ output extension=".vb" #>
  T4引擎会先分析模板然后创建一个源自TextTransformation类的临时类。这个类会被编译并用来制造输出。语块中的代码被放到了临时类的TransformText性能中。模板的文字文本包含在对Writer方法的调用中。你可以通过引入模板中的断点和单步执行代码的方式对其进行调试。调试的时候,可以访问任何模板中可获取的数据。除了声明的域的性能以外,可以访问基础类。基础类会允许你控制缩进(PushIndent,PopIndent,Clearindent,CurrentIndent),报告事件(Error,Warning,Errors)以及直接访问字符串创建器(Write,WriteLine,GenerationEnvironment)。
  带有语块的代码正在运行,但是不会被视为输出。通常你需要包含计算过的或检索过的域。你可以用< #= #>区分的表达块完成这一操作。此外,由于模板代码成为了方法中的代码,你需要块中的不同类来包含额外方法或额外类。被< #+#>抵消的性能块提供了这种性能。你可以编写上述模板来论证这些性能:
  
< #@ template language="C#" debug="true" #>   < #  int hour = DateTime.Now.Hour;  if(hour < 12)  {#>  Good Morning World!< #  }  else if(hour > 12 && hour < 17)  {#>  Good Afternoon World!< #  }  else  {#>  Good Evening World!< #  }#>
  你用来创建应用程序代码的模板是三种要素的综合体:指导模板逻辑的语句块,用于输出的表达块以及用于方法和类的类性能块。

  除了模板和输出指令以外,T4自身提供了其余四种指令。输入指令提供了命名空间识别,这类似于VB中的Imports。包含指令将另一个T4模板的内容放到了当前模板中。组装指令为模板代码添加了.NET组件,这类似于在VS中添加引用。使用默认工具,组件必须包含完整的文件路径,如果包含指令使用相对路径且所包含的模板保留有额外的包含指令,所有的与第一个或根模板位置相关的相对路径会增加重复使用代码的难度。

 你可以用VS自动生成功能操作模板,但是你可能很快会碰到阻碍:将模板直接放入输出目录中会阻碍代码的重复使用。你不能输送输入参数了。如果没有IntelliSense和句法检查就很难编写.NET代码。很难将生成机制和输出项目结合起来,因为前者破坏了单一责任原则和关注点隔离。笔者更倾向于跳出VS,使用自给自足的模板。如果你想使用VS模型,可以使用CodePlex上的T4工具箱。另一个选择是编写自定义生成工具。自定义工具可以提供T4包装并使用T4替换物解决问题。
  T4工具
  笔者喜欢编写独立且能够运行多模板以创建应用程序的生成工具。如果使用单独工具,你就不会使用创建依赖文件的默认VS行为。阻止默认行为的最简单方式是为模板提供一个.T4扩展。Clarius编辑器仍然能够通过这一扩展识别你的模板。
  生成工具使用自定义T4托管提供额外的功能,如传送参数。托管必须执行TestTemplatingEngineHost,还会执行ServiceProvider。托管对所有与环境相互作用的行为负责:管理对自定义指令处理器的链接,寻找组件以及包含文件,提供服务。通过创建自定义托管,你可以提供额外的功能和数据。
  创建自定义指令可以让你扩展模板作者提供的信息。在模板里面,参数会被视为属性,因此按照惯例,参数是通过自定义指令公布的。将不明确的参数请求包含在模板中得到了更好的包装。
  自定义指令要求一个指令处理器,那么在引擎调用临时类的时候,处理器会将所需的代码插入临时类中。自定义指令处理器源自DirectiveProcessor。其ProcessDirective方法为每个指令调用一次。这个方法创建的代码是指令所需的代码且引擎将文本插入到临时类中。属性指令处理器为每个指令创建了一个.NET属性。
  代码生成和MEF
  这样的代码还有些粗糙。为简化使用T4的过程,笔者创建了以MEF(托管扩展框架)为基础的T4工具。

  代码生成和MEF是完美的搭配。模板成为可以被发现的MEF部件。你可以用替换目录内容来添加或删除模板。元数据,输出服务,甚至是属性指令处理器都成为了MEF部件。工具的目的就是授权创新且灵活的能够满足需求的生态系统。
  值得返回到MEF部件的必须是.NET对象,而T4模板在执行前都只是文本文件。每个T4模板必须由类包装,而类不是MEF共享的部件且工具需要演示查找。由于查找以目录为基础,工具只需抓取特定目录中的T4模板,将其放入MEF部件包装中并确保MEF能够找到部件即可。查找本身是一个MEF部件。
  虽然笔者已经谈到托管参数词典和属性指令处理器之间的相互作用。由于T4模板只是一个文本文件,而MEF包装可以很轻易地将其打开,分析并提取来自属性指令的预计参数列表。属性指令被使用了两次以创建参数词典已经生成用于临时T4类的代码。
  由于MEF创建了一个生态系统,包装不需要明确找到参数值;它首先向MEF容器要求匹配参数名称的数据,然后是匹配类型的数据。为避免不必要的依赖性,要使用仅用于简单类型的名称再将匹配放到数据类型之上。
  虽然笔者只论证了属性指令,但是大家也可以创建任何想要的指令类型。指令处理器通过MEF进行检索,笔者已经提供了简化指令处理器创建过程的基础类。代码生成通常会要求模板用不同代码运行多次以创建多个文件。它可能会违背单一责任原则。工具通过搜索输出特殊IloopValues接口的部件解决了这一问题。如果一个参数受到循环值提供商的支持,那么T4模板会为集合中的每个项目调用一次。
  由于许多文件是输出的,所以每个文件都需要一个单独的名称。为了简化这一过程,工具提供了名为TemplateOutputFileName的特殊参数。以访问模板的数据为基础来指定名称。
  代码生成工具会假设你具备控制能力且知道生成过程中运行的代码内容。MEF部件和T4模板能运行代码,因此就能够运行恶意代码。你需要确保部件和环境中T4模板的完整性。
分享 转发
相信与不相信都是矛盾的.  5207宣!欢迎您来到点滴论坛
TOP
发新话题 回复该主题