播放九霄惊魂电影:.NET 框架中的 XML:在 .NET 框架中使用 XML 架构执行代码生成(1)

来源:百度文库 编辑:偶看新闻 时间:2024/05/10 23:32:18
摘要:了解类型化数据集和 xsd.exe 工具所生成的类之间的区别,以及如何通过重用支持该代码生成过程的基础结构类来扩展这一过程,同时保持与 XmlSerializer 的兼容性。(23 页打印页)
  
  
  内容
  
  本页内容
   简介
   基础类
   扩展 XSD 处理
   XmlSerializer 的内部原理
   通过 CodeDom 自定义
   映射技巧
   小结
  
  简介
  自动代码生成 — 无论是数据访问层、业务实体类还是用户界面 — 可以大大提高开发人员的生产效率。这一生成过程可以基于许多输入,例如数据库、任意 XML 文件、UML 关系图等。Microsoft?Visual Studio?.NET 随附了对从 W3C XML 架构文件 (XSD) 进行代码生成的内置支持,分为两种形式:类型化数据集以及与 XmlSerializer 配合使用的自定义类。
  
  XSD 文件描述了允许包含在 XML 文档中以便被该文档视为有效的内容。由于需要以类型安全的方式对数据进行处理(这些数据最终将被序列化为 XML 数据以供使用),因此产生了各种将 XSD 转换为类的方法。我们可以回想一下,XSD 并“不是”作为一种描述对象及其关系的手段而创建的。已经存在一种更好的格式可用于该目的,它就是 UML,并且已被广泛用来对应用程序进行建模以及根据模型生成代码。因此,在 .NET 及其面向对象的编程 (OOP) 概念以及 XSD 的概念之间存在某些(预料的)不匹配现象。当您将 XSD 映射到类时,请记住这一点。
  
  也就是说,可以将 CLR 类型系统视为 XSD 的子集:它支持一些无法映射到常规 OO 概念的功能。因此,如果您只是使用 XSD 来对类进行建模,而不是对文档进行建模,您很可能找不到任何冲突。
  
  在本文的其余部分,我们将讨论类型化数据集方法,还将讨论通过 xsd.exe 工具生成的自定义类如何有助于得到更好的解决方案,以及如何扩展和自定义从 XSD 到类的生成过程的输出。
  
  为了最深入地领会本文的内容,您需要对 CodeDom 有一些基本的了解。
  
  类型化数据集有什么问题?
  类型化数据集正在越来越多地用于表示业务实体,也就是说,用于充当应用程序各个层之间的实体数据传送器,甚至充当 Web 服务的输出。与“正常的”数据集不同,类型化数据集很有吸引力,因为您还可以获得对表、行、列等的类型化访问。然而,它们并非没有代价和/或限制:
  
  • 实现开销:数据集包含许多可能不为您的实体所需的功能,如更改跟踪、类似于 SQL 的查询、数据视图、大量事件等等。
  
  • 性能:与 XML 之间的序列化速度不够快。XmlSerializer 的性能很容易超过它。
  
  • 互操作性:对于返回类型化数据集的 Web 服务的非 .NET 客户端而言,可能难以解决。
  
  • XML 结构:许多分层的(而且完全有效的)文档及其架构无法扁平化为表模型。
  
  获取有关类型化数据集的更多信息。
  
  
  因此,除非数据集的其他功能普遍对您有用,否则使用类型化数据集进行数据传递可能不是最佳选择。值得庆幸的是,还有另一个可以利用的选择。
  
  XmlSerializer 和自定义类
  XmlSerializer 改善了 XML 数据处理方法。通过序列化特性,XmlSerializer 能够根据对象的 XML 表示形式还原对象,并且能够反序列化到 XML 形式。此外,它还能够以非常有效的方式完成这些工作,因为它可以生成动态编译的基于 XmlReader 的(因而也是流式的)类,该类专门用于序列化(以及反序列化)具体的类型。所以,它确实非常快捷。
  
  阅读有关 XML 序列化特性的更多内容。
  
  当然,猜测使用哪些特性以便符合某个 XSD 绝对不是一件好玩的事情。为了解决这个问题,.NET SDK 随附了一个可以帮助您完成艰苦工作的实用工具:xsd.exe。它是一个命令行应用程序,能够根据 XSD 文件生成类型化数据集和自定义类。自定义类在生成后具有相应的 XML 序列化特性,因此在进行序列化时,可以保证完全忠实于架构。
  
  阅读 Don Box 对 XSD 以及 CLR 映射和特性的介绍。
  
  迄今为止,一切都很好。我们具有有效且快速的方法将 XML 转换为对象或者将对象转换为 XML,并且我们具有能够为我们生成类的工具。问题在于,我们有时希望得到与所生成的内容稍有不同的内容。例如,xsd.exe 所生成的类无法数据绑定到 Windows 窗体网格,因为它查找属性而不是公共字段来显示。我们可能希望在许多地方添加自己的自定义特性,将数组更改为类型化集合,等等。当然,我们在做这些事情的时候,应保证在序列化时能够与 XSD 兼容。
  
  自定义 XSD 将明显改变所生成的类的形式。如果您只是期望将 PascalCaseIf 变成实际的 XML 标准以便使用 camelCase,那么我建议您三思而后行。MS 的一些即将问世的产品表明它们将要使用 PascalCase 来表示 XML,以便使它们更好地支持 .NET。
  
  如果您需要进行更多的与上述自定义类似的自定义,您的选择是什么?人们几乎普遍认为 xsd.exe 是不可扩展的,并且没有办法对其进行自定义。这是不准确的,因为 .NET XML 团队实际上向我们提供了恰好可供该工具使用的类。您将需要自己动手使用 CodeDom 以便利用它们,但自定义程度只受到您需要的限制!
  
  您可以在下列文章中阅读有关 CodeDom 的内容:
  
  Generating and Compiling Source Code Dynamically in Multiple Languages
  
  Generate .NET Code in Any Language Using CodeDOM
  
  返回页首
  基础类
  一种从 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 类型等。
  
  为了使用这些类,我们将遵循一个基本的工作流程,如下所述:
  
  1.
   加载架构(原则上加载一个)。
  
  2.
   为每个顶级 XSD 元素派生一系列映射。
  
  3.
   将这些映射导出到 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 实例的属性,进一步自定义生成的代码格式和其他选项。有关可用的选项,请参阅 MSDN 文档。
  
  在编译该控制台应用程序后,我可以生成与 xsd.exe 工具所生成的完全相同的代码。有了这一功能,使我完全不必再依赖该工具,并且我不再需要知道该工具是否已安装或者位于何处,也不再需要为它启动新的进程,等等。然而,每当我修改架构以后,都需要一遍遍地从命令行运行它,这是很不理想的。Microsoft?Visual Studio?.NET 使开发人员可以通过所谓的自定义工具来利用设计时代码生成。其中一个例子是类型化数据集,当您使用它时(尽管不必具体指定),都会有一个自定义工具在您每次保存数据集 XSD 文件时对其进行处理,并自动生成相应的“代码隐藏”类。
  
  有关构建自定义工具的内容超出了本文的范围,但您可以阅读更多有关将我迄今为止所编写的代码转换为该网络日记张贴中的自定义工具的内容。该工具的代码包含在本文的下载内容中,您可以通过将“XsdCodeGen”自定义工具名称指定给 XSD 文件属性来简单地使用它。注册方法在随附的自述文件中进行了说明。
  
  即使我能够找到更容易使用的自定义工具,但是将 xsd.exe 工具替换为另一个执行完全相同任务的工具并没有太大意义,不是吗?毕竟,我们完成这些工作的原因就是为了改变这种做法!因此,让我们从这一底线开始对其进行自定义。