从数据到代码通过代码生成机制实现强类型编程[下篇]

  在《上篇》中,我们实现了将保存有消息条目的XML向CodeDOM的转换,即是将XML文件生成一个CodeCompileUnit对象,而该CodeCompileUnit对象反映出来的DOM层次和我们将会生成的代码文件向匹配。在下篇中,我们将实现整个代码生成系统的第二个步骤——通过VS的Custom Tool实现数据(保存消息条目的XML)向代码文件的自动转换。

  一、让MessageCodeGenerator继承BaseCodeGeneratorWithSite

  在《上篇》我们创建了MessageCodeGenerator类,定义了如下一个BuildCodeObject方法实现将一个XmlDocument转换成一个CodeCompileUnit对象。

1: namespace Artech.CodeDomGenerator 2: { 3: public class MessageCodeGenerator 4: { 5: // Others... 6: public CodeCompileUnit BuildCodeObject(XmlDocument messages); 7: } 8: }

  现在我们需要做的是让这个MessageCodeGenerator继承一个特殊的类:BaseCodeGeneratorWithSite。BaseCodeGeneratorWithSite所在的程序集名称为Microsoft.VisualStudio.TextTemplating.VSHost.10.0.dll,这是一个Visual Studio SDK的程序集。我们例子采用的是Visual Studio 2010,你可以在如下的目录中找到该程序集:%ProgramFiles%Microsoft Visual Studio 2010 SDK\VisualStudioIntegration\Common\Assemblies\v4.0。如果你没有安装VS 2010 SDK,你可以从这里下载。

  除了添加对Microsoft.VisualStudio.TextTemplating.VSHost.10.0.dll程序集的引用外,你还需要添加两个额外的程序集引用:Microsoft.VisualStudio.OLE.Interop.dll和Microsoft.VisualStudio.Shell.Interop.dll,它们所在的目录分别是%ProgramFiles%Microsoft Visual Studio 2010 SDK\VisualStudioIntegration\Common\Assemblies\v4.0和%ProgramFiles%Microsoft Visual Studio 2010 SDK\VisualStudioIntegration\Common\Assemblies\v2.0。

  添加了相应的程序集引用,并将BaseCodeGeneratorWithSite这个抽象类作为MessageCodeGenerator的基类后,需要实现如下两个抽象方法:GenerateCode和GetDefaultExtension。

1: namespace Artech.CodeDomGenerator 2: { 3: public class MessageCodeGenerator : BaseCodeGeneratorWithSite 4: { 5: public CodeCompileUnit BuildCodeObject(XmlDocument messages) 6: { 7: //...... 8: } 9: protected override byte[] GenerateCode(string inputFileName, string inputFileContent) 10: { 11: var messageDoc = new XmlDocument(); 12: messageDoc.LoadXml(inputFileContent); 13: var codeObject = BuildCodeObject(messageDoc); 14: CodeDomProvider provider = CodeDomProvider.CreateProvider("CSharp"); 15: CodeGeneratorOptions options = new CodeGeneratorOptions(); 16: options.BracingStyle = "C"; 17: using (StringWriter writer = new StringWriter()) 18: { 19: provider.GenerateCodeFromCompileUnit(codeObject, writer, options); 20: string code = writer.ToString(); 21: byte[] preambleBytes = Encoding.Unicode.GetPreamble(); 22: byte[] codeBytes = Encoding.Unicode.GetBytes(code); 23: byte[] result = new byte[preambleBytes.Length + codeBytes.Length]; 24: Buffer.BlockCopy(preambleBytes, 0, result, 0, preambleBytes.Length); 25: Buffer.BlockCopy(codeBytes, 0, result, preambleBytes.Length, codeBytes.Length); 26: return result; 27: } 28: } 29:  30: public override string GetDefaultExtension() 31: { 32: return ".cs"; 33: } 34: } 35: }

  GenerateCode返回的字节数组表示最终生成的的代码的内容,在这里的逻辑很简单,就是通过CodeDomProvider将CodeCompileUnit转化成基于具体编程语言(在这里我们只考虑C#)的代码。而GetDefaultExtension返回生成的代码文件的扩展名,在这里自然是“.cs”。

  二、将MessageCodeGenerator注册成COM组件

  到目前我们MessageCodeGenerator完全通过托管程序编写,但是VS和扩展是通过COM的方式进行交互的,所以我们需要将MessageCodeGenerator注册成COM组件。我们首先需要做的是对MessageCodeGenerator所在的程序集进行注册。一般地,进行注册的程序集都具有一个强名称,所以我们先对程序集进行签名。这只需要对定义MessageCodeGenerator所在的项目的“签名”选项进行如下设置就可以了。

  我们还需要对程序集的COM可见性进行相应的设置。对于COM可见性的设置,我们只需在AssemblyInfo.cs文件中,添加如下一个ComVisibleAttribute特性并将参数设置成true即可(默认为false)。

1: // Setting ComVisible to false makes the types in this assembly not visible 2: // to COM components. If you need to access a type in this assembly from 3: // COM, set the ComVisible attribute to true on that type. 4: [assembly: ComVisible(true)]

  为了让我们定义的MessageCodeGenerator通过COM组件的形式暴露出来,我们需要功过在器类型上通过应用一个GuidAttribute指定一个唯一标识。这个唯一标识可以通过VS自带的GUID生成器生成。

1: [Guid("F9A0FCB3-864F-4B87-885B-FAEBC860BD64")] 2: public class MessageCodeGenerator : BaseCodeGeneratorWithSite 3: { 4: //Others... 5: }

  程序集的注册通过命令行工具RegAsm.exe完成,我们只需要启动通过VS 2010的命名行工具,执行RegAsm.exe命令对编译生成的程序集进行注册。

1: RegAsm "c:\CodeDOMGenerator\Artech.CodeDomGenerator.CodeGenerator.dll"

  实际上,我们也可以直接通过VS对相应的项目进行相应的设置,让VS在编译完成后自动完成对目标程序基的注册。你只需要在项目设置对话框中的Build页,钩选“Register for COM interop”即可。

  注:由于我们的MessageCodeGenerator内部引用到了另一个程序集Microsoft.VisualStudio.Shell.Interop.dll中的某些类型,你需要通过执行如下RegAsm.exe命令行对该程序基进行注册,并采用/tlb开关生成类型库。

1: RegAsm /tlb "%ProgramFiles%Microsoft Visual Studio 2010 SDK\VisualStudioIntegration\Common\Assemblies\v2.0\Microsoft.VisualStudio.Shell.Interop.dll"

  三、设置注册表

  到目前为止,我们定义的代码生成器MessageCodeGenerator已经通过COM组件的形式暴露出来了,我们需要作的就是让VS能够正常地加载该COM组件,这通过设置VS相关的注册表信息来完成。VS2010与代码生成相关的注册表项定义在HKLM\Software\Microsoft\VisualStudio\10.0\Generators\节点下。该节点下的子节点(Key)均通过相应的GUID表示,不同的GUID实际上表示的是相应的编程语言。其中{164B10B9-B200-11D0-8C61-00A0C91E29D5}代表VB.NET,而C#对应的GUID为下图选中的{FAE04EC1-301F-11d3-BF4B-00C04F79EFBC}。

  现在我们需要在表示C#的节点下创建一个Key,并起名为MessageCodeGenerator,即我们约定的代码生成器的名称。

  如上图所示,我们需要对我们添加的注册表键进行如下三项设置:

(Default)[REG_SZ]:设置代码生成器的表述性信息; CLSID[REG_SZ]:作为COM组件的代码生成器的GUID,即我们在定义MessageCodeGenerator类新通过GuidAttribute特性指定的GUID,注意不要忘了花括号; GeneratesDesignTimeSource[REG_WWORD]: 0或者1,表明是否提供设计时原代码生成的支持  四、通过Custom Tool直接通过XML生成C#代码

  现在我们就可以来直接使用我们我们的MessageCodeGenerator了。现在我们创建一个项目,添加一个用于保存消息的XML文件,比如起名为Messages.xml,内容如下:

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文件,在弹出的上下文菜单中选择Properties选项。你会发现在属性对话框中有个叫作Custom Tool的属性名称,在该项上填写上我们的代码生成器的名称:MessageCodeGenerator。

  此后,当你右击该XML文件时,在上下文菜单中都会多出一个叫做Run Custom Tool的项目,选择它我们的.cs文件将会自动生成,

  该.cs文件和我们在《上篇》给出的代码一模一样。那么我们就可以借助于生成出来的代码,以一种强类型的方式获取相应的、被格式化的消息文本。

1: //------------------------------------------------------------------------------ 2: // <auto-generated> 3: // This code was generated by a tool. 4: // Runtime Version:4.0.30319.1 5: // 6: // Changes to this file may cause incorrect behavior and will be lost if 7: // the code is regenerated. 8: // </auto-generated> 9: //------------------------------------------------------------------------------ 10:  11: namespace Artech.CodeDomGenerator 12: { 13: 14: 15: public class Messages 16: { 17: 18: public class Validation 19: { 20: 21: public static Artech.CodeDomGenerator.MessageEntry MandatoryField = new Artech.CodeDomGenerator.MessageEntry("MandatoryField", "The {0} is mandatory.", "Validation"); 22: 23: public static Artech.CodeDomGenerator.MessageEntry GreaterThan = new Artech.CodeDomGenerator.MessageEntry("GreaterThan", "The {0} must be greater than {1}.", "Validation"); 24: } 25: 26: public class Confirmation 27: { 28: 29: public static Artech.CodeDomGenerator.MessageEntry ReallyDelete = new Artech.CodeDomGenerator.MessageEntry("ReallyDelete", "Do you really want to delete the {0}.", "Confirmation"); 30: } 31: } 32: }  五、将MessageCodeGenerator和文件扩展名绑定

  实际上我们可以看出VS代码生成机制的本质:将一个文件作为源文件(Source),利用相应的生成器生成目标文件(Destination)。至于采用怎样的生成器,则是通过源文件的Custom Tool属性进行匹配的。除了这种需要手工设置文件属性的方式进行源文件和生成器之间的匹配关系外,还具有另一种更为方便的匹配方式:基于源文件扩展名的匹配。

  现在我们的消息文件时通过一个XML文件(文件的结构和扩展名均是XML),如果我们现在给它一种特殊的扩展名,并且将设置源文件扩展名和代码生成器的匹配关系,就无需再手工地为源文件设置Custom Tool这一属性了。

  实际上,我们可以一个简单的注册表设置就可以实现这样的功能。假设作为MessageCodeGenerator的源文件的扩展名为msg(不要认为是OutLook邮件消息),我们住需要在上面提到过的基于某种编程语言的注册表节点下,创建一个以扩展名命名的Key,并将Default值直接设置成代码生成器的名称即可。  现在当你添加一个扩展名为.msg的文件后,Custom Tool自动为你设置成MessageCodeGenerator。无需手工设置,你就可以直接通过Run Custom Tool生成相应的代码文件了。

时间: 2024-11-03 03:34:16

从数据到代码通过代码生成机制实现强类型编程[下篇]的相关文章

从数据到代码——通过代码生成机制实现强类型编程[下篇]

在<上篇>中,我们实现了将保存有消息条目的XML向CodeDOM的转换,即是将XML文件生成一个CodeCompileUnit对象,而该CodeCompileUnit对象反映出来的DOM层次和我们将会生成的代码文件向匹配.在下篇中,我们将实现整个代码生成系统的第二个步骤--通过VS的Custom Tool实现数据(保存消息条目的XML)向代码文件的自动转换. 一.让MessageCodeGenerator继承BaseCodeGeneratorWithSite 在<上篇>我们创建了M

一起谈.NET技术,从数据到代码—通过代码生成机制实现强类型编程[下篇]

在<上篇>中,我们实现了将保存有消息条目的XML向CodeDOM的转换,即是将XML文件生成一个CodeCompileUnit对象,而该CodeCompileUnit对象反映出来的DOM层次和我们将会生成的代码文件向匹配.在下篇中,我们将实现整个代码生成系统的第二个步骤--通过VS的Custom Tool实现数据(保存消息条目的XML)向代码文件的自动转换. 一.让MessageCodeGenerator继承BaseCodeGeneratorWithSite 在<上篇>我们创建了M

从数据到代码通过代码生成机制实现强类型编程[上篇]

我不知道大家对CodeDOM的代码生成机制是否熟悉,但是有一点可以确定:如果你使用过Visual Studio,你就应该体验过它带给我们在编程上的便利.随便列举三种典型的代码生成的场景:在创建强类型DataSet的时候,VS会自动根据Schema生成相应的C#或者VB.NET代码:当我们编辑Resource文件的时候,相应的的后台代码也会自动生成:当我们通过添加Web Reference调用Web Service或者WCF Service的时候,VS会自动生成服务代理的代码和相应的配置.总的来说

通过代码生成机制实现强类型编程-CodeSimth版

        一直想写一个Code生成系列,但写到CodeSimth,发觉在TerryLee 和努力学习的小熊 两位大牛的博客里讲很详尽,所以就像写些示例方面的,但是苦于没有想到写些什么.最近Artech写了两篇从数据到代码--通过代码生成机制实现强类型编程--上篇和下篇,大牛写得是CodeDom的,今天我就想借借大牛的示例写个CodeSimth版的,希望Artech不要怪我,呵呵.我的Code生成技术已经写了CodeDom的见CodeDom系列目录,欢迎各位园友指教.         好直接

从数据到代码——基于T4的代码生成方式

在之前写一篇文章<从数据到代码>(上篇.下篇)中,我通过基于CodeDOM+Custom Tool的代码生成方式实现了将一个XML表示的消息列表转换成了相应的C#代码,从而达到了强类型编程的目的.实际上,我们最常用的代码生成当时不是CodeDOM,而是T4,这是一个更为强大,并且适用范围更广的代码生成技术.今天,我将相同的例子通过T4的方式再实现一次,希望为那些对T4不了解的读者带来一些启示.同时这篇文章将作为后续文章的引子,在此之后,我将通过两篇文章通过具体实例的形式讲述如果在项目将T4为我

一起谈.NET技术,从数据到代码—基于T4的代码生成方式

在之前写一篇文章<从数据到代码>(上篇.下篇)中,我通过基于CodeDOM+Custom Tool的代码生成方式实现了将一个XML表示的消息列表转换成了相应的C#代码,从而达到了强类型编程的目的.实际上,我们最常用的代码生成当时不是CodeDOM,而是T4,这是一个更为强大,并且适用范围更广的代码生成技术.今天,我将相同的例子通过T4的方式再实现一次,希望为那些对T4不了解的读者带来一些启示.同时这篇文章将作为后续文章的引子,在此之后,我将通过两篇文章通过具体实例的形式讲述如果在项目将T4为我

如何使用 Feed4JUnit 进行数据与代码分离的 Java 单元测试

Feed4JUnit 与 JUnit 经常,在应用程序的业务逻辑中存在大量的这样的接口:他们接受不同的输入,然后进行 或验证,或处理,进而完成相同的流程.比如网站的登录入口,用户名和密码都有长度的限制,同时也具有是否允许特殊字 符的限制等,所以在我们进行其单元测试的过程中,根据不同长度的用户名和密码,以及不同的字符组合,只需要提供相同 的测试代码结构,就能完成测试,不同的仅仅测试数据与期望值,但是因为每一个测试方法中的输入参数不同,我们必须为 每一个输入组编写单独的测试用例,从而产生大量冗余代码

框架-JAVA问题:删除数据库中数据的代码,测试不会报错,但实际什么都删不了

问题描述 JAVA问题:删除数据库中数据的代码,测试不会报错,但实际什么都删不了 这些操作做完后,数据库里的t_product表中,id=2的数据依然在,不是刷新的问题,刷新也还在 解决方案 为什么你的三个函数保存.修改.删除方法调用sql的时候都没有传递sql参数值呢? 解决方案二: 调用mapper里面的sql代码的时候,把要删除的id传进去了吗?不应该是sqlsession.delete("",参数);吗? 解决方案三: 参数没带.sqlsession.delect带上id 解决

数据即代码:元驱动编程

Coolshell.cn (感谢 @文艺复兴记(todd) 投递此文) 几个小伙伴在考虑下面这个各个语言都会遇到的问题: 问题:设计一个命令行参数解析API 一个好的命令行参数解析库一般涉及到这几个常见的方面: 1) 支持方便地生成帮助信息 2) 支持子命令,比如:git包含了push, pull, commit等多种子命令 3) 支持单字符选项.多字符选项.标志选项.参数选项等多种选项和位置参数 4) 支持选项默认值,比如:–port选项若未指定认为5037 5) 支持使用模式,比如:tar命