65.9K
CodeProject 正在变化。 阅读更多。
Home

一个 XSD 到 .NET 语言代码类生成器,为 Microsoft 的 XSD 工具增添了真正的威力

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.96/5 (15投票s)

2004 年 10 月 2 日

11分钟阅读

viewsIcon

340564

该工具扩展了 Microsoft XSD 工具,提供高质量、健壮且易于使用的自动生成代码。

目录

引言

在以文档为中心的环境中,数据通常以 XML 文档的形式持久化。在这种情况下,经常需要在处理这些文档的各个阶段提供业务逻辑。一种方法是使用 System.Xml.XmlDocument 类来“包装” XML 文档,并提供一种修改数据的方式。然而,XmlDocument 主要模拟文档的 XML 结构,而不是文档本身的数据结构。另一种方法是提供一个数据访问层,其中包含模拟实际数据结构和内容的类,并提供序列化支持,以便将数据读取和写入 XML 流。

第二种方法的优势对代码开发人员和维护人员来说非常显著。

  • 这些类可以在其他数据组合类的上下文中重复使用。
  • 开发人员直接处理基于域的类和数据类型,这在语义上更容易理解,编写代码更简洁,并且通常代码更容易阅读和维护。
  • 随着数据定义的变化,代码通常更容易更新。
  • Visual Studio 会自动为这些数据类型提供 IntelliSense 提示,因此编码更快,出错的可能性更小。

W3C XML 模式 (XSD) 到代码类和类属性的映射,尽管复杂,但是一致的,并且代码的自动生成是完全可能的,并且已作为 Visual Studio 中的标准功能集提供。Visual Studio 中包含一个名为 XSD.EXE 的实用程序,它除了其他功能外,还可以从 XSD 生成语言代码。该实用程序提供了一个基于控制台的应用程序,用于生成 C#.NET 和 VB.NET 代码。

此功能供用户生成自己的代码类,但此实用程序背后的功能也用于 Visual Studio 中为 ASP.NET Web 服务生成序列化映射类。

如果将 XSD.EXE 生成的类视为数据访问类,那么查看代码会发现,使用起来相当复杂,并且没有真正提供封装,因为所有属性都定义为公共字段。事实上,代码虽然简单,却相当丑陋,并且不符合生产健壮应用程序代码的许多实践。

本文的目的是演示如何修改 XSD 工具的输出代码,以提供更好的封装、健壮性和易用性。

背景

有几种方法可以“升级”XSD 工具的输出。

  • 通过使用 System.Reflection 命名空间功能来获取类定义,并应用代码模板生成器来生成一组新类,这些类要么“包装”生成的类,要么完全替换它们。这样做的优点是,模板可以提供特定的、复杂的应用程序代码添加(可以在运行时由用户提供!)来生成新的代码类。缺点是必须为每种目标代码语言生成一个模板。互联网上有一些此类示例,有些是程序化地使用 C# 和 VB 语言模板完成的,有些则使用 XSL 样式表执行类似的任务。
  • 另一种更通用的方法是使用 XSD 工具本身使用的相同功能创建 XSD 的 CodeDOM,然后修改 CodeDOM 以提供所需的输出。这样做的优点是,理论上可以直接从 CodeDOM 为任何支持的 .NET 语言生成代码。主要缺点是,在 CodeDOM 级别提供语言代码构造非常繁琐、复杂且容易出错(CodeDOM 编程让我想起汇编编程——事倍功半!)。目前,.NET 中(包括 Rotor 和 Mono 项目中的)真正的 ICodeParser 实现非常缺乏,它们生成的代码不仅仅是接口代码。这意味着目前只能通过 CodeDOM 路线进行现实的代码添加。(ICodeParser 实现者会读取源代码并生成 CodeDOM 程序图。这提供了一种用一种 .NET 语言编写代码,轻松将其转换为 CodeDOM,然后用于生成任何其他 .NET 语言代码的方法。)

在本文中,我将描述 CodeXS 工具如何遵循第二种方法。CodeXS 代码生成功能的核心在此 MSDN 文章中进行了描述。

您也可以在此处下载一个提供 CodeXS 中一些增强功能的工具。

特点

Code-XS 为 XSD.EXE 生成的代码添加了以下功能:

CodeXS 基于可扩展的架构构建

生成代码的基础是从模式 (XSD) 生成 CodeDOM,这可以通过以下方式完成:

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(xs);
    // 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);
    }
}

CodeXS 工具提供了读取 XML 模式并为模式类生成 CodeDOM 的基础。然后,该工具提供了一个设施来挂钩第三方代码修改器程序集,这些程序集提供了工具定义的 ICodeModifier 接口的实现者。然后,以结构化的方式将 CodeDOM 传递给这些代码修改器,这些实现者实际上提供了 XSD 工具生成的 CodeDOM 的代码扩展。一旦这些代码实现者完成,该工具就会将 CodeDOM 分割成每个模式文件的单独 CodeDOM(对于包含的模式),然后为每个文件生成代码输出。

CodeXS 生成器以类库的形式存在。决定将精力投入到提供一个在线工具。CodeXS 生成器使用一个包含标准代码修改器集合的程序集(恰当地命名为“StandardCodeModifier.dll”),并由一个名为 CodeXS 的 ASP.NET Web 服务进行前端。CodeXS 在线工具是一个由 CodeXS Web 服务服务的 ASP.NET 客户端。可以在此处找到该在线工具:

正确使用具有保留关键字类型的元素

一些模式定义了包含保留关键字或类型名称的元素或类型。Global Justice XML (GJXML) 定义了一个复杂的类型来包装名为 'string' 的文本数据。

在代码中使用 'string' 作为类名是可能的,如下所示,对于 C#:

public class @string
{
    ..
};

以及对于 VB:

Public Class [string]
..
End Class

XSD 工具的代码生成确实提供了此类定义,但有时会生成无法正确编译的代码。CodeXS 纠正了此错误,并扩展了功能,以提供此类对象的集合。CodeXS 以这种方式支持重新定义 .NET 定义的大多数保留类型。

支持使用相对 schemaLocation 目录说明符的多包含模式

CodeXS 支持通过目标模式根元素中的 schemaLocation 属性隐式包含所有包含的模式,以及递归地包含包含模式中包含的模式。唯一的限制是目标模式路径被视为所有包含模式的根位置(URL 或目录路径),并且它们的 schemaLocation 指定为相对路径。这在大多数已发布的模式中是成立的。

支持单命名空间或多命名空间、多文件模式

CodeXS 将正确管理所有目标命名空间与目标模式不同的模式的递归包含。CodeXS 还将通过迭代地从源模式文件构建模式来正确管理共享相同目标命名空间的多个模式文件,以生成正确的 XmlSchema 对象。这在以下方法中进行了管理:

private XmlSchemas IncludeSchemas(XmlSchema Parent, Uri SourceUri, 
                           XmlSchemas Schemas, Hashtable AddedSchemas)
{
    foreach(XmlSchemaExternal externalSchema in Parent.Includes)
    {
        try
        {
            Uri schemaUri = new Uri(SourceUri, externalSchema.SchemaLocation);
            string uriPath = this.GetUriPath(schemaUri);
            XmlSchema schema = this.ReadSchema(uriPath);
            if(AddedSchemas[uriPath] == null)
            {
                if(Schemas[schema.TargetNamespace] != null)
                {
                    XmlSchema compSchema = Schemas[schema.TargetNamespace];
                    foreach(XmlSchemaObject schemaObj in schema.Items)
                    {
                        try
                        {
                            compSchema.Items.Add(schemaObj);
                        }
                        catch { /*ignore*/ }
                    }
                }
                else
                {
                    Schemas.Add(schema);
                }
                AddedSchemas[uriPath] = schema;
                this.IncludeSchemas(schema, schemaUri, Schemas, AddedSchemas);
            }
        }
        catch { /*ignore*/ }
    }
    return Schemas;
}

多文件代码生成,每个模式文件都有对应的代码文件

最初,CodeXS 支持 XSD 工具的默认设置,并为整个模式集生成一个代码文件。

生成的 C# 和 VB 文件大小都超过 3 MB。在 Visual Studio 中编辑这些文件已经足够困难了 - 在 VB 编辑器中,这几乎是不可能的,因为编辑器变得非常慢,以至于无法进行交互。因此,决定根据元素和类型定义在模式文件中的位置来分割文件,从而生成与模式文件有效对应的代码文件。在大多数情况下,文件大小问题得到了解决。一个额外的好处是,定义得到了很好的划分,特别是如果模式设计者仔细进行了数据定义划分。这一点在 Amber Alert 模式中很明显。

其中,整个美国 NCIC 数据库定义(以及其他美国国家数据定义)与 GJXML 定义的其他部分分开,使得复杂模式的结构更容易理解。

字段设为私有,并为每个字段定义 Set/get 属性

这可能是增强的 XSD 工具应该提供的第一件事。显然,对实际数据字段进行数据保护很重要,通过将字段声明为 private 并提供 get/set 属性可以实现这一点。这也意味着,当字段值被修改时,业务规则可以集中在一个地方(父对象的属性)进行编码。

无界模式元素集存储在类型化集合中

那么,一个像样的 XSD 扩展工具应该允许的第二件事是——一种方法来管理同一类型的大量元素对象,而不是 XSD 工具生成的未初始化类型数组。CodeXS 生成类型化集合,这些集合继承自 System.Collections.CollectionBase,支持添加、插入和删除的标准操作,以及使用 foreach(..) 等构造进行完整的枚举,以及完整的数组索引功能。

当元素被引用但尚未创建时,默认构造该元素

CodeXS 会在子对象通过父对象的“get”属性被引用,并且该对象尚不存在时,自动(默认)构造该元素和属性。这尽可能地完成——对于模式选择元素的情况,必须由应用程序代码显式构造。生成的代码文件中的一个片段说明了这一点:

public IncidentType Incident
{
    get
    {
        // Default construction if null reference
        if ((this._incident == null))
        {
            this._incident = new IncidentType();
        }
        return this._incident;
    }
    set
    {
        this._incident = value;
    }
}

这使得模式代码类更容易使用,因为应用程序代码在需要时大多不必构造对象。

正确处理默认的模式属性值

XSD 工具会向具有定义默认值的模式属性添加 DefaultValueAttribute。在生成的代码中,分配的值总是正确的,但是当属性也是必需的时,标准序列化会忽略在 XML 中添加该属性,将其视为可选属性。这通常意味着序列化失败。CodeXS 通过从属性的字段(或属性)定义中删除 DefaultValueAttribute 来解决此问题。

正确处理限定和非限定的模式元素和属性形式

XSD 工具在所有情况下都不能正确处理模式元素或模式属性的元素非/限定形式。这可能导致序列化错误,特别是对于非常复杂的模式,其中元素和属性通常都是限定的。CodeXS 纠正了这个问题,并且在大多数情况下似乎都能正常工作。

正确生成根元素中的 schemaLocation 或 noNamespaceSchemaLocation 属性

CodeXS 尝试在生成的代码的最终 XML 输出中正确生成这些根元素属性。执行此操作的代码位于 Serializer 类(Serializer.cs.vb)中,并生成正确的 URL 或本地磁盘路径。这意味着,如果您在 XMLSpy 等 XML 编辑器中验证由 CodeXS 代码类生成的 XML 文件,则无需将其指向模式文件的正确位置。W3C 将这些属性视为仅提示:您不需要提供模式文件的实际位置。其后果是,您花费大量时间查找模式来验证——在开发过程中令人沮丧。

一些语言修复构造,以避免编译错误

除了某些已发布的模式“不考虑”使用 .NET 保留类型名称外,其他保留的语言关键字也会引起问题。这似乎对 VB 代码生成更普遍,部分原因是语言定义不区分大小写。除了扫描并根据需要更改语言定义名称,并与语言关键字词典进行比较之外,没有真正的解决方法——这是一项非常艰巨的任务,CodeXS 并不尝试。相反,这些是通过特定的 ICodeModifier 实现者插件以临时方式进行的。

标准序列化支持,可轻松地与 XML 字符串进行序列化和反序列化

CodeXS 提供了一个单独的代码文件(Serializer.cs.vb),它使用 System.Xml.Serialization.XmlSerializer 类提供常见的序列化支持。它提供了与 XML 格式 strings 的基本序列化支持。目的是Serializer类可以轻松修改以提供更多功能,甚至可以根据需要提供非 XML 序列化。

符合 VS/nDoc/MSDN 的广泛文档注释

CodeXS 会自动为生成的每个类和方法生成符合 VS/nDoc 的文档。目的是这些可能在代码生成后手动修改。目前正在开发的另一个可能性是添加模式注解/注释作为文档,尽管许多已发布的模式并未利用这些。目前,CodeXS 不为枚举类型提供附加文档。

完全符合 ASP.NET Web 服务

XSD 工具生成的代码类可以用作 ASP.NET Web 服务的 Web 方法的参数类型和返回值类型。CodeXS 生成的代码提供完全相同的功能。

历史

  • 2004 年 9 月 11 日:版本 0.50 ß:第一个版本

许可证

本文未附加明确的许可证,但可能在文章文本或下载文件本身中包含使用条款。如有疑问,请通过下面的讨论区联系作者。

作者可能使用的许可证列表可以在此处找到。

© . All rights reserved.