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
Create an instance of the XsdDataContractImporter.
Optional. Pass a
CodeCompileUnit
in the constructor. The types generated during schema import are added to thisCodeCompileUnit
instance instead of starting with a blankCodeCompileUnit
.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 asImport
(the next step).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 ofXmlQualifiedName
objects). In this case, only the specified types are imported. An overload takes an XmlSchemaElement that imports a particular element out of theXmlSchemaSet
, as well as its associated type (whether it is anonymous or not). This overload returns anXmlQualifiedName
, 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 sameCodeCompileUnit
. A type is not generated into theCodeCompileUnit
if it already exists there. CallImport
multiple times on the sameXsdDataContractImporter
instead of using multipleXsdDataContractImporter
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 aCodeCompileUnit
resulting from a failed import could expose you to security vulnerabilities.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
orfalse
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:
CodeProvider property. Specify the CodeDomProvider to use to generate the code for the generated classes. The import mechanism attempts to avoid features that the CodeDomProvider does not support. If the CodeProvider is not set, the full set of .NET Framework features is used with no restrictions.
DataContractSurrogate property. An IDataContractSurrogate implementation can be specified with this property. The IDataContractSurrogate customizes the import process. For more information, see Data Contract Surrogates. By default, no surrogate is used.