提供第三種代碼生成方式——通過自定義BuildProvider為ASP.NET提供代碼生成

作者: Artech  來源: 博客園  發布時間: 2010-11-11 23:14  閱讀: 1592 次  推薦: 0   原文鏈接   [收藏]  

  之前寫了一些關于代碼生成的文章,提供了兩種不同方式的代碼生成解決方案,即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的職能感知感受到相應的代碼已經生成。

image  為什么說“感受”得到代碼已經被成功生成呢?這是因為不象之前介紹的兩種代碼生成方式,會顯式地創建一個.cs或者.vb物理文件,并自動添加到項目文件。BuildProvider采用的是一種隱式代碼生成機制。不過你通過Go to definition菜單可以得到整個生成代碼的內容。如果你采用基于C#的WebSite,生成的代碼時如下所示。由于CodeDOM的語言無關性,你也可以將MessageBuildProvider用于基于VB.NET的ASP.NET應用。

image

0
0
 
 
 

文章列表

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()