Importación del esquema para generar clases

Para generar clases de esquemas utilizables con Windows Communication Foundation (WCF), use la clase XsdDataContractImporter. En este tema se describen el proceso y variaciones.

El proceso de importación

El proceso de importación del esquema se inicia con XmlSchemaSet y genera CodeCompileUnit.

XmlSchemaSet forma parte del Modelo de objetos de esquemas (SOM) de .NET Framework que representa un conjunto de documentos de esquema (XSD) de lenguaje de definición de esquemas XML. Para crear un objeto XmlSchemaSet a partir de un conjunto de documentos XSD, deserialice cada documento en un objeto (utilizando el XmlSchema) XmlSerializer y agregue estos objetos a un nuevo XmlSchemaSet.

CodeCompileUnit forma parte del Modelo de objetos de documento (CodeDOM) de Código de .NET Framework que representa el código de .NET Framework de una manera abstracta. Para generar el código real a partir de CodeCompileUnit, utilice una subclase de la clase CodeDomProvider, como CSharpCodeProvider o la clase VBCodeProvider.

Importar un esquema

  1. Creación de una instancia de XsdDataContractImporter.

  2. Opcional. Pase CodeCompileUnit en el constructor. Los tipos generados durante la importación del esquema se agregan a la instancia CodeCompileUnit en lugar de iniciarse con un CodeCompileUniten blanco.

  3. Opcional. Llame a uno de los métodos CanImport. El método determina si el esquema determinado es un esquema de contrato de datos válido y si se puede importar. El método CanImport tiene las mismas sobrecargas que Import (el paso siguiente).

  4. Llame a uno de los métodos Import sobrecargados, por ejemplo, el método Import(XmlSchemaSet).

    La sobrecarga más simple toma XmlSchemaSet e importa todos los tipos, incluidos los tipos anónimos, situados en ese esquema establecido. Otras sobrecargas le permiten especificar el tipo XSD o una lista de tipos a importar (en el formulario de XmlQualifiedName o una colección de los objetos XmlQualifiedName ). En este caso, solo se importan los tipos especificados. Una sobrecarga toma XmlSchemaElement que importa un elemento determinado fuera de XmlSchemaSet, así como su tipo asociado (si es anónimo o no). Esta sobrecarga devuelve XmlQualifiedName, que representa el nombre de contrato de datos del tipo generado para este elemento.

    Varias llamadas del método Import producen como resultado varios elementos que se agregan al mismo CodeCompileUnit. Un tipo no se genera en CodeCompileUnit si ya existe en este. Llame varias veces Import al mismo XsdDataContractImporter en lugar de utilizar varios objetos XsdDataContractImporter. Ésta es la manera recomendada de evitar que se generen tipos duplicados.

    Nota

    Si se produce un error durante la importación, CodeCompileUnit estará en un estado imprevisible. Si utiliza CodeCompileUnit como resultado de una importación en la que se ha producido un error, podría exponerse a vulnerabilidades de seguridad.

  5. Obtenga acceso a CodeCompileUnit a través de la propiedad CodeCompileUnit .

Importar opciones: Personalizar los tipos generados

Puede establecer la propiedad Options de XsdDataContractImporter a una instancia de la clase ImportOptions para administrar varios aspectos del proceso de importación. Hay varias opciones influyen directamente en los tipos generados.

Controlar el nivel de acceso (GenerateInternal o el modificador /interno)

Esto corresponde al modificador /internal de la Herramienta de utilidad de metadatos de ServiceModel (Svcutil.exe).

Normalmente, los tipos públicos se generan a partir del esquema, con campos privados y propiedades de miembro de datos públicos coincidentes. Para generar tipos internos en su lugar, establezca la propiedad GenerateInternal en true.

El ejemplo siguiente muestra un esquema transformado en una clase interna cuando la propiedad GenerateInternal está establecida en 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

Controlar espacios de nombres (Espacios de nombres o el modificador /espacio de nombres)

Esto corresponde al modificador /namespace de la herramienta Svcutil.exe.

Normalmente, los tipos generados a partir del esquema se generan en los espacios de nombres de .NET Framework, y cada espacio de nombres XSD corresponde a un espacio de nombres de .NET Framework determinado según una asignación descrita en Referencia de esquema de contrato de datos. Puede personalizar esta asignación mediante la propiedad Namespaces a Dictionary<TKey,TValue>. Si se encuentra un espacio de nombres XSD determinado en el diccionario, el espacio de nombres de .NET Framework correspondiente también se toma de su diccionario.

Por ejemplo, considere el siguiente esquema:

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

El ejemplo siguiente utiliza la propiedad Namespaces para asignar el espacio de nombres http://schemas.contoso.com/carSchema a "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"))

Agregar SerializableAttribute (GenerateSerializable o el modificador /serializable)

Esto corresponde al modificador /serializable de la herramienta Svcutil.exe.

En algunas ocasiones, es importante que los tipos generados a partir del esquema se puedan utilizar con los motores de la serialización en tiempo de ejecución de .NET Framework. Esto resulta útil cuando se usan tipos para la comunicación remota de .NET Framework. Para habilitarlo, debe aplicar el atributo SerializableAttribute a los tipos generados además del atributo DataContractAttribute normal. Se genera el atributo automáticamente si la opción de importación GenerateSerializable está establecida en true.

El ejemplo siguiente muestra la clase Vehicle generada con la opción establecida de importación GenerateSerializable a 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

Agregar compatibilidad de enlace de datos (EnableDataBinding o el modificador /enableDataBinding)

Esto corresponde al modificador /enableDataBinding de la herramienta Svcutil.exe.

Es posible que quiera enlazar los tipos generados a partir del esquema a los componentes de interfaz gráfica de usuario para que las actualizaciones en las instancias de estos tipos se actualicen automáticamente en la Interfaz de usuario. XsdDataContractImporter puede generar tipos que implementan la interfaz INotifyPropertyChanged de manera que cualquier cambio de propiedad active un evento. Si está generando tipos para utilizarlos con un entorno de programación de Interfaz de usuario de cliente que admite esta interfaz (como Windows Presentation Foundation (WPF)), establezca la propiedad EnableDataBinding en true para habilitar esta característica.

El ejemplo siguiente muestra la clase Vehicle generada con EnableDataBinding establecida a 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

Opciones de importación: Elegir tipos de colección

Dos patrones especiales en XML representan colecciones de elementos: listas de elementos y asociaciones entre un elemento y otro. A continuación se muestra un ejemplo de una lista de cadenas:

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

A continuación, se muestra un ejemplo de una asociación entre una cadena y un entero (city name y 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>

Nota

Las asociaciones también se podrían considerar una lista. Por ejemplo, puede ver la asociación anterior como una lista de objetos city complejos que tienen dos campos (un campo de cadena y un campo de valor entero). Ambos patrones tienen una representación en el Esquema XSD. No hay ningún modo de diferenciar entre una lista y una asociación, así que estos patrones siempre se tratan como listas a menos que haya una anotación especial en el esquema específica para WCF. La anotación indica que un patrón determinado representa una asociación. Para más información, consulte Referencia de esquema de contrato de datos.

Normalmente, una lista se importa como un contrato de datos de colección derivado de una Lista Genérica o como una matriz de .NET Framework, según si el esquema sigue el patrón estándar de denominación o no para las colecciones. Esto se describe con más detalle en Tipos de colección en contratos de datos. Las asociaciones se importan normalmente como Dictionary<TKey,TValue> o como un contrato de datos de colección derivado del objeto de diccionario. Por ejemplo, considere el siguiente esquema:

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

Esto se importaría como se indica a continuación (se muestran los campos en lugar de las propiedades para facilitar la lectura).

[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

Se pueden personalizar los tipos de colección generados para estos patrones del esquema. Por ejemplo, puede querer generar colecciones derivadas de BindingList<T> en lugar de la clase List<T> para enlazar el tipo a un cuadro de lista y para que se actualice automáticamente cuando cambien los contenidos de la colección. Para ello, establezca la propiedad ReferencedCollectionTypes de la clase ImportOptions en una lista de tipos de colección que se utilizarán (de ahora en adelante conocidos como los tipos con referencia). Cuando se importa una colección, se digitaliza esta lista de tipos de colección con referencia y, si se encuentra uno, se utiliza la colección que mejor coincida. Solamente se comparan asociaciones con tipos que implementan la interfaz genérica o no genérica IDictionary, mientras que las listas se comparan con cualquier tipo de colección compatible.

Por ejemplo, si la propiedad ReferencedCollectionTypes está establecida en BindingList<T>, el tipo people en el ejemplo anterior se genera como se indica a continuación.

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

Se considera que un tipo genérico cerrado es el que más coincide. Por ejemplo, si los tipos BindingList(Of Integer) y ArrayList se pasan a la colección de tipos con referencia, las listas de enteros encontradas en el esquema se importan como BindingList(Of Integer). Cualquier otra lista, por ejemplo, List(Of String), se importa como ArrayList.

Si un tipo que implementa la interfaz IDictionary genérica se agrega a la colección de tipos con referencia, sus parámetros de tipo deben estar o totalmente abiertos o totalmente cerrados.

No se permiten duplicados. Por ejemplo, no puede agregar List(Of Integer) sino Collection(Of Integer) a los tipos con referencia. Eso haría imposible determinar cuál se debería utilizar cuando una lista de enteros se encuentra en esquema. Los duplicados se detectarán solo si hay un tipo en esquema que expone el problema de los duplicados. Por ejemplo, si el esquema importado no contiene listas de enteros, puede tener List(Of Integer) y Collection(Of Integer) en la colección de tipos con referencia, pero no tendrán ningún efecto.

El mecanismo de tipos de colección con referencia funciona igual de bien para las colecciones de tipos complejos (incluso las colecciones de otras colecciones) y no solo para las colecciones de primitivos.

La propiedad ReferencedCollectionTypes corresponde al modificador /collectionType de la herramienta SvcUtil.exe. Tenga en cuenta que, para hacer referencia a varios tipos de colección, se debe especificar varias veces el modificador /collectionType. Si el tipo no está en MsCorLib.dll, se debe hacer referencia también a su ensamblado mediante el modificador /reference.

Opciones de importación: Hacer referencia a tipos existentes

De vez en cuando, los tipos en esquema corresponden a los tipos de .NET Framework existentes y no es necesario generar estos tipos desde el principio. (Esta sección solo se aplica a tipos que no son de colección. Para los tipos de colección, consulte la sección anterior).

Por ejemplo, puede tener un tipo de contrato de datos "Persona" estándar para toda la compañía que siempre quiere utilizar cuando represente una persona. Cuando algún servicio utiliza este tipo y su esquema aparece en los metadatos del servicio, puede reutilizar el tipo Person existente cuando importe este esquema en lugar de generar uno nuevo para cada servicio.

Para ello, pase una lista de tipos de .NET Framework que quiere reutilizar en la colección que la propiedad ReferencedTypes devuelve en la clase ImportOptions. Si alguno de estos tipos tiene un nombre de contrato de datos y espacio de nombres que coincide con el nombre y espacio de nombres de un tipo de esquema, se realiza una comparación estructural. Si se determina que los tipos coinciden en nombres y en estructuras, el tipo de .NET Framework existente se reutiliza en lugar de generar uno nuevo. Si solo coincide el nombre y no la estructura, se produce una excepción. Tenga en cuenta que no hay ninguna concesión para controlar las versiones al hacer referencia a tipos (por ejemplo, agregar nuevos miembros de datos opcionales). Las estructuras deben coincidir con exactitud.

Es legal agregar varios tipos con el mismo nombre de contrato de datos y espacio de nombres a la colección de tipos con referencia, siempre que no se importe ningún tipo de esquema con ese nombre y espacio de nombres. Esto le permite agregar con facilidad todos los tipos en un ensamblado a la colección sin preocuparse por los duplicados para los tipos que realmente no se producen en esquema.

La propiedad ReferencedTypes corresponde al modificador /reference en ciertos modos de operación de la herramienta Svcutil.exe.

Nota

Cuando se utiliza Svcutil.exe o (en Visual Studio) las herramientas Agregar referencia de servicio, se hace referencia a todos los tipos en MsCorLib.dll automáticamente.

Opciones de importación: Importar esquema Non-DataContract como tipos IXmlSerializable

XsdDataContractImporter admite un subconjunto limitado del esquema. Si las construcciones de esquema no compatibles están presentes (por ejemplo, atributos XML), se produce un error en los intentos de importación y se genera una excepción. Sin embargo, si se establece la propiedad ImportXmlType en true, se extiende el intervalo de esquema compatible. Cuando se establece en true, XsdDataContractImporter genera tipos que implementan la interfaz IXmlSerializable. Esto permite el acceso directo a la representación XML de estos tipos.

Consideraciones de diseño
  • Puede ser difícil trabajar directamente con la representación XML con tipos débiles. Considere utilizar un motor de serialización alternativo, como por ejemplo XmlSerializer, para trabajar con esquema no compatible con contratos de datos fuertemente tipados. Para más información, consulte Utilización de la clase XmlSerializer.

  • XsdDataContractImporter no puede importar algunas construcciones de esquema incluso cuando la propiedad ImportXmlType está establecida en true. De nuevo, considere utilizar XmlSerializer para estos casos.

  • Las construcciones exactas de esquema admitidas cuando ImportXmlType es true o false se describen en Referencia de esquema de contrato de datos.

  • El esquema para tipos IXmlSerializable generados que no retienen la fidelidad cuando se importan y exportan. Es decir, exportar el esquema a partir de los tipos generados e importar como clases no devuelve el esquema original.

Es posible combinar la opción ImportXmlType con la opción ReferencedTypes descrita previamente. Para los tipos que tienen que ser generados como implementaciones IXmlSerializable, se omite la comprobación estructural cuando se utiliza la característica ReferencedTypes.

La opción ImportXmlType corresponde al modificador /importXmlTypes de la herramienta Svcutil.exe.

Trabajar con tipos generados IXmlSerializable

Los tipos IXmlSerializable generados contienen un campo privado, denominado "nodesField", que devuelve una matriz de los objetos XmlNode. Cuando se deserializa una instancia de este tipo, usted puede tener acceso directamente a los datos XML a través de este campo utilizando el Modelo de objetos del documento XML. Cuando se serializa una instancia de este tipo, usted puede establecer este campo a los datos XML deseados y se serializará.

Esto se logra a través de la implementación IXmlSerializable. En el tipo IXmlSerializable generado, la implementación ReadXml llama al método ReadNodes de la clase XmlSerializableServices. El método es un método del asistente que convierte XML proporcionado a través de XmlReader a una matriz de los objetos XmlNode. La implementación WriteXml hace el contrario y convierte la matriz de los objetos XmlNode a una secuencia de llamadas XmlWriter. Esto se realiza mediante el método WriteNodes.

Se puede ejecutar el proceso de exportación del esquema en las clases IXmlSerializable generadas. Como se ha expresado previamente, no se le devolverá el esquema original. En su lugar, obtendrá el tipo XSD estándar "anyType", que es un carácter comodín para cualquier tipo XSD.

Esto se logra mediante la aplicación del atributo XmlSchemaProviderAttribute a las clases IXmlSerializable generadas y la especificación de un método que llama al método AddDefaultSchema para generar el tipo "anyType".

Nota

El tipo XmlSerializableServices existe solamente para admitir esta característica determinada. No se recomienda utilizarlo para cualquier otro propósito.

Opciones de importación: Opciones avanzadas

Estas son opciones de importación avanzadas:

Consulte también