閱讀857 返回首頁    go 小米 go 小米6


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

之前寫了一些關於代碼生成的文章,提供了兩種不同方式的代碼生成解決方案,即CodeDOM+Custom ToolT4。對於ASP.NET應用,你還有第三種選擇——自定義BuildProvider。[文中涉及的源代碼從這裏下載]

目錄
一、BuildProvider是什麼?
二、將XML表示的消息轉換成VB.NET或者C#代碼
三、將XML轉換成CodeDOM
四、自定義BuildProvider
五、BuildProvider的應用

對於ASP.NET應用的開發者來說,你可能不知道什麼是BuildProvider,但是你幾乎無時無刻不在使用它所帶來的代碼生成機製。當你創建一個.aspx文件的時候,為什麼會自動創建對應源代碼?當你在該.aspx頁麵中以XML的方式添加一個按鈕,源代碼中為什麼會自動添加一個同名的屬性。實際上,ASP.NET就是通過一個特殊的BuildProvider實現了將.aspx文件內容轉換成相應的源代碼,這個特殊的.aspx文件就是:。基於不同的文件類型,ASP.NET會采用不同的BuildProvider進行源代碼的生成。比如和分別實現了基於用戶控件文件(.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表示的數據轉換成代碼,以實現強類型編程。

可能有些人沒有看過之前的文章,所以在這裏我再次簡單介紹一些我們需要通過代碼生成機製實現的場景:無論對於怎麼樣的應用,我們都需要維護一係列的消息。消息的類型很多,比如驗證消息、確認消息、日誌消息等。我們一般會將消息儲存在一個文件或者數據庫中進行維護,並提供一些API來獲取相應的消息項。這些API一般都是基於消息的ID來獲取的,換句話說,消息獲取的方式是以一種“”的編程方式實現的。。

比如說,現在我們定義了如下一個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。。

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: }

實際BuildProvider也是采用CodeDOM來定義代碼的結構,在這之前我已經創建了一個CodeGenerator類實現了如何加載具有上述結構的XML,並生成一個體現最終代碼結構的對象。該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將以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以配置的方式和源文件的類型(擴展名),在這裏我們通過一個擴展名為.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


作者:蔣金楠
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
原文鏈接

最後更新:2017-10-27 14:04:17

  上一篇:go  Windows Azure Developer Guidance Map(含PDF下載)
  下一篇:go  我看周馬,以及3Q大戰背後的社會問題