.NET框架中的XML基礎類:xsd.exe
.NET框架中的XML基礎類
一種從 XSD 生成代碼的方法是以統(tǒng)一的方式對架構(gòu)對象模型 (SOM) 進行簡單的迭代,并直接根據(jù)該模型編寫代碼。這正是大量為克服 xsd.exe 工具局限而創(chuàng)建的代碼生成器所采取的方法。不過,這需要付出相當大的努力以及編寫大量的代碼,因為我們應考慮 XSD 到 CLR 類型映射、XSD 類型繼承、XML 序列化特性等問題。掌握 SOM 也不是一件輕而易舉的事情。如果無須由我們自己來完成所有工作,而只需添加或修改 xsd.exe 工具的內(nèi)置代碼生成,難道不好嗎?
正像我前面所說的,但與普遍看法不同的是,xsd.exe 用于生成輸出的類就在 System.Xml.Serialization 命名空間中并被聲明為公共類,即使 xsd.exe 工具在某種程度上不允許進行任何類型的自定義。它們中的大多數(shù)確實未進行記載,但我將在這一部分中向您說明如何使用它們。請不要被 MSDN 幫助中的以下聲明嚇?。骸癧TheTopSecretClassName] 類型支持Microsoft? .NET 框架基礎結(jié)構(gòu),并且不適合直接從您的代碼中使用”。我將在不進行胡亂刪改以及不采用任何反射代碼的前提下使用它們。
一種比相當平常的 "StringBuilder.Append" 代碼生成好得多的方法是利用 System.CodeDom 命名空間中的類,而這正是內(nèi)置代碼生成類(從現(xiàn)在開始簡稱為 codegen)所做的。通過 CodeDom 中包含的一些類,我們可以用一種與語言無關(guān)的方式,在所謂的 AST(抽象語法樹)中表示幾乎所有的編程構(gòu)造。稍后,另一個類(代碼生成器)可以對其進行解釋并生成您期望的原始代碼,例如Microsoft? Visual C# 或Microsoft? Visual Basic?.NET 代碼。這就是 .NET 框架中大多數(shù)代碼生成過程的工作方式。
Codegen 方法不僅利用這一點,還通過映射過程來分離架構(gòu)分析和實際的 CodeDom 生成。對于我們希望為其生成代碼的每個架構(gòu)元素,都必須執(zhí)行該映射。從根本上說,它將構(gòu)建一個新的對象以表示分析的結(jié)果,例如它的結(jié)構(gòu)(這將是要為其生成的類型名)、它的成員以及這些成員的 CLR 類型等。
為了使用這些類,我們將遵循一個基本的工作流程,如下所述:
加載架構(gòu)(原則上加載一個)。
為每個頂級 XSD 元素派生一系列映射。
將這些映射導出到 System.CodeDom.CodeDomNamespace。
在此過程中涉及到四個類,它們都定義在 System.Xml.Serialization 命名空間中:
圖 1. 用于獲得 CodeDom 樹的類
可以按以下方式,使用這些類來獲得 CodeDom 樹:
- namespace XsdGenerator
- {
- public sealed class Processor
- {
- public static CodeNamespace Process( string xsdFile,
- string targetNamespace )
- {
- // Load the XmlSchema and its collection.
- XmlSchema xsd;
- using ( FileStream fs = new FileStream( xsdFile, FileMode.Open ) )
- {
- xsd = XmlSchema.Read( fs, null );
- xsd.Compile( null );
- }
- XmlSchemas schemas = new XmlSchemas();
- schemas.Add( xsd );
- // Create the importer for these schemas.
- XmlSchemaImporter importer = new XmlSchemaImporter( schemas );
- // System.CodeDom namespace for the XmlCodeExporter to put classes in.
- CodeNamespace ns = new CodeNamespace( targetNamespace );
- XmlCodeExporter exporter = new XmlCodeExporter( ns );
- // Iterate schema top-level elements and export code for each.
- foreach ( XmlSchemaElement element in xsd.Elements.Values )
- {
- // Import the mapping first.
- XmlTypeMapping mapping = importer.ImportTypeMapping(
- element.QualifiedName );
- // Export the code finally.
- exporter.ExportTypeMapping( mapping );
- }
- return ns;
- }
- }
- }
這些代碼非常簡單,盡管您可能希望在其中添加異常管理代碼。需要注意的一件事情是 XmlSchemaImporter 通過使用類型的限定名來導入類型,然后將其放在相應的 XmlSchema 中。因此,必須將架構(gòu)中的所有全局元素傳遞給它,然后使用 XmlSchema.Elements 集合進行迭代。該集合像 XmlSchemaElement.QualifiedName 一樣,也是在架構(gòu)編譯之后被填充的所謂的 Post Schema Compilation Infoset(即 PSCI,請參閱 MSDN 幫助)的成員。它具有在解析引用、架構(gòu)類型、繼承、包含等之后填充和組織架構(gòu)信息的作用。其功能類似于 DOM Post Validation Infoset(即 PSVI,請參閱 Dare Obasanjo 的 MSDN 文章和 XSD 規(guī)范)。
您可能已經(jīng)注意到 XmlSchemaImporter 工作方式的一個副作用(實際上是一個缺陷):您只能檢索(導入)全局定義的元素的映射。在架構(gòu)中的任何位置局部定義的任何其他元素將無法通過該機制訪問。這具有我將在后面討論的一些后果,它們可能會限制您可以應用的自定義,或者影響我們的架構(gòu)設計。
XmlCodeExporter 類根據(jù)所導入的映射,用類型定義來填充傳遞給其構(gòu)造函數(shù)的 CodeDomNamespace,從而生成所謂的 CodeDom 樹。通過上述方法得到的 CodeDom 就是 xsd.exe 工具在內(nèi)部生成的東西。有了該樹以后,就可以直接將其編譯為程序集,或者生成源代碼。
如果我希望擺脫 xsd.exe 工具,可以輕松地生成使用該類的控制臺應用程序。為達到該目的,我需要根據(jù)收到的 CodeDom 樹生成一個源代碼文件。我通過創(chuàng)建一個適用于用戶所選的目標語言的 CodeDomProvider 來做到這一點:
- static void Main( string[] args )
- {
- if ( args.Length != 4 )
- {
- Console.WriteLine(
- "Usage: XsdGenerator xsdfile namespace outputfile [cs|vb]" );
- return;
- }
- // Get the namespace for the schema.
- CodeNamespace ns = Processor.Process( args[0], args[1] );
- // Create the appropriate generator for the language.
- CodeDomProvider provider;
- if ( args[3] == "cs" )
- provider = new Microsoft.CSharp.CSharpCodeProvider();
- else if ( args[3] == "vb" )
- provider = new Microsoft.VisualBasic.VBCodeProvider();
- else
- throw new ArgumentException( "Invalid language", args[3] );
- // Write the code to the output file.
- using ( StreamWriter sw = new StreamWriter( args[2], false ) )
- {
- provider.CreateGenerator().GenerateCodeFromNamespace(
- ns, sw, new CodeGeneratorOptions() );
- }
- Console.WriteLine( "Finished" );
- Console.Read();
- }
我可以使用生成器所收到的 CodeGeneratorOptions 實例的屬性,進一步自定義生成的代碼格式和其他選項。
在編譯該控制臺應用程序后,我可以生成與 xsd.exe 工具所生成的完全相同的代碼。有了這一功能,使我完全不必再依賴該工具,并且我不再需要知道該工具是否已安裝或者位于何處,也不再需要為它啟動新的進程,等等。然而,每當我修改架構(gòu)以后,都需要一遍遍地從命令行運行它,這是很不理想的。Microsoft?Visual Studio?.NET 使開發(fā)人員可以通過所謂的自定義工具來利用設計時代碼生成。其中一個例子是類型化數(shù)據(jù)集,當您使用它時(盡管不必具體指定),都會有一個自定義工具在您每次保存數(shù)據(jù)集 XSD 文件時對其進行處理,并自動生成相應的“代碼隱藏”類。
有關(guān)構(gòu)建自定義工具的內(nèi)容超出了本文的范圍,但您可以閱讀更多有關(guān)將我迄今為止所編寫的代碼轉(zhuǎn)換為 該網(wǎng)絡日記張貼中的自定義工具的內(nèi)容。該工具的代碼包含在本文的下載內(nèi)容中,您可以通過將“XsdCodeGen”自定義工具名稱指定給 XSD 文件屬性來簡單地使用它。注冊方法在隨附的自述文件中進行了說明。
即使我能夠找到更容易使用的自定義工具,但是將 xsd.exe 工具替換為另一個執(zhí)行完全相同任務的工具并沒有太大意義,不是嗎?畢竟,我們完成這些工作的原因就是為了改變這種做法!因此,讓我們從這一底線開始對其進行自定義。
【編輯推薦】