导入架构以生成类

若要从可用于 Windows Communication Foundation (WCF) 的架构生成类,请使用 XsdDataContractImporter 类。 本主题描述该过程和变体。

导入过程

架构导入过程以一个 XmlSchemaSet 开始并生成一个 CodeCompileUnit

XmlSchemaSet 是 .NET Framework 的架构对象模型 (SOM) 的一部分,SOM 表示 XML 架构定义语言 (XSD) 架构文档集。 若要从 XSD 文档集创建一个 XmlSchemaSet 对象,则将每个文档反序列化到 XmlSchema 对象中(使用 XmlSerializer),并将这些对象添加到新的 XmlSchemaSet 中。

CodeCompileUnit 是 .NET Framework 的代码文档对象模型 (CodeDOM) 的一部分,CodeDOM 以抽象方式表示 .NET Framework 代码。 若要从 CodeCompileUnit 生成实际代码,则使用 CodeDomProvider 类的一个子类,例如 CSharpCodeProviderVBCodeProvider 类。

导入架构

  1. 创建 XsdDataContractImporter 的实例:

  2. 可选。 在构造函数中传递一个 CodeCompileUnit。 将在架构导入期间生成的类型添加到此 CodeCompileUnit 实例中,而不是以空白 CodeCompileUnit 开始。

  3. 可选。 调用 CanImport 方法之一。 该方法确定给定架构是否为有效的数据协定架构,以及是否可以被导入。 CanImport 方法具有与下一步中的 Import 相同的重载。

  4. 调用重载的 Import 方法之一,例如 Import(XmlSchemaSet) 方法。

    最简单的重载采用 XmlSchemaSet 并导入在架构集中找到的所有类型,包括匿名类型。 使用其他重载可以指定 XSD 类型或要导入的类型列表(以 XmlQualifiedNameXmlQualifiedName 对象集合的形式)。 在此情况下,仅导入指定的类型。 存在一个重载,它采用 XmlSchemaElement 导入 XmlSchemaSet 之外的特定元素,以及它的关联类型(无论类型是否为匿名)。 该重载返回一个 XmlQualifiedName,表示为此元素生成的类型的数据协定名称。

    多次调用 Import 方法会向同一个 CodeCompileUnit 添加多个项。 如果某个类型已经存在于 CodeCompileUnit 中,则不会再在其中生成该类型。 在同一个 Import 上多次调用 XsdDataContractImporter,而不是使用多个 XsdDataContractImporter 对象。 建议使用这种方法以避免生成重复的类型。

    注意

    如果导入期间出现了故障,则 CodeCompileUnit 将处于不可预知的状态。 使用从失败的导入中产生的 CodeCompileUnit 可能会使您暴露出安全漏洞。

  5. 通过 CodeCompileUnit 属性访问 CodeCompileUnit

导入选项:自定义生成的类型

可以将 OptionsXsdDataContractImporter 属性设置为 ImportOptions 类的一个实例,以便控制导入过程的各个方面。 许多选项会直接影响生成的类型。

控制访问级别(GenerateInternal 或 /internal 开关)

这对应于 ServiceModel 元数据实用工具 (Svcutil.exe) 中的 /internal 开关。

通常情况下,公共类型是从架构生成的,具有私有字段和匹配的公共数据成员属性。 若要生成内部类型,则将 GenerateInternal 属性设置为 true

下面的示例演示在将 GenerateInternal 属性设置为 true. 时转换为内部类的架构。

[DataContract]
internal partial class Vehicle : IExtensibleDataObject
{
    private int yearField;
    private string colorField;

    [DataMember]
    internal int year
    {
        get { return this.yearField; }
        set { this.yearField = value; }
    }
    [DataMember]
    internal string color
    {
        get { return this.colorField; }
        set { this.colorField = value; }
    }

    private ExtensionDataObject extensionDataField;
    public ExtensionDataObject ExtensionData
    {
        get { return this.extensionDataField; }
        set { this.extensionDataField = value; }
    }
}
Class Vehicle
    Implements IExtensibleDataObject
    Private yearField As Integer
    Private colorField As String

    <DataMember()> _
    Friend Property year() As Integer
        Get
            Return Me.yearField
        End Get
        Set
            Me.yearField = value
        End Set
    End Property

    <DataMember()> _
    Friend Property color() As String
        Get
            Return Me.colorField
        End Get
        Set
            Me.colorField = value
        End Set
    End Property
    Private extensionDataField As ExtensionDataObject

    Public Property ExtensionData() As ExtensionDataObject _
        Implements IExtensibleDataObject.ExtensionData
        Get
            Return Me.extensionDataField
        End Get
        Set(ByVal value As ExtensionDataObject)
            Me.extensionDataField = value
        End Set
    End Property
End Class

控制命名空间(Namespaces 或 /namespace 开关)

这对应于 Svcutil.exe 工具中的 /namespace 开关。

通常,从架构生成的类型将生成到 .NET Framework 命名空间中,并且每个 XSD 命名空间对应于特定的 .NET Framework 命名空间(根据数据协定架构参考中所述的映射)。 通过将 Namespaces 属性设置为 Dictionary<TKey,TValue> 可以自定义此映射。 如果在字典中找到了给定的 XSD 命名空间,则也可从字典中获得匹配的 .NET Framework 命名空间。

例如,考虑下面的架构。

<xs:schema targetNamespace="http://schemas.contoso.com/carSchema">
  <xs:complexType name="Vehicle">
    <!-- details omitted... -->
  </xs:complexType>
</xs:schema>

以下示例使用 Namespaces 属性将 http://schemas.contoso.com/carSchema 命名空间映射到“Contoso.Cars”。

XsdDataContractImporter importer = new XsdDataContractImporter();
importer.Options.Namespaces.Add(new KeyValuePair<string, string>("http://schemas.contoso.com/carSchema", "Contoso.Cars"));
Dim importer As New XsdDataContractImporter
importer.Options.Namespaces.Add(New KeyValuePair(Of String, String)("http://schemas.contoso.com/carSchema", "Contoso.Cars"))

添加 SerializableAttribute(GenerateSerializable 或 /serializable 开关)

这对应于 Svcutil.exe 工具中的 /serializable 开关。

有时,对于从架构生成的类型来说,能够与 .NET Framework 运行时序列化引擎一起使用十分重要。 这在使用这些类型进行 .NET Framework 远程处理时很有用。 若要启用它,则除常规 SerializableAttribute 属性除外,还必须将 DataContractAttribute 属性应用于生成的类型。 如果 GenerateSerializable 导入选项设置为 true,则将自动生成该属性。

下面的示例演示在 Vehicle 导入选项设置为 GenerateSerializable 时生成的 true 类。

[DataContract]
[Serializable]
public partial class Vehicle : IExtensibleDataObject
{
    // Code not shown.
    public ExtensionDataObject ExtensionData
    {
        get
        {
            throw new Exception("The method or operation is not implemented.");
        }
        set
        {
            throw new Exception("The method or operation is not implemented.");
        }
    }
}
<DataContract(), Serializable()> _
Partial Class Vehicle
    Implements IExtensibleDataObject
    Private extensionDataField As ExtensionDataObject

    ' Code not shown.

    Public Property ExtensionData() As ExtensionDataObject _
        Implements IExtensibleDataObject.ExtensionData
        Get
            Return Me.extensionDataField
        End Get
        Set(ByVal value As ExtensionDataObject)
            Me.extensionDataField = value
        End Set
    End Property

End Class

添加数据绑定支持(EnableDataBinding 或 /enableDataBinding 开关)

这对应于 Svcutil.exe 工具中的 /enableDataBinding 开关。

有时,可能需要将从架构生成的类型绑定到图形用户界面组件,这样这些类型实例的任何更新都将自动更新 UI。 XsdDataContractImporter 可以生成实现 INotifyPropertyChanged 接口的类型,以使任何属性更改都触发事件。 如果要生成将与支持此接口(如 Windows Presentation Foundation (WPF))的客户端 UI 编程环境一起使用的类型,请将 EnableDataBinding 属性设置为 true 以启用此功能。

下面的示例演示在 Vehicle 设置为 EnableDataBinding 时生成的 true 类。

[DataContract]
public partial class Vehicle : IExtensibleDataObject, INotifyPropertyChanged
{
    private int yearField;
    private string colorField;

    [DataMember]
    public int year
    {
        get { return this.yearField; }
        set
        {
            if (this.yearField.Equals(value) != true)
            {
                this.yearField = value;
                this.RaisePropertyChanged("year");
            }
        }
    }
    [DataMember]
    public string color
    {
        get { return this.colorField; }
        set
        {
            if (this.colorField.Equals(value) != true)
            {
                this.colorField = value;
                this.RaisePropertyChanged("color");
            }
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    protected void RaisePropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler propertyChanged =
this.PropertyChanged;
        if (propertyChanged != null)
        {
            propertyChanged(this,
new PropertyChangedEventArgs(propertyName));
        }
    }

    private ExtensionDataObject extensionDataField;
    public ExtensionDataObject ExtensionData
    {
        get { return this.extensionDataField; }
        set { this.extensionDataField = value; }
    }
}
Partial Class Vehicle
    Implements IExtensibleDataObject, INotifyPropertyChanged
    Private yearField As Integer
    Private colorField As String

    <DataMember()> _
    Public Property year() As Integer
        Get
            Return Me.yearField
        End Get
        Set
            If Me.yearField.Equals(value) <> True Then
                Me.yearField = value
                Me.RaisePropertyChanged("year")
            End If
        End Set
    End Property

    <DataMember()> _
    Public Property color() As String
        Get
            Return Me.colorField
        End Get
        Set
            If Me.colorField.Equals(value) <> True Then
                Me.colorField = value
                Me.RaisePropertyChanged("color")
            End If
        End Set
    End Property

    Public Event PropertyChanged As PropertyChangedEventHandler _
      Implements INotifyPropertyChanged.PropertyChanged

    Private Sub RaisePropertyChanged(ByVal propertyName As String)
        RaiseEvent PropertyChanged(Me, _
         New PropertyChangedEventArgs(propertyName))
    End Sub

    Private extensionDataField As ExtensionDataObject

    Public Property ExtensionData() As ExtensionDataObject _
        Implements IExtensibleDataObject.ExtensionData
        Get
            Return Me.extensionDataField
        End Get
        Set(ByVal value As ExtensionDataObject)
            Me.extensionDataField = value
        End Set
    End Property

End Class

导入选项:选择集合类型

XML 中存在两种特殊的模式,用于表示项集合:项列表和项与项之间的关联。 下面是一个字符串列表示例。

<People>
  <person>Alice</person>
  <person>Bob</person>
  <person>Charlie</person>
</People>

下面是一个字符串与整数(city namepopulation)之间的关联示例。

<Cities>
  <city>
    <name>Auburn</name>
    <population>40000</population>
  </city>
  <city>
    <name>Bellevue</name>
    <population>80000</population>
  </city>
  <city>
    <name>Cedar Creek</name>
    <population>10000</population>
  </city>
</Cities>

注意

也可将任何关联视为列表。 例如,可以将上面的关联视为一个复杂的 city 对象列表,这些对象碰巧有两个字段(一个字符串字段和一个整数字段)。 这两种模式在 XSD 架构中都有一种表示形式。 由于无法区分列表和关联,因此始终将这种模式作为列表处理,架构中出现特定于 WCF 的特殊批注除外。 该批注指示给定模式表示一个关联。 有关详细信息,请参阅数据协定架构参考

通常情况下,将列表作为派生自泛型列表的集合数据协定导入,或作为 .NET Framework 数组导入,具体取决于架构是否遵循集合的标准命名模式。 有关详细信息,请参阅数据协定中的集合类型。 通常将关联作为 Dictionary<TKey,TValue> 或派生自字典对象的集合数据协定导入。 例如,考虑下面的架构。

<xs:complexType name="Vehicle">
  <xs:sequence>
    <xs:element name="year" type="xs:int"/>
    <xs:element name="color" type="xs:string"/>
    <xs:element name="passengers" type="people"/>
  </xs:sequence>
</xs:complexType>
<xs:complexType name="people">
  <xs:sequence>
    <xs:element name="person" type="xs:string" maxOccurs="unbounded" />
  </xs:sequence>
</xs:complexType>

这将按照下面的方式导入(出于可读性目的,显示的是字段而非属性)。

[DataContract]
public partial class Vehicle : IExtensibleDataObject
{
    [DataMember] public int yearField;
    [DataMember] public string colorField;
    [DataMember] public people passengers;

    // Other code not shown.

    public ExtensionDataObject ExtensionData
    {
        get
        {
            throw new Exception("The method or operation is not implemented.");
        }
        set
        {
            throw new Exception("The method or operation is not implemented.");
        }
    }
}
[CollectionDataContract(ItemName = "person")]
public class people : List<string> { }
Public Partial Class Vehicle
    Implements IExtensibleDataObject

    <DataMember()> _
    Public yearField As Integer
    <DataMember()> _
    Public colorField As String
    <DataMember()> _
    Public passengers As people

    ' Other code not shown.

    Public Property ExtensionData() As ExtensionDataObject _
    Implements IExtensibleDataObject.ExtensionData
        Get
            Throw New Exception("The method or operation is not implemented.")
        End Get
        Set
            Throw New Exception("The method or operation is not implemented.")
        End Set
    End Property
End Class

<CollectionDataContract(ItemName:="person")> _
Public Class people
    Inherits List(Of String)
End Class

可以自定义为此类架构模式生成的集合类型。 例如,可能需要生成派生自 BindingList<T> 而非 List<T> 类的集合,以将此类型绑定到列表框并在集合内容更改时使其自动更新。 为此,将 ReferencedCollectionTypes 类的 ImportOptions 属性设置为要使用的集合类型(以后称为引用的类型)的列表。 导入任何集合时,将扫描引用的集合类型的列表,并使用最佳匹配集合(如果能够找到)。 关联只能与实现泛型或非泛型 IDictionary 接口的类型相匹配,而列表可与任何受支持的集合类型相匹配。

例如,如果 ReferencedCollectionTypes 属性设置为 BindingList<T>,则按下面的方式生成上面示例中的 people 类型。

[CollectionDataContract(ItemName = "person")]
public class people : BindingList<string> { }
<CollectionDataContract(ItemName:="person")> _
Public Class people
    Inherits BindingList(Of String)

将封闭式泛型视为最佳匹配。 例如,如果将类型 BindingList(Of Integer)ArrayList 传递给引用的类型集合,则将在架构中找到的任何整数列表作为 BindingList(Of Integer) 导入。 将任何其他列表(例如 List(Of String))作为 ArrayList 导入。

如果将实现泛型 IDictionary 接口的一个类型添加到引用的类型集合,则其类型参数必须完全打开或完全封闭。

不允许重复。 例如,不能同时向引用的类型添加 List(Of Integer)Collection(Of Integer)。 这会使在架构中找到整数列表时无法确定应该使用哪一个整数。 仅当架构中的某个类型暴露出重复问题时才能检测到重复项。 例如,如果导入的架构中不包含整数列表,则允许引用的类型集合中同时具有 List(Of Integer)Collection(Of Integer),两者都不会产生任何影响。

引用的集合类型机制不仅适用于基元集合,也适用于复杂类型集合(包括其他集合的集合)。

ReferencedCollectionTypes 属性对应于 SvcUtil.exe 工具中的 /collectionType 开关。 请注意,若要引用多个集合类型,则必须多次指定 /collectionType 开关。 如果类型不在 MsCorLib.dll 中,也必须使用 /reference 开关引用其程序集。

导入选项:引用现有类型

有时,架构中的类型对应于现有 .NET Framework 类型,因此不需要从头开始生成这些类型。 (本部分仅适用于非集合类型。有关集合类型,请参阅上一部分。)

例如,您可能有一个标准公司范围内的“Person”数据协定类型,通常在表示人员时需要使用它。 每当某个服务利用该类型且其架构显示在该服务元数据中时,在导入此架构时您可能需要重用现有的 Person 类型,而不需要为每个服务生成新的类型。

为此,将要重用的 .NET Framework 类型列表传递给 ReferencedTypes 属性返回到 ImportOptions 类上的集合。 如果任何这些类型的数据协定名称和命名空间与架构类型的名称和命名空间相匹配,则执行结构比较。 如果比较结果确定这些类型同时具有匹配的名称和匹配的构造,则重用现有的 .NET Framework 类型,而不生成新的类型。 如果仅名称匹配而构造不匹配,则引发异常。 请注意,引用类型(例如,添加新的可选数据成员)时不允许进行版本管理。 构造必须完全匹配。

向引用的类型集合添加多个具有相同数据协定名称和命名空间的类型是合法的,前提是不导入具有该名称和命名空间的架构类型。 这样您就可以轻松地将程序集中的所有类型添加到集合中,而无需担心在架构中并未实际出现的类型重复项。

ReferencedTypes 属性对应于 Svcutil.exe 工具的特定操作模式中的 /reference 开关。

注意

使用 Svcutil.exe 或 Visual Studio 中的“添加服务引用”工具时,将自动引用 MsCorLib.dll 中的所有类型。

导入选项:将 Non-DataContract 架构作为 IXmlSerializable 类型导入

XsdDataContractImporter 支持架构的有限子集。 如果出现不受支持的架构构造(例如,XML 属性),导入尝试会失败并引发异常。 但是,将 ImportXmlType 属性设置为 true 可扩展受支持架构的范围。 设置为 true 时,XsdDataContractImporter 生成可实现 IXmlSerializable 接口的类型。 这样可直接访问这些类型的 XML 表示形式。

设计注意事项
  • 直接使用弱类型 XML 表示形式可能有些困难。 可以考虑使用其他序列化引擎(例如 XmlSerializer),以便以强类型方式使用与数据协定不兼容的架构。 有关详细信息,请参阅使用 XmlSerializer 类

  • 某些架构构造不能通过 XsdDataContractImporter 事件导入,即使是 ImportXmlType 属性设置为 true。 此外,在此情况下可以考虑使用 XmlSerializer

  • 有关当 ImportXmlTypetruefalse 时所支持的确切架构构造,请参阅数据协定架构参考

  • 生成的 IXmlSerializable 类型的架构在导入和导出时不保留保真。 也就是说,从生成的类型导出架构或作为类导入时不返回原始架构。

可以结合使用 ImportXmlType 选项和上述 ReferencedTypes 选项。 对于那些必须作为 IXmlSerializable 实现生成的类型,使用 ReferencedTypes 功能时将跳过结构检查。

ImportXmlType 选项对应于 Svcutil.exe 工具中的 /importXmlTypes 开关。

使用生成的 IXmlSerializable 类型

生成的 IXmlSerializable 类型包含名为“nodesField”的私有字段,该字段返回一个 XmlNode 对象数组。 反序列化此类的实例时,可以使用 XML 文档对象模型直接通过该字段访问 XML 数据。 序列化此类的实例时,可以将该字段设置为所需的 XML 数据,然后它将被序列化。

这是通过 IXmlSerializable 实现完成的。 在生成的 IXmlSerializable 类型中,ReadXml 实现调用 ReadNodes 类的 XmlSerializableServices 方法。 该方法是一个帮助器方法,将所提供的 XML 通过 XmlReader 转换为一个 XmlNode 对象数组。 WriteXml 实现执行相反操作,将 XmlNode 对象数组转换为一个 XmlWriter 调用序列。 这是使用 WriteNodes 方法完成的。

可以在生成的 IXmlSerializable 类上运行架构导出过程。 如上所述,您将不会重新获得原始架构。 相反,你将获得“anyType”标准 XSD 类型,这是任何 XSD 类型的通配符。

完成此操作的方法是将 XmlSchemaProviderAttribute 属性应用到生成的 IXmlSerializable 类并指定一个方法调用 AddDefaultSchema 方法以生成“anyType”类型。

注意

XmlSerializableServices 类型存在的目的只是为了支持此特定功能。 建议不将其用于任何其他目的。

导入选项:高级选项

下面介绍高级导入选项:

另请参阅