Importing Schema to Generate Classes

To generate classes from schemas that are usable with Windows Communication Foundation (WCF), use the XsdDataContractImporter class. This topic describes the process and variations.

The Import Process

The schema import process starts with an XmlSchemaSet and produces a CodeCompileUnit.

The XmlSchemaSet is a part of the .NET Framework’s Schema Object Model (SOM) that represents a set of XML Schema definition language (XSD) schema documents. To create an XmlSchemaSet object from a set of XSD documents, deserialize each document into an XmlSchema object (using the XmlSerializer) and add these objects to a new XmlSchemaSet.

The CodeCompileUnit is part of the .NET Framework’s Code Document Object Model (CodeDOM) that represents .NET Framework code in an abstract way. To generate the actual code from a CodeCompileUnit, use a subclass of the CodeDomProvider class, such as the CSharpCodeProvider or VBCodeProvider class.

To import a schema

  1. Create an instance of the XsdDataContractImporter.

  2. Optional. Pass a CodeCompileUnit in the constructor. The types generated during schema import are added to this CodeCompileUnit instance instead of starting with a blank CodeCompileUnit.

  3. Optional. Call one of the CanImport methods. The method determines whether the given schema is a valid data contract schema and can be imported. The CanImport method has the same overloads as Import (the next step).

  4. Call one of the overloaded Import methods, for example, the Import(XmlSchemaSet) method.

    The simplest overload takes an XmlSchemaSet and imports all types, including anonymous types, found in that schema set. Other overloads allow you to specify the XSD type or a list of types to import (in the form of an XmlQualifiedName or a collection of XmlQualifiedName objects). In this case, only the specified types are imported. An overload takes an XmlSchemaElement that imports a particular element out of the XmlSchemaSet, as well as its associated type (whether it is anonymous or not). This overload returns an XmlQualifiedName, which represents the data contract name of the type generated for this element.

    Multiple calls of the Import method result in multiple items being added to the same CodeCompileUnit. A type is not generated into the CodeCompileUnit if it already exists there. Call Import multiple times on the same XsdDataContractImporter instead of using multiple XsdDataContractImporter objects. This is the recommended way to avoid duplicate types being generated.

    Note

    If there is a failure during import, the CodeCompileUnit will be in an unpredictable state. Using a CodeCompileUnit resulting from a failed import could expose you to security vulnerabilities.

  5. Access the CodeCompileUnit through the CodeCompileUnit property.

Import Options: Customizing the Generated Types

You can set the Options property of the XsdDataContractImporter to an instance of the ImportOptions class to control various aspects of the import process. A number of options directly influence the types that are generated.

Controlling the Access Level (GenerateInternal or the /internal switch)

This corresponds to the /internal switch on the ServiceModel Metadata Utility Tool (Svcutil.exe).

Normally, public types are generated from schema, with private fields and matching public data member properties. To generate internal types instead, set the GenerateInternal property to true.

The following example shows a schema transformed into an internal class when the GenerateInternal property is set to 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

Controlling Namespaces (Namespaces or the /namespace switch)

This corresponds to the /namespace switch on the Svcutil.exe tool.

Normally, types generated from schema are generated into .NET Framework namespaces, with each XSD namespace corresponding to a particular .NET Framework namespace according to a mapping described in Data Contract Schema Reference. You can customize this mapping by the Namespaces property to a Dictionary<TKey,TValue>. If a given XSD namespace is found in the dictionary, the matching .NET Framework namespace is also taken from your dictionary.

For example, consider the following schema.

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

The following example uses the Namespaces property to map the http://schemas.contoso.com/carSchema namespace to "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"))

Adding the SerializableAttribute (GenerateSerializable or the /serializable switch)

This corresponds to the /serializable switch on the Svcutil.exe tool.

Sometimes it is important for the types generated from the schema to be usable with .NET Framework runtime serialization engines. This is useful when using types for .NET Framework remoting. To enable this, you must apply the SerializableAttribute attribute to the generated types in addition to the regular DataContractAttribute attribute. The attribute is generated automatically if the GenerateSerializable import option is set to true.

The following example shows the Vehicle class generated with the GenerateSerializable import option set to 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

Adding Data Binding Support (EnableDataBinding or the /enableDataBinding switch)

This corresponds to the /enableDataBinding switch on the Svcutil.exe tool.

Sometimes, you may want to bind the types generated from the schema to graphical user interface components so that any update to instances of these types will automatically update the UI. The XsdDataContractImporter can generate types that implement the INotifyPropertyChanged interface in such a way that any property change triggers an event. If you are generating types for use with a client UI programming environment that supports this interface (such as Windows Presentation Foundation (WPF)), set the EnableDataBinding property to true to enable this feature.

The following example shows the Vehicle class generated with the EnableDataBinding set to 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

Import Options: Choosing Collection Types

Two special patterns in XML represent collections of items: lists of items and associations between one item and another. The following is an example of a list of strings.

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

The following is an example of an association between a string and an integer (city name and population).

<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>

Note

Any association could also be considered a list. For example, you can view the preceding association as a list of complex city objects that happen to have two fields (a string field and an integer field). Both patterns have a representation in the XSD Schema. There is no way to differentiate between a list and an association, so such patterns are always treated as lists unless a special annotation specific to WCF is present in the schema. The annotation indicates that a given pattern represents an association. For more information, see Data Contract Schema Reference.

Normally, a list is imported as a collection data contract that derives from a Generic List or as a .NET Framework array, depending on whether or not the schema follows the standard naming pattern for collections. This is described in more detail in Collection Types in Data Contracts. Associations are normally imported as either a Dictionary<TKey,TValue> or a collection data contract that derives from the dictionary object. For example, consider the following schema.

<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>

This would be imported as follows (fields are shown instead of properties for readability).

[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

It is possible to customize the collection types that are generated for such schema patterns. For example, you may want to generate collections deriving from the BindingList<T> instead of the List<T> class in order to bind the type to a list box and have it be automatically updated when the contents of the collection change. To do this, set the ReferencedCollectionTypes property of the ImportOptions class to a list of collection types to be used (hereafter known as the referenced types). When importing any collection, this list of referenced collection types is scanned and the best-matching collection is used if one is found. Associations are matched only against types that implement either the generic or the nongeneric IDictionary interface, while lists are matched against any supported collection type.

For example, if the ReferencedCollectionTypes property is set to a BindingList<T>, the people type in the preceding example is generated as follows.

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

A closed generic is considered the best match. For example, if the types BindingList(Of Integer) and ArrayList are passed to the collection of referenced types, any lists of integers found in schema are imported as a BindingList(Of Integer). Any other lists, for example, a List(Of String), are imported as an ArrayList.

If a type that implements the generic IDictionary interface is added to the collection of referenced types, its type parameters must either be fully open or fully closed.

Duplicates are not allowed. For example, you cannot add both a List(Of Integer) and a Collection(Of Integer) to the referenced types. That would make it impossible to determine which should be used when a list of integers is found in schema. Duplicates will be detected only if there is a type in schema that exposes the duplicates problem. For example, if the imported schema does not contain lists of integers, it is allowed to have both the List(Of Integer) and the Collection(Of Integer) in the referenced types collection, but neither will have any effect.

The referenced collection types mechanism works equally well for collections of complex types (including collections of other collections), and not just for collections of primitives.

The ReferencedCollectionTypes property corresponds to the /collectionType switch on the SvcUtil.exe tool. Note that to reference multiple collection types, the /collectionType switch must be specified multiple times. If the type is not in the MsCorLib.dll, its assembly must also be referenced using the /reference switch.

Import Options: Referencing Existing Types

Occasionally, types in schema correspond to existing .NET Framework types, and there is no need to generate these types from scratch. (This section applies only to noncollection types. For collection types, see the preceding section.)

For example, you may have a standard company-wide "Person" data contract type that you always want used when representing a person. Whenever some service makes use of this type, and its schema appears in the service metadata, you may want to reuse the existing Person type when importing this schema instead of generating a new one for every service.

To do this, pass a list of .NET Framework types that you want to reuse into the collection the ReferencedTypes property returns on the ImportOptions class. If any of these types have a data contract name and namespace that matches the name and namespace of a schema type, a structural comparison is performed. If it is determined that the types have both matching names and matching structures, the existing .NET Framework type is reused instead of generating a new one. If only the name matches but not the structure, an exception is thrown. Note that there is no allowance for versioning when referencing types (for example, adding new optional data members). The structures must match exactly.

It is legal to add multiple types with the same data contract name and namespace to the referenced types collection, as long as no schema types are imported with that name and namespace. This allows you to easily add all the types in an assembly to the collection without worrying about duplicates for types that do not actually occur in schema.

The ReferencedTypes property corresponds to the /reference switch in certain modes of operation of the Svcutil.exe tool.

Note

When using the Svcutil.exe or (in Visual Studio) the Add Service Reference tools, all of the types in MsCorLib.dll are automatically referenced.

Import Options: Importing Non-DataContract Schema as IXmlSerializable types

The XsdDataContractImporter supports a limited subset of the schema. If unsupported schema constructs are present (for example, XML attributes), the import attempt fails with an exception. However, setting the ImportXmlType property to true extends the range of schema supported. When set to true, the XsdDataContractImporter generates types that implement the IXmlSerializable interface. This enables direct access to the XML representation of these types.

Design Considerations
  • It may be difficult to work with the weakly typed XML representation directly. Consider using an alternative serialization engine, such as the XmlSerializer, to work with schema not compatible with data contracts in a strongly typed way. For more information, see Using the XmlSerializer Class.

  • Some schema constructs cannot be imported by the XsdDataContractImporter even when the ImportXmlType property is set to true. Again, consider using the XmlSerializer for such cases.

  • The exact schema constructs that are supported both when ImportXmlType is true or false are described in Data Contract Schema Reference.

  • Schema for generated IXmlSerializable types do not retain fidelity when imported and exported. That is, exporting the schema from the generated types and importing as classes does not return the original schema.

It is possible to combine the ImportXmlType option with the ReferencedTypes option previously described. For types that have to be generated as IXmlSerializable implementations, the structural check is skipped when using the ReferencedTypes feature.

The ImportXmlType option corresponds to the /importXmlTypes switch on the Svcutil.exe tool.

Working with Generated IXmlSerializable Types

The generated IXmlSerializable types contain a private field, named "nodesField," that returns an array of XmlNode objects. When deserializing an instance of such a type, you can access the XML data directly through this field by using the XML Document Object Model. When serializing an instance of this type, you can set this field to the desired XML data and it will be serialized.

This is accomplished through the IXmlSerializable implementation. In the generated IXmlSerializable type, the ReadXml implementation calls the ReadNodes method of the XmlSerializableServices class. The method is a helper method that converts XML provided through an XmlReader to an array of XmlNode objects. The WriteXml implementation does the opposite and converts the array of XmlNode objects to a sequence of XmlWriter calls. This is accomplished using the WriteNodes method.

It is possible to run the schema export process on the generated IXmlSerializable classes. As previously stated, you will not get the original schema back. Instead, you will get the "anyType" standard XSD type, which is a wildcard for any XSD type.

This is accomplished by applying the XmlSchemaProviderAttribute attribute to the generated IXmlSerializable classes and specifying a method that calls the AddDefaultSchema method to generate the "anyType" type.

Note

The XmlSerializableServices type exists solely to support this particular feature. It is not recommended for use for any other purpose.

Import Options: Advanced Options

The following are advanced import options:

See also