.NET框架中的XML基础类
一种从 XSD 生成代码的方法是以统一的方式对架构对象模型 (SOM) 进行简单的迭代,并直接根据该模型编写代码。这正是大量为克服 xsd.exe 工具局限而创建的代码生成器所采取的方法。不过,这需要付出相当大的努力以及编写大量的代码,因为我们应考虑 XSD 到 CLR 类型映射、XSD 类型继承、XML 序列化特性等问题。掌握 SOM 也不是一件轻而易举的事情。如果无须由我们自己来完成所有工作,而只需添加或修改 xsd.exe 工具的内置代码生成,难道不好吗?
正像我前面所说的,但与普遍看法不同的是,xsd.exe 用于生成输出的类就在 System.Xml.Serialization 命名空间中并被声明为公共类,即使 xsd.exe 工具在某种程度上不允许进行任何类型的自定义。它们中的大多数确实未进行记载,但我将在这一部分中向您说明如何使用它们。请不要被 MSDN 帮助中的以下声明吓住:“[TheTopSecretClassName] 类型支持Microsoft? .NET 框架基础结构,并且不适合直接从您的代码中使用”。我将在不进行胡乱删改以及不采用任何反射代码的前提下使用它们。
一种比相当平常的 "StringBuilder.Append" 代码生成好得多的方法是利用 System.CodeDom 命名空间中的类,而这正是内置代码生成类(从现在开始简称为 codegen)所做的。通过 CodeDom 中包含的一些类,我们可以用一种与语言无关的方式,在所谓的 AST(抽象语法树)中表示几乎所有的编程构造。稍后,另一个类(代码生成器)可以对其进行解释并生成您期望的原始代码,例如Microsoft? Visual C# 或Microsoft? Visual Basic?.NET 代码。这就是 .NET 框架中大多数代码生成过程的工作方式。
Codegen 方法不仅利用这一点,还通过映射过程来分离架构分析和实际的 CodeDom 生成。对于我们希望为其生成代码的每个架构元素,都必须执行该映射。从根本上说,它将构建一个新的对象以表示分析的结果,例如它的结构(这将是要为其生成的类型名)、它的成员以及这些成员的 CLR 类型等。
为了使用这些类,我们将遵循一个基本的工作流程,如下所述:
加载架构(原则上加载一个)。
为每个顶级 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 中。因此,必须将架构中的所有全局元素传递给它,然后使用 XmlSchema.Elements 集合进行迭代。该集合像 XmlSchemaElement.QualifiedName 一样,也是在架构编译之后被填充的所谓的 Post Schema Compilation Infoset(即 PSCI,请参阅 MSDN 帮助)的成员。它具有在解析引用、架构类型、继承、包含等之后填充和组织架构信息的作用。其功能类似于 DOM Post Validation Infoset(即 PSVI,请参阅 Dare Obasanjo 的 MSDN 文章和 XSD 规范)。
您可能已经注意到 XmlSchemaImporter 工作方式的一个副作用(实际上是一个缺陷):您只能检索(导入)全局定义的元素的映射。在架构中的任何位置局部定义的任何其他元素将无法通过该机制访问。这具有我将在后面讨论的一些后果,它们可能会限制您可以应用的自定义,或者影响我们的架构设计。
XmlCodeExporter 类根据所导入的映射,用类型定义来填充传递给其构造函数的 CodeDomNamespace,从而生成所谓的 CodeDom 树。通过上述方法得到的 CodeDom 就是 xsd.exe 工具在内部生成的东西。有了该树以后,就可以直接将其编译为程序集,或者生成源代码。
如果我希望摆脱 xsd.exe 工具,可以轻松地生成使用该类的控制台应用程序。为达到该目的,我需要根据收到的 CodeDom 树生成一个源代码文件。我通过创建一个适用于用户所选的目标语言的 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 工具所生成的完全相同的代码。有了这一功能,使我完全不必再依赖该工具,并且我不再需要知道该工具是否已安装或者位于何处,也不再需要为它启动新的进程,等等。然而,每当我修改架构以后,都需要一遍遍地从命令行运行它,这是很不理想的。Microsoft?Visual Studio?.NET 使开发人员可以通过所谓的自定义工具来利用设计时代码生成。其中一个例子是类型化数据集,当您使用它时(尽管不必具体指定),都会有一个自定义工具在您每次保存数据集 XSD 文件时对其进行处理,并自动生成相应的“代码隐藏”类。
有关构建自定义工具的内容超出了本文的范围,但您可以阅读更多有关将我迄今为止所编写的代码转换为 该网络日记张贴中的自定义工具的内容。该工具的代码包含在本文的下载内容中,您可以通过将“XsdCodeGen”自定义工具名称指定给 XSD 文件属性来简单地使用它。注册方法在随附的自述文件中进行了说明。
即使我能够找到更容易使用的自定义工具,但是将 xsd.exe 工具替换为另一个执行完全相同任务的工具并没有太大意义,不是吗?毕竟,我们完成这些工作的原因就是为了改变这种做法!因此,让我们从这一底线开始对其进行自定义。
【编辑推荐】