一起谈.NET技术,提供第三种代码生成方式——通过自定义BuildProvider为ASP.NET提供代码生成

  之前写了一些关于代码生成的文章,提供了两种不同方式的代码生成解决方案,即CodeDOM+Custom ToolT4。对于ASP.NET应用,你还有第三种选择——自定义BuildProvider。[文中涉及的源代码从这里下载]

目录
一、BuildProvider是什么?
二、将XML表示的消息转换成VB.NET或者C#代码
三、将XML转换成CodeDOM
四、自定义BuildProvider
五、BuildProvider的应用

  一、BuildProvider是什么?

  对于ASP.NET应用的开发者来说,你可能不知道什么是BuildProvider,但是你几乎无时无刻不在使用它所带来的代码生成机制。当你创建一个.aspx文件的时候,为什么会自动创建对应源代码?当你在该.aspx页面中以XML的方式添加一个按钮,源代码中为什么会自动添加一个同名的属性。实际上,ASP.NET就是通过一个特殊的BuildProvider实现了将.aspx文件内容转换成相应的源代码,这个特殊的.aspx文件就是:PageBuildProvider。基于不同的文件类型,ASP.NET会采用不同的BuildProvider进行源代码的生成。比如UserControlBuildProvider和MasterPageBuildProvider分别实现了基于用户控件文件(.ascx)和母板页(.master)的源代码生成。你可以通过查看%Windows%\Microsoft.NET\Framework\v2.0.50727\CONFIG\web.config看看在默认情况下使用的BuildProvider以及它基于的源文件类型(扩展名)。


1: <?xml version="1.0" encoding="utf-8"?>
2: <configuration>
3: <system.web>
4: ... ...
5: <compilation>
6: <buildProviders>
7: <add extension=".aspx" type="System.Web.Compilation.PageBuildProvider"/>
8: <add extension=".ascx" type="System.Web.Compilation.UserControlBuildProvider"/>
9: <add extension=".master" type="System.Web.Compilation.MasterPageBuildProvider"/>
10: <add extension=".asmx" type="System.Web.Compilation.WebServiceBuildProvider"/>
11: <add extension=".ashx" type="System.Web.Compilation.WebHandlerBuildProvider"/>
12: <add extension=".soap" type="System.Web.Compilation.WebServiceBuildProvider"/>
13: <add extension=".resx" type="System.Web.Compilation.ResXBuildProvider"/>
14: <add extension=".resources" type="System.Web.Compilation.ResourcesBuildProvider"/>
15: <add extension=".wsdl" type="System.Web.Compilation.WsdlBuildProvider"/>
16: <add extension=".xsd" type="System.Web.Compilation.XsdBuildProvider"/>
17: <add extension=".js" type="System.Web.Compilation.ForceCopyBuildProvider"/>
18: <add extension=".lic" type="System.Web.Compilation.IgnoreFileBuildProvider"/>
19: <add extension=".licx" type="System.Web.Compilation.IgnoreFileBuildProvider"/>
20: <add extension=".exclude" type="System.Web.Compilation.IgnoreFileBuildProvider"/>
21: <add extension=".refresh" type="System.Web.Compilation.IgnoreFileBuildProvider"/>
22: <add extension=".svc" type="System.ServiceModel.Activation.ServiceBuildProvider, System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"/>
23: <add extension=".xoml" type="System.ServiceModel.Activation.WorkflowServiceBuildProvider, System.WorkflowServices, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
24: </buildProviders>
25: </compilation>
26: </system.web>
27: </configuration>

  既然ASP.NET可以通过相应的BuildProvider为不同类型的文件生成相应的源代码,我们自然也能自定义BuildProvider实现我们希望的代码生成机制。为了让读者和之前提供的两种方式的代码生成机制作一个对于,我们依然采用相同的应用场景:将以XML表示的数据转换成代码,以实现强类型编程。

  二、将XML表示的消息转换成VB.NET或者C#代码

  可能有些人没有看过之前的文章,所以在这里我再次简单介绍一些我们需要通过代码生成机制实现的场景:无论对于怎么样的应用,我们都需要维护一系列的消息。消息的类型很多,比如验证消息、确认消息、日志消息等。我们一般会将消息储存在一个文件或者数据库中进行维护,并提供一些API来获取相应的消息项。这些API一般都是基于消息的ID来获取的,换句话说,消息获取的方式是以一种“弱类型”的编程方式实现的。如果我们能够根据消息存储的内容动态地生成相应的C#或者VB.NET代码,那么我们就能够以一种强类型的方式来获取相应的消息项了。

  比如说,现在我们定义了如下一个MessageEntry类型来表示一个消息条目。为了简单,我们尽量简化MessageEntry的定义,仅仅保留三个属性Id、Value和Category。Category表示该消息条目所属的类型,你可以根据具体的需要对其分类(比如根据模块名称或者Severity等)。Value是一个消息真实的内容,可以包含一些占位符({0},{1},…{N})。通过指定占位符对用的值,最中格式化后的文本通过Format返回。


1: public class MessageEntry
2: {
3: public string Id { get; private set; }
4: public string Value { get; private set; }
5: public string Category { get; private set; }
6: public MessageEntry(string id, string value, string category)
7: {
8: this.Id = id;
9: this.Value = value;
10: this.Category = category;
11: }
12: public string Format(params object[] args)
13: {
14: return string.Format(this.Value, args);
15: }
16: }

  现在我们所有的消息定义在如下一个XML文件中,<message>XML元素代码一个具体的MessageEntry,相应的属性(Attribute)和MessageEntry的属性(Property)相对应。


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中,定义了两个类别(Validation和Confirmation)的三条MessageEntry。我们需要通过我们的代码生成工具生成一个包含如下C#代码的CS文件。


1: public class Messages {
2: public class Validation {
3: public static Artech.MessageCodeGenerator.MessageEntry MandatoryField = new Artech.MessageCodeGenerator.MessageEntry("MandatoryField", "The {0} is mandatory.", "Validation");
4: public static Artech.MessageCodeGenerator.MessageEntry GreaterThan = new Artech.MessageCodeGenerator.MessageEntry("GreaterThan", "The {0} must be greater than {1}.", "Validation");
5: }
6: public class Confirmation {
7: public static Artech.MessageCodeGenerator.MessageEntry ReallyDelete = new Artech.MessageCodeGenerator.MessageEntry("ReallyDelete", "Do you really want to delete the {0}.", "Confirmation");
8: }
9: }

  三、将XML转换成CodeDOM

  实际BuildProvider也是采用CodeDOM来定义代码的结构,在这之前我已经创建了一个CodeGenerator类实现了如何加载具有上述结构的XML,并生成一个体现最终代码结构的CodeCompileUnit对象。该CodeGenerator的所有代码的定义如下。


1: public class CodeGenerator
2: {
3: public CodeCompileUnit BuildCodeObject(XmlDocument messages)
4: {
5: var codeObject = new CodeCompileUnit();
6: var codeNamespace = new CodeNamespace("Artech.CodeDomGenerator");
7: codeObject.Namespaces.Add(codeNamespace);
8: var codeType = new CodeTypeDeclaration("Messages");
9: codeNamespace.Types.Add(codeType);
10: GenerateCatetoryClasses(codeType, messages);
11: return codeObject;
12: }
13:
14: private void GenerateCatetoryClasses(CodeTypeDeclaration root, XmlDocument messageDoc)
15: {
16: var messageEntries = messageDoc.GetElementsByTagName("message").Cast<XmlElement>();
17: var categories = (from element in messageEntries
18: select element.Attributes["category"].Value).Distinct();
19:
20: foreach (var category in categories)
21: {
22: var categoryType = new CodeTypeDeclaration(category);
23: root.Members.Add(categoryType);
24:
25: foreach (var element in messageDoc.GetElementsByTagName("message").Cast<XmlElement>().
26: Where(element => element.Attributes["category"].Value == category))
27: {
28: GenerateMessageProperty(element, categoryType);
29: }
30: }
31: }
32:
33: private void GenerateMessageProperty(XmlElement messageEntry, CodeTypeDeclaration type)
34: {
35: string id = messageEntry.Attributes["id"].Value;
36: string value = messageEntry.Attributes["value"].Value;
37: string categotry = messageEntry.Attributes["category"].Value;
38:
39: var field = new CodeMemberField(typeof(MessageEntry), id);
40: type.Members.Add(field);
41: field.Attributes = MemberAttributes.Public | MemberAttributes.Static;
42: field.InitExpression = new CodeObjectCreateExpression(
43: typeof(MessageEntry),
44: new CodePrimitiveExpression(id),
45: new CodePrimitiveExpression(value),
46: new CodePrimitiveExpression(categotry));
47: }
48: }

  四、自定义BuildProvider

  现在我们才进行我们的重点,如何通过一个自定义的BuildProvider将以XML形式存储的消息列表转换成相应的C#或者VB.NET代码。为此我们创建一个名为MessageBuildProvider的类,MessageBuildProvider继承自抽象类BuildProvider。因为从XML到CodeDOM的转换已经实现在了上面的CodeGenerator类中,MessageBuildProvider的定义很简单。


1: public class MessageBuildProvider : BuildProvider
2: {
3: public override void GenerateCode(AssemblyBuilder assemblyBuilder)
4: {
5: var messageDoc = new XmlDocument();
6: using (var stream = this.OpenStream())
7: {
8: messageDoc.Load(stream);
9: }
10: var codeObj = new CodeGenerator().BuildCodeObject(messageDoc);
11: assemblyBuilder.AddCodeCompileUnit(this, codeObj);
12: }
13: }

  五、BuildProvider的应用

  自定义的BuildProvider以配置的方式和源文件的类型(扩展名),在这里我们通过一个扩展名为.msg(不代表OutLook的消息文件)来表示上述的存储消息列表的XML。那么,你可以创建一个WebSite,并添加对定义了MessageBuildProvider的Dll引用或者项目引用。然后添加一个XML文件,并将扩展名改成.msg,然后定义如下一段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>

  然后在Web.config中添加如下一段配置以建立MessageBuildProvider和源文件扩展名(.msg)之间的匹配关系。


1: <?xml version="1.0"?>
2: <configuration>
3: <system.web>
4: <compilation debug="false" targetFramework="4.0" >
5: <buildProviders>
6: <add extension=".msg" type="Artech.MessageCodeGenerator.MessageBuildProvider, Artech.MessageCodeGenerator.Lib"/>
7: </buildProviders>
8: </compilation>
9: </system.web>
10: </configuration>

  然后,你在任何该WebSite范围类进行编程的时候,就可以利用VS的职能感知感受到相应的代码已经生成。

  为什么说“感受”得到代码已经被成功生成呢?这是因为不象之前介绍的两种代码生成方式,会显式地创建一个.cs或者.vb物理文件,并自动添加到项目文件。BuildProvider采用的是一种隐式代码生成机制。不过你通过Go to definition菜单可以得到整个生成代码的内容。如果你采用基于C#的WebSite,生成的代码时如下所示。由于CodeDOM的语言无关性,你也可以将MessageBuildProvider用于基于VB.NET的ASP.NET应用。

时间: 2024-10-22 20:40:32

一起谈.NET技术,提供第三种代码生成方式——通过自定义BuildProvider为ASP.NET提供代码生成的相关文章

提供第三种代码生成方式——通过自定义BuildProvider为ASP.NET提供代码生成

之前写了一些关于代码生成的文章,提供了两种不同方式的代码生成解决方案,即CodeDOM+Custom Tool和T4.对于ASP.NET应用,你还有第三种选择--自定义BuildProvider.[文中涉及的源代码从这里下载] 目录 一.BuildProvider是什么? 二.将XML表示的消息转换成VB.NET或者C#代码 三.将XML转换成CodeDOM 四.自定义BuildProvider 五.BuildProvider的应用 一.BuildProvider是什么? 对于ASP.NET应用

提供第三种代码生成方式通过自定义BuildProvider为ASP.NET提供代码生成

之前写了一些关于代码生成的文章,提供了两种不同方式的代码生成解决方案,即CodeDOM+Custom Tool和T4.对于ASP.NET应用,你还有第三种选择--自定义BuildProvider.[文中涉及的源代码从这里下载] 目录 一.BuildProvider是什么? 二.将XML表示的消息转换成VB.NET或者C#代码 三.将XML转换成CodeDOM 四.自定义BuildProvider 五.BuildProvider的应用 一.BuildProvider是什么? 对于ASP.NET应用

微软提供的三种核心服务:Windows+Office 365+Azure

微软提供的三种核心服务:Windows+Office 365+Azure 英文新闻源:http://techcrunch.com/2014/11/10/microsofts-ceo-breaks-down-the-new-soul-of-his-company/中文新闻源:http://techcrunch.cn/2014/11/11/microsofts-ceo-breaks-down-the-new-soul-of-his-company/ 微软目前是全球最大的电脑软件提供商,消费市场.企业

Google AdWords提供的三种出价方式

Google AdWords为我们提供了三种出价方式 : 1,关注点击,即每次点击出价 2,关注展示,即以每千次展示出价 3,关注转化,即以转化效果出价 我们对以上的几种出价方式进行分析,然后得出一些结论,提供给大家一些可以参考的思路. 1,点击出价(CPC),有两个选项,人工出价和自动出价.对于自动出价,我们自己设计每日预算,然后 AdWords 系统会在该预算范围内帮助我们获得尽可能多的点击次数.需要的话,我们可以设置每次点击费用出价上限,确保 AdWords 系统的出价不会超过特定金额.

Android三种方式实现ProgressBar自定义圆形进度条_Android

进度条样式在项目中经常可以见到,下面小编给大家分享Android三种方式实现ProgressBar自定义圆形进度条. Android进度条有4种风格可以使用. 默认值是progressBarStyle. 设置成progressBarStyleSmall后,图标变小. 设置成progressBarStyleLarge后,图标变大 设置成progressBarStyleHorizontal后,变成横向长方形. 自定义圆形进度条ProgressBar的一般有三种方式: 一.通过动画实现 定义res/a

Android三种方式实现ProgressBar自定义圆形进度条

进度条样式在项目中经常可以见到,下面小编给大家分享Android三种方式实现ProgressBar自定义圆形进度条. Android进度条有4种风格可以使用. 默认值是progressBarStyle. 设置成progressBarStyleSmall后,图标变小. 设置成progressBarStyleLarge后,图标变大 设置成progressBarStyleHorizontal后,变成横向长方形. 自定义圆形进度条ProgressBar的一般有三种方式: 一.通过动画实现 定义res/a

求大神提供一个 用java代码写的生成长图 就是把html代码生成成图片,感激不尽!

问题描述 求大神提供一个 用java代码写的生成长图 就是把html代码生成成图片,感激不尽! 新人求帮助,感激不尽! 用java代码写的生成长图 就是把html代码生成成图片, 如果是利用截图实现的话,最好能自己控制截图的大小和位置. 解决方案 不会写--但是有思路--用js的canvas的--drawImage方法-把整个网页格式成字符串-然后画出来

应用Linux远程连接技术常见的三种形式

远程控制是在网络上由一台计算机(主控端Remote/客户端)远距离去控制另一台计算机(被控端Host/服务器端)的技术,这里的远程不是字面意思的远距离,一般指通过网络控制远程计算机,不过,大多数时候我们所说的远程控制往往指在局域网中的远程控制而言. 当操作者使用主控端计算机控制被控端计算机时,就如同坐在被控端计算机的屏幕前一样,可以启动被控端计算机的应用程序,可以使用被控端计算机的文件数据,甚至可以利用被控端计算机的外部打印设备(打印机)和通信设备(http://www.aliyun.com/z

浅谈SQL Server中的三种物理连接操作(性能比较)_MsSql

在SQL Server中,我们所常见的表与表之间的Inner Join,Outer Join都会被执行引擎根据所选的列,数据上是否有索引,所选数据的选择性转化为Loop Join,Merge Join,Hash Join这三种物理连接中的一种.理解这三种物理连接是理解在表连接时解决性能问题的基础,下面我来对这三种连接的原理,适用场景进行描述. 嵌套循环连接(Nested Loop Join) 循环嵌套连接是最基本的连接,正如其名所示那样,需要进行循环嵌套,嵌套循环是三种方式中唯一支持不等式连接的