Compartilhar via


Importando esquema para gerar classes

Para gerar classes de esquemas que podem ser usados com o WCF (Windows Communication Foundation), use a classe XsdDataContractImporter. Este tópico descreve o processo e as variações.

O processo de importação

O processo de importação de esquema começa com um XmlSchemaSet e produz um CodeCompileUnit.

O XmlSchemaSet faz parte do SOM (Modelo de Objeto de Esquema) do .NET Framework que representa um conjunto de documentos de esquema XSD (linguagem de definição de esquema XML). Para criar um objeto XmlSchemaSet a partir de um conjunto de documentos XSD, desserialize cada documento em um objeto XmlSchema (usando o XmlSerializer) e adicione esses objetos a um novo XmlSchemaSet.

O CodeCompileUnit faz parte do CodeDOM (Modelo de Objeto de Documento de Código) do .NET Framework que representa .NET Framework código de forma abstrata. Para gerar o código real de CodeCompileUnit, use uma subclasse da classe CodeDomProvider, como a classe CSharpCodeProvider ou VBCodeProvider.

Para importar um esquema

  1. Crie uma instância de XsdDataContractImporter.

  2. Opcional. Passe um CodeCompileUnit no construtor. Os tipos gerados durante a importação de esquema são adicionados a essa instância CodeCompileUnit em vez de começar com uma CodeCompileUnit em branco.

  3. Opcional. Chame um dos métodos CanImport. O método determina se o esquema determinado é um esquema de contrato de dados válido e pode ser importado. O método CanImport tem as mesmas sobrecargas Import que (a próxima etapa).

  4. Chame um dos métodos sobrecarregados Import, por exemplo, o método Import(XmlSchemaSet).

    A sobrecarga mais simples usa XmlSchemaSet e importa todos os tipos, incluindo tipos anônimos, encontrados nesse conjunto de esquemas. Outras sobrecargas permitem que você especifique o tipo XSD ou uma lista de tipos a serem importados (na forma de XmlQualifiedName ou uma coleção de objetos XmlQualifiedName). Nesse caso, somente os tipos especificados são importados. Uma sobrecarga recebe XmlSchemaElement que importa um determinado elemento do XmlSchemaSet, bem como seu tipo associado (se ele é anônimo ou não). Essa sobrecarga retorna XmlQualifiedName, que representa o nome do contrato de dados do tipo gerado para esse elemento.

    Várias chamadas do método Import resultam na adição de vários itens ao mesmo CodeCompileUnit. Um tipo não é gerado no CodeCompileUnit caso de ele já existir lá. Chame Import várias vezes no mesmo XsdDataContractImporter, em vez de usar vários objetos XsdDataContractImporter. Essa é a maneira recomendada de evitar que tipos duplicados sejam gerados.

    Observação

    Se houver uma falha durante a importação, CodeCompileUnit estará em um estado imprevisível. Usar um CodeCompileUnit resultante de uma importação com falha pode expô-lo a vulnerabilidades de segurança.

  5. Acesse a propriedade CodeCompileUnit por meio da propriedade CodeCompileUnit.

Opções de importação: personalizando os tipos gerados

Você pode definir a propriedade Options da instância XsdDataContractImporter para uma instância da classe ImportOptions, para controlar vários aspectos do processo de importação. Várias opções influenciam diretamente os tipos gerados.

Controlando o nível de acesso (GenerateInternal ou o comutador /interno)

Isso corresponde ao comutador /interno na Ferramenta de Utilitário de Metadados do ServiceModel (Svcutil.exe).

Normalmente, os tipos públicos são gerados com base no esquema, com campos privados e propriedades de membros de dados públicos correspondentes. Para gerar tipos internos, defina a propriedade GenerateInternal como true.

O exemplo a seguir mostra um esquema transformado em uma classe interna quando a propriedade GenerateInternal é definida como 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

Controlando namespaces (namespaces ou o comutador /namespace)

Isso corresponde ao comutador /namespace na ferramenta Svcutil.exe.

Normalmente, os tipos gerados a partir do esquema são gerados em namespaces .NET Framework, com cada namespace XSD correspondente a um determinado namespace .NET Framework de acordo com um mapeamento descrito na Referência de Esquema de Contrato de Dados. Você pode personalizar esse mapeamento pela propriedade Namespaces para um Dictionary<TKey,TValue>. Se um determinado namespace XSD for encontrado no dicionário, o namespace de .NET Framework correspondente também será obtido do dicionário.

Por exemplo, considere o seguinte esquema.

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

O exemplo a seguir usa a propriedade Namespaces para mapear o namespace http://schemas.contoso.com/carSchema para "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"))

Adicionando o SerializableAttribute (GenerateSerializable ou o comutador /serializable)

Isso corresponde ao comutador /serializable na ferramenta Svcutil.exe.

Às vezes, é importante que os tipos gerados com base no esquema sejam utilizáveis com mecanismos de serialização de runtime do .NET Framework). Isso é útil ao usar tipos para .NET Framework comunicação remota. Para habilitar isso, você deve aplicar o atributo SerializableAttribute aos tipos gerados, além do atributo regular DataContractAttribute. O atributo será gerado automaticamente se a opção GenerateSerializable de importação estiver definida como true.

O exemplo a seguir mostra a classe Vehicle gerada com a opção de importação GenerateSerializable definida como 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

Adicionando suporte à associação de dados (EnableDataBinding ou o comutador /enableDataBinding)

Isso corresponde ao comutador /enableDataBinding na ferramenta Svcutil.exe.

Às vezes, talvez você queira associar os tipos gerados do esquema a componentes gráficos da interface do usuário para que qualquer atualização para instâncias desses tipos atualize automaticamente a interface do usuário. XsdDataContractImporter pode gerar tipos que implementam a interface INotifyPropertyChanged de forma que qualquer alteração de propriedade dispare um evento. Se você estiver gerando tipos para uso com um ambiente de programação da interface do usuário do cliente que dá suporte a essa interface (como Windows Presentation Foundation (WPF)), defina a propriedade EnableDataBinding para true, a fim de habilitar esse recurso.

O exemplo a seguir mostra a classe Vehicle gerada com EnableDataBinding definida como 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

Opções de importação: escolhendo tipos de coleção

Dois padrões especiais em XML representam coleções de itens: listas de itens e associações entre um item e outro. Veja a seguir um exemplo de uma lista de cadeias de caracteres.

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

Veja a seguir um exemplo de associação entre uma cadeia de caracteres e um inteiro (city name e 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>

Observação

Qualquer associação também pode ser considerada uma lista. Por exemplo, você pode exibir a associação anterior como uma lista de objetos complexos city que têm dois campos (um campo de cadeia de caracteres e um campo inteiro). Ambos os padrões têm uma representação no esquema XSD. Não há como diferenciar entre uma lista e uma associação, portanto, esses padrões são sempre tratados como listas, a menos que uma anotação especial específica ao WCF esteja presente no esquema. A anotação indica que um determinado padrão representa uma associação. Para saber mais, confira Referência de esquema de contrato de dados.

Normalmente, uma lista é importada como um contrato de dados de coleta que deriva de uma Lista Genérica ou como uma matriz de .NET Framework, dependendo se o esquema segue ou não o padrão de nomenclatura padrão para coleções. Isso é descrito mais detalhadamente em Tipos de Coleta em Contratos de Dados. As associações normalmente são importadas como Dictionary<TKey,TValue> ou um contrato de dados de coleta que deriva do objeto de dicionário. Por exemplo, considere o seguinte 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>

Isso seria importado da seguinte maneira (os campos são mostrados em vez de propriedades para legibilidade).

[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

É possível personalizar os tipos de coleção gerados para esses padrões de esquema. Por exemplo, talvez você queira gerar coleções derivadas de BindingList<T> invés da classe List<T> para associar o tipo a uma caixa de listagem e fazer com que ele seja atualizado automaticamente quando o conteúdo da coleção for alterado. Para fazer isso, defina a propriedade ReferencedCollectionTypes da classe ImportOptions como uma lista de tipos de coleção a serem usados (posteriormente conhecidos como tipos referenciados). Ao importar qualquer coleção, essa lista de tipos de coleção referenciados é verificada e a coleção de melhor correspondência é usada se uma for encontrada. As associações são correspondentes somente aos tipos que implementam a interface genérica ou não genérica IDictionary, enquanto as listas são correspondentes a qualquer tipo de coleção com suporte.

Por exemplo, se a propriedade ReferencedCollectionTypes for definida como BindingList<T>, o tipo people no exemplo anterior será gerado da seguinte maneira.

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

Um genérico fechado é considerado a melhor correspondência. Por exemplo, se os tipos BindingList(Of Integer) e ArrayList forem passados para a coleção de tipos referenciados, todas as listas de inteiros encontrados no esquema serão importadas como um BindingList(Of Integer). Todas as outras listas, por exemplo, a List(Of String), são importadas como ArrayList.

Se um tipo que implementa a interface genérica IDictionary for adicionado à coleção de tipos referenciados, seus parâmetros de tipo deverão ser totalmente abertos ou totalmente fechados.

Não são permitidas duplicatas. Por exemplo, você não pode adicionar um List(Of Integer) e um Collection(Of Integer) aos tipos referenciados. Isso tornaria impossível determinar quais devem ser usados quando uma lista de inteiros é encontrada no esquema. As duplicatas serão detectadas somente se houver um tipo no esquema que exponha o problema de duplicatas. Por exemplo, se o esquema importado não contiver listas de inteiros, é permitido ter o List(Of Integer) e o Collection(Of Integer) na coleção de tipos referenciados, mas nenhum deles terá qualquer efeito.

O mecanismo de tipos de coleção referenciados funciona igualmente bem para coleções de tipos complexos (incluindo coleções de outras coleções) e não apenas para coleções de primitivos.

A propriedade ReferencedCollectionTypes corresponde à opção /collectionType na ferramenta SvcUtil.exe. Observe que, para fazer referência a vários tipos de coleção, a opção /collectionType deve ser especificada várias vezes. Se o tipo não estiver no MsCorLib.dll, seu assembly também deverá ser referenciado usando o comutador /reference.

Opções de importação: referenciando tipos existentes

Ocasionalmente, os tipos no esquema correspondem aos tipos de .NET Framework existentes e não há necessidade de gerar esses tipos do zero. (Esta seção se aplica somente a tipos de nãocolleção. Para tipos de coleção, consulte a seção anterior.)

Por exemplo, você pode ter um tipo de contrato de dados "Pessoa" padrão de toda a empresa que você sempre deseja usar ao representar uma pessoa. Sempre que algum serviço usa esse tipo e seu esquema aparece nos metadados de serviço, talvez você queira reutilizar o tipo existente Person ao importar esse esquema em vez de gerar um novo para cada serviço.

Para fazer isso, passe uma lista de tipos de .NET Framework que você deseja reutilizar na coleção que a propriedade ReferencedTypes retorna na classe ImportOptions. Se qualquer um desses tipos tiver um nome de contrato de dados e um namespace que corresponda ao nome e ao namespace de um tipo de esquema, uma comparação estrutural será executada. Se for determinado que os tipos têm nomes correspondentes e estruturas correspondentes, o tipo .NET Framework existente será reutilizado em vez de gerar um novo. Se apenas o nome corresponder, mas não a estrutura, uma exceção será gerada. Observe que não há nenhuma permissão para controle de versão ao referenciar tipos (por exemplo, adicionar novos membros de dados opcionais). As estruturas devem corresponder exatamente.

É legal adicionar vários tipos com o mesmo nome e namespace de contrato de dados à coleção de tipos referenciados, desde que nenhum tipo de esquema seja importado com esse nome e namespace. Isso permite que você adicione facilmente todos os tipos em um assembly à coleção sem se preocupar com duplicatas para tipos que realmente não ocorrem no esquema.

A propriedade ReferencedTypes corresponde à opção /reference em determinados modos de operação da ferramenta Svcutil.exe.

Observação

Ao usar o Svcutil.exe ou (no Visual Studio) as ferramentas Adicionar Referência de Serviço, todos os tipos em MsCorLib.dll são referenciados automaticamente.

Opções de importação: importando esquema não DataContract como tipos IXmlSerializable

O XsdDataContractImporter oferece suporte a um subconjunto limitado do esquema. Se construções de esquema sem suporte estiverem presentes (por exemplo, atributos XML), a tentativa de importação falhará com uma exceção. No entanto, definir a propriedade ImportXmlType para true estende o intervalo de esquema com suporte. Quando definido como true, os tipos XsdDataContractImporter gerados que implementam a interface IXmlSerializable. Isso permite o acesso direto à representação XML desses tipos.

Considerações de criação
  • Pode ser difícil trabalhar diretamente com a representação XML com tipo fraco. Considere usar um mecanismo de serialização alternativo, como o XmlSerializer, para trabalhar com esquema não compatível com contratos de dados de forma fortemente tipada. Para saber mais, confira Uso da classe XmlSerializer.

  • Alguns constructos de esquema não podem ser importados por XsdDataContractImporter mesmo quando a propriedade ImportXmlType é definida como true. Novamente, considere usar XmlSerializer para esses casos.

  • Os constructos de esquema exatos com suporte quando ImportXmlType é true ou false são descritos na Referência de Esquema de Contrato de Dados.

  • O esquema para tipos IXmlSerializable gerados não retém a fidelidade quando importado e exportado. Ou seja, exportar o esquema dos tipos gerados e importar como classes não retorna o esquema original.

É possível combinar a opção ImportXmlType com a opção ReferencedTypes descrita anteriormente. Para tipos que precisam ser gerados como implementações IXmlSerializable, a verificação estrutural é ignorada ao usar o recurso ReferencedTypes.

A opção ImportXmlType corresponde à opção /importXmlTypes na ferramenta Svcutil.exe.

Trabalhando com tipos IXmlSerializable gerados

Os tipos gerados IXmlSerializable contêm um campo privado chamado "nodesField", que retorna uma matriz de objetos XmlNode. Ao desserializar uma instância desse tipo, você pode acessar os dados XML diretamente por meio desse campo usando o Modelo de Objeto de Documento XML. Ao serializar uma instância desse tipo, você pode definir esse campo para os dados XML desejados e ele será serializado.

Isso é feito por meio da implementação IXmlSerializable. No tipo gerado IXmlSerializable, a implementação ReadXml chama o método ReadNodes da classe XmlSerializableServices. O método é um método auxiliar que converte XML fornecido por meio de um XmlReader em uma matriz de objetos XmlNode. A implementação WriteXml faz o oposto e converte a matriz de objetos XmlNode em uma sequência de chamadas XmlWriter. Isso é feito usando o método WriteNodes.

É possível executar o processo de exportação de esquema nas classes IXmlSerializable geradas. Conforme indicado anteriormente, você não obterá o esquema original de volta. Em vez disso, você obterá o tipo XSD padrão "anyType", que é um curinga para qualquer tipo XSD.

Isso é feito aplicando o atributo XmlSchemaProviderAttribute às classes IXmlSerializable geradas e especificando um método que chama o método AddDefaultSchema para gerar o tipo "anyType".

Observação

O tipo XmlSerializableServices existe apenas para dar suporte a esse recurso específico. Ele não é recomendado para uso para qualquer outra finalidade.

Opções de Importação: Opções Avançadas

Veja a seguir as opções avançadas de importação:

Confira também