Partilhar via


Substitutos de Contrato de Dados

O substituto do contrato de dados é um recurso avançado criado com base no modelo de contrato de dados. Esse recurso foi projetado para ser usado para personalização e substituição de tipos em situações em que os usuários desejam alterar como um tipo é serializado, desserializado ou projetado em metadados. Alguns cenários em que um substituto pode ser usado é quando um contrato de dados não foi especificado para o tipo, campos e propriedades não são marcados com o DataMemberAttribute atributo ou os usuários desejam criar dinamicamente variações de esquema.

A serialização e a desserialização são realizadas com o substituto do contrato de dados ao usar DataContractSerializer para converter do .NET Framework para um formato adequado, como XML. O substituto do contrato de dados também pode ser usado para modificar os metadados exportados para tipos, ao produzir representações de metadados, como documentos de esquema XML (XSD). Após a importação, o código é criado a partir de metadados e o substituto pode ser usado neste caso para personalizar o código gerado também.

Como funciona o substituto

Um substituto funciona mapeando um tipo (o tipo "original") para outro tipo (o tipo "substituto"). O exemplo a seguir mostra o tipo Inventory original e um novo tipo substituto InventorySurrogated . O Inventory tipo não é serializável, mas o InventorySurrogated tipo é:

public class Inventory
{
    public int pencils;
    public int pens;
    public int paper;
}

Como um contrato de dados não foi definido para essa classe, converta a classe em uma classe substituta com um contrato de dados. A classe substituta é mostrada no exemplo a seguir:

[DataContract(Name = "Inventory")]
public class InventorySurrogated
{
    [DataMember]
    public int numpencils;
    [DataMember]
    public int numpaper;
    [DataMember]
    private int numpens;

    public int pens
    {
        get { return numpens; }
        set { numpens = value; }
    }
}

Implementando o IDataContractSurrogate

Para usar o substituto do contrato de dados, implemente a IDataContractSurrogate interface.

A seguir está uma visão geral de cada método de IDataContractSurrogate com uma possível implementação.

GetDataContractType

O GetDataContractType método mapeia um tipo para outro. Esse método é necessário para serialização, desserialização, importação e exportação.

A primeira tarefa é definir quais tipos serão mapeados para outros tipos. Por exemplo:

public Type GetDataContractType(Type type)
{
    Console.WriteLine("GetDataContractType");
    if (typeof(Inventory).IsAssignableFrom(type))
    {
        return typeof(InventorySurrogated);
    }
    return type;
}
  • Na serialização, o mapeamento retornado por esse método é subsequentemente usado para transformar a instância original em uma instância substituída chamando o GetObjectToSerialize método.

  • Na desserialização, o mapeamento retornado por esse método é usado pelo serializador para desserializar em uma instância do tipo substituto. Posteriormente, ele pede GetDeserializedObject para transformar a instância substituída em uma instância do tipo original.

  • Na exportação, o tipo substituto retornado por esse método é refletido para obter o contrato de dados a ser usado para gerar metadados.

  • Na importação, o tipo inicial é alterado para um tipo substituto que é refletido para que o contrato de dados seja usado para fins como suporte de referência.

O Type parâmetro é o tipo do objeto que está sendo serializado, desserializado, importado ou exportado. O GetDataContractType método deve retornar o tipo de entrada se o substituto não manipular o tipo. Caso contrário, devolva o tipo substituto apropriado. Se existirem vários tipos substitutos, vários mapeamentos podem ser definidos neste método.

O GetDataContractType método não é chamado para primitivos de contrato de dados internos, como Int32 ou String. Para outros tipos, como matrizes, tipos definidos pelo usuário e outras estruturas de dados, esse método será chamado para cada tipo.

No exemplo anterior, o método verifica se o parâmetro e Inventory são type comparáveis. Em caso afirmativo, o método o mapeia para InventorySurrogated. Sempre que um esquema de serialização, desserialização, importação ou exportação é chamado, essa função é chamada primeiro para determinar o mapeamento entre tipos.

Método GetObjectToSerialize

O GetObjectToSerialize método converte a instância de tipo original para a instância de tipo substituído. O método é necessário para a serialização.

A próxima etapa é definir a maneira como os dados físicos serão mapeados da instância original para o substituto, implementando o GetObjectToSerialize método. Por exemplo:

public object GetObjectToSerialize(object obj, Type targetType)
{
    Console.WriteLine("GetObjectToSerialize");
    if (obj is Inventory)
    {
        InventorySurrogated isur = new InventorySurrogated();
        isur.numpaper = ((Inventory)obj).paper;
        isur.numpencils = ((Inventory)obj).pencils;
        isur.pens = ((Inventory)obj).pens;
        return isur;
    }
    return obj;
}

O GetObjectToSerialize método é chamado quando um objeto é serializado. Este método transfere dados do tipo original para os campos do tipo substituído. Os campos podem ser mapeados diretamente para campos substitutos, ou manipulações dos dados originais podem ser armazenadas no substituto. Alguns usos possíveis incluem: mapear diretamente os campos, realizar operações nos dados a serem armazenados nos campos substitutos ou armazenar o XML do tipo original no campo substituto.

O targetType parâmetro refere-se ao tipo declarado do membro. Este parâmetro é o tipo substituto retornado pelo GetDataContractType método. O serializador não impõe que o objeto retornado seja atribuível a esse tipo. O obj parâmetro é o objeto a ser serializado e será convertido em seu substituto, se necessário. Esse método deve retornar o objeto de entrada se o substituto não manipular o objeto. Caso contrário, o novo objeto substituto será retornado. O substituto não é chamado se o objeto for nulo. Vários mapeamentos substitutos para diferentes instâncias podem ser definidos dentro desse método.

Ao criar um DataContractSerializer, você pode instruí-lo a preservar referências de objeto. (Para obter mais informações, consulte Serialização e Desserialização.) Isso é feito definindo o preserveObjectReferences parâmetro em seu construtor como true. Nesse caso, o substituto é chamado apenas uma vez para um objeto, uma vez que todas as serializações subsequentes apenas gravam a referência no fluxo. Se preserveObjectReferences estiver definido como false, o substituto será chamado sempre que uma instância for encontrada.

Se o tipo da instância serializada for diferente do tipo declarado, as informações de tipo serão gravadas no fluxo, por exemplo, xsi:type para permitir que a instância seja desserializada na outra extremidade. Este processo ocorre quer o objeto seja substituído ou não.

O exemplo acima converte os dados da instância para os Inventory de InventorySurrogated. Ele verifica o tipo do objeto e executa as manipulações necessárias para converter para o tipo substituto. Nesse caso, os Inventory campos da classe são copiados diretamente para os InventorySurrogated campos da classe.

Método GetDeserializedObject

O GetDeserializedObject método converte a instância de tipo substituída para a instância de tipo original. É necessário para a desserialização.

A próxima tarefa é definir a forma como os dados físicos serão mapeados da instância substituta para o original. Por exemplo:

public object GetDeserializedObject(object obj, Type targetType)
{
    Console.WriteLine("GetDeserializedObject");
    if (obj is InventorySurrogated)
    {
        Inventory invent = new Inventory();
        invent.pens = ((InventorySurrogated)obj).pens;
        invent.pencils = ((InventorySurrogated)obj).numpencils;
        invent.paper = ((InventorySurrogated)obj).numpaper;
        return invent;
    }
    return obj;
}

Esse método é chamado somente durante a desserialização de um objeto. Ele fornece mapeamento reverso de dados para a desserialização do tipo substituto de volta ao seu tipo original. Semelhante ao GetObjectToSerialize método, alguns usos possíveis podem ser para trocar diretamente dados de campo, executar operações nos dados e armazenar dados XML. Ao desserializar, você nem sempre pode obter os valores de dados exatos do original devido a manipulações na conversão de dados.

O targetType parâmetro refere-se ao tipo declarado do membro. Este parâmetro é o tipo substituto retornado pelo GetDataContractType método. O obj parâmetro refere-se ao objeto que foi desserializado. O objeto pode ser convertido de volta ao seu tipo original se for substituído. Esse método retorna o objeto de entrada se o substituto não manipular o objeto. Caso contrário, o objeto desserializado será retornado assim que sua conversão for concluída. Se existirem vários tipos substitutos, você pode fornecer conversão de dados de substituto para tipo primário para cada um, indicando cada tipo e sua conversão.

Ao retornar um objeto, as tabelas de objetos internos são atualizadas com o objeto retornado por esse substituto. Quaisquer referências subsequentes a uma instância obterão a instância substituída das tabelas de objeto.

O exemplo anterior converte objetos do tipo InventorySurrogated de volta para o tipo Inventoryinicial. Nesse caso, os dados são transferidos diretamente de InventorySurrogated volta para seus campos correspondentes em Inventory. Como não há manipulações de dados, cada um dos campos de membro conterá os mesmos valores que antes da serialização.

Método GetCustomDataToExport

Ao exportar um esquema, o GetCustomDataToExport método é opcional. Ele é usado para inserir dados adicionais ou dicas no esquema exportado. Dados adicionais podem ser inseridos no nível de membro ou nível de tipo. Por exemplo:

public object GetCustomDataToExport(System.Reflection.MemberInfo memberInfo, Type dataContractType)
{
    Console.WriteLine("GetCustomDataToExport(Member)");
    System.Reflection.FieldInfo fieldInfo = (System.Reflection.FieldInfo)memberInfo;
    if (fieldInfo.IsPublic)
    {
        return "public";
    }
    else
    {
        return "private";
    }
}

Este método (com duas sobrecargas) permite a inclusão de informações extras nos metadados no nível de membro ou tipo. É possível incluir dicas sobre se um membro é público ou privado, e comentários que seriam preservados durante toda a exportação e importação do esquema. Essas informações perder-se-iam sem este método. Esse método não causa a inserção ou exclusão de membros ou tipos, mas adiciona dados adicionais aos esquemas em qualquer um desses níveis.

O método está sobrecarregado e pode tomar um Type (clrtype parâmetro) ou MemberInfo (memberInfo parâmetro). O segundo parâmetro é sempre um Type (dataContractType parâmetro). Este método é chamado para cada membro e tipo do tipo substituto dataContractType .

Qualquer uma dessas sobrecargas deve retornar um ou um null objeto serializável. Um objeto não nulo será serializado como anotação no esquema exportado. Para a sobrecarga, cada tipo que é exportado Type para o esquema é enviado para este método no primeiro parâmetro junto com o tipo substituído como o dataContractType parâmetro. Para a MemberInfo sobrecarga, cada membro que é exportado para o esquema envia suas informações como o memberInfo parâmetro com o tipo substituído no segundo parâmetro.

Método GetCustomDataToExport (Type, Type)

O método é chamado durante a IDataContractSurrogate.GetCustomDataToExport(Type, Type) exportação de esquema para cada definição de tipo. O método adiciona informações aos tipos dentro do esquema ao exportar. Cada tipo definido é enviado para esse método para determinar se há algum dado adicional que precisa ser incluído no esquema.

Método GetCustomDataToExport (MemberInfo, Type)

O IDataContractSurrogate.GetCustomDataToExport(MemberInfo, Type) é chamado durante a exportação para cada membro nos tipos que são exportados. Esta função permite que você personalize quaisquer comentários para os membros que serão incluídos no esquema após a exportação. As informações para cada membro dentro da classe são enviadas para este método para verificar se algum dado adicional precisa ser adicionado no esquema.

O exemplo acima pesquisa através do dataContractType para cada membro do substituto. Em seguida, ele retorna o modificador de acesso apropriado para cada campo. Sem essa personalização, o valor padrão para modificadores de acesso é público. Portanto, todos os membros seriam definidos como públicos no código gerado usando o esquema exportado, independentemente de quais sejam suas restrições de acesso reais. Quando não estiver usando essa implementação, o membro numpens será público no esquema exportado, mesmo que tenha sido definido no substituto como privado. Através do uso deste método, no esquema exportado, o modificador de acesso pode ser gerado como privado.

Método GetReferencedTypeOnImport

Este método mapeia o substituto Type para o tipo original. Este método é opcional para a importação de esquema.

Ao criar um substituto que importa um esquema e gera código para ele, a próxima tarefa é definir o tipo de uma instância substituta para seu tipo original.

Se o código gerado precisar fazer referência a um tipo de usuário existente, isso será feito implementando o GetReferencedTypeOnImport método.

Ao importar um esquema, esse método é chamado para cada declaração de tipo para mapear o contrato de dados substituídos para um tipo. Os parâmetros typeName de cadeia de caracteres e typeNamespace definem o nome e o namespace do tipo substituído. O valor de retorno para GetReferencedTypeOnImport é usado para determinar se um novo tipo precisa ser gerado. Esse método deve retornar um tipo válido ou null. Para tipos válidos, o tipo retornado será usado como um tipo referenciado no código gerado. Se null for retornado, nenhum tipo será referenciado e um novo tipo deverá ser criado. Se existirem vários substitutos, é possível realizar o mapeamento para cada tipo substituto de volta ao seu tipo inicial.

O customData parâmetro é o objeto originalmente retornado de GetCustomDataToExport. Isso customData é usado quando os autores substitutos desejam inserir dados/dicas extras nos metadados a serem usados durante a importação para gerar código.

Método ProcessImportedType

O ProcessImportedType método personaliza qualquer tipo criado a partir da importação de esquema. Este método é opcional.

Ao importar um esquema, esse método permite que qualquer tipo importado e informações de compilação sejam personalizadas. Por exemplo:

public System.CodeDom.CodeTypeDeclaration ProcessImportedType(System.CodeDom.CodeTypeDeclaration typeDeclaration, System.CodeDom.CodeCompileUnit compileUnit)
{
    Console.WriteLine("ProcessImportedType");
    foreach (CodeTypeMember member in typeDeclaration.Members)
    {
        object memberCustomData = member.UserData[typeof(IDataContractSurrogate)];
        if (memberCustomData != null
          && memberCustomData is string
          && ((string)memberCustomData == "private"))
        {
            member.Attributes = ((member.Attributes & ~MemberAttributes.AccessMask) | MemberAttributes.Private);
        }
    }
    return typeDeclaration;
}

Durante a importação, esse método é chamado para cada tipo gerado. Altere o especificado CodeTypeDeclaration ou modifique o CodeCompileUnitarquivo . Isso inclui alterar o nome, membros, atributos e muitas outras propriedades do CodeTypeDeclaration. Ao processar o CodeCompileUnit, é possível modificar as diretivas, namespaces, assemblies referenciados e vários outros aspetos.

O CodeTypeDeclaration parâmetro contém o código DOM type declaration. O CodeCompileUnit parâmetro permite a modificação para processar o código. O retorno null resulta no descarte da declaração de tipo. Por outro lado, ao retornar um CodeTypeDeclaration, as modificações são preservadas.

Se os dados personalizados forem inseridos durante a exportação de metadados, eles precisarão ser fornecidos ao usuário durante a importação para que possam ser usados. Esses dados personalizados podem ser usados para dicas de modelo de programação ou outros comentários. Cada CodeTypeDeclaration instância inclui CodeTypeMember dados personalizados como a UserData propriedade, convertidos para o IDataContractSurrogate tipo.

O exemplo acima executa algumas alterações no esquema importado. O código preserva os membros privados do tipo original usando um substituto. O modificador de acesso padrão ao importar um esquema é public. Portanto, todos os membros do esquema substituto serão públicos, a menos que sejam modificados, como neste exemplo. Durante a exportação, os dados personalizados são inseridos nos metadados sobre quais membros são privados. O exemplo procura os dados personalizados, verifica se o modificador de acesso é privado e, em seguida, modifica o membro apropriado para ser privado definindo seus atributos. Sem essa personalização, o numpens membro seria definido como público em vez de privado.

Método GetKnownCustomDataTypes

Este método obtém tipos de dados personalizados definidos a partir do esquema. O método é opcional para importação de esquema.

O método é chamado no início da exportação e importação do esquema. O método retorna os tipos de dados personalizados usados no esquema exportado ou importado. O método é passado a Collection<T> (o customDataTypes parâmetro), que é uma coleção de tipos. O método deve adicionar outros tipos conhecidos a esta coleção. Os tipos de dados personalizados conhecidos são necessários para habilitar a serialização e a desserialização de dados personalizados usando o DataContractSerializer. Para obter mais informações, consulte Tipos conhecidos de contrato de dados.

Implementando um substituto

Para usar o substituto do contrato de dados no WCF, você deve seguir alguns procedimentos especiais.

Para usar um substituto para serialização e desserialização

Use o para executar a DataContractSerializer serialização e desserialização de dados com o substituto. O DataContractSerializer é criado pelo DataContractSerializerOperationBehavior. O substituto também deve ser especificado.

Para implementar a serialização e a desserialização
  1. Crie uma instância do para o ServiceHost seu serviço. Para obter instruções completas, consulte Programação básica do WCF.

  2. Para cada ServiceEndpoint um dos hosts de serviço especificados, localize seu OperationDescriptionarquivo .

  3. Pesquise os comportamentos de operação para determinar se uma instância do DataContractSerializerOperationBehavior é encontrada.

  4. Se um DataContractSerializerOperationBehavior for encontrado, defina sua DataContractSurrogate propriedade como uma nova instância do substituto. Se não DataContractSerializerOperationBehavior for encontrado, crie uma nova instância e defina o DataContractSurrogate membro do novo comportamento como uma nova instância do substituto.

  5. Finalmente, adicione esse novo comportamento aos comportamentos de operação atuais, conforme mostrado no exemplo a seguir:

    using (ServiceHost serviceHost = new ServiceHost(typeof(InventoryCheck)))
        foreach (ServiceEndpoint ep in serviceHost.Description.Endpoints)
        {
            foreach (OperationDescription op in ep.Contract.Operations)
            {
                DataContractSerializerOperationBehavior dataContractBehavior =
                    op.Behaviors.Find<DataContractSerializerOperationBehavior>()
                    as DataContractSerializerOperationBehavior;
                if (dataContractBehavior != null)
                {
                    dataContractBehavior.DataContractSurrogate = new InventorySurrogated();
                }
                else
                {
                    dataContractBehavior = new DataContractSerializerOperationBehavior(op);
                    dataContractBehavior.DataContractSurrogate = new InventorySurrogated();
                    op.Behaviors.Add(dataContractBehavior);
                }
            }
        }
    

Para usar um substituto para importação de metadados

Ao importar metadados como WSDL e XSD para gerar código do lado do cliente, o substituto precisa ser adicionado ao componente responsável pela geração de código do esquema XSD, XsdDataContractImporter. Para fazer isso, modifique diretamente o WsdlImporter usado para importar metadados.

Para implementar um substituto para importação de metadados
  1. Importe os metadados usando a WsdlImporter classe.

  2. Use o TryGetValue método para verificar se um XsdDataContractImporter foi definido.

  3. Se o TryGetValue método retornar false, crie um novo XsdDataContractImporter e defina sua Options propriedade para uma nova instância da ImportOptions classe. Caso contrário, use o importador retornado pelo out parâmetro do TryGetValue método.

  4. Se o XsdDataContractImporter não ImportOptions tiver definido, defina a propriedade como uma nova instância da ImportOptions classe.

  5. Defina a DataContractSurrogate propriedade do ImportOptions do para uma nova instância do substituto XsdDataContractImporter .

  6. Adicione o XsdDataContractImporter à coleção retornada pela State propriedade do WsdlImporter (herdada da MetadataExporter classe.)

  7. Use o ImportAllContracts método do WsdlImporter para importar todos os contratos de dados dentro do esquema. Durante a última etapa, o código é gerado a partir dos esquemas carregados chamando o substituto.

    MetadataExchangeClient mexClient = new MetadataExchangeClient(metadataAddress);
    mexClient.ResolveMetadataReferences = true;
    MetadataSet metaDocs = mexClient.GetMetadata();
    WsdlImporter importer = new WsdlImporter(metaDocs);
    object dataContractImporter;
    XsdDataContractImporter xsdInventoryImporter;
    if (!importer.State.TryGetValue(typeof(XsdDataContractImporter),
        out dataContractImporter))
        xsdInventoryImporter = new XsdDataContractImporter();
    
    xsdInventoryImporter = (XsdDataContractImporter)dataContractImporter;
    xsdInventoryImporter.Options ??= new ImportOptions();
    xsdInventoryImporter.Options.DataContractSurrogate = new InventorySurrogated();
    importer.State.Add(typeof(XsdDataContractImporter), xsdInventoryImporter);
    
    Collection<ContractDescription> contracts = importer.ImportAllContracts();
    

Para usar um substituto para exportação de metadados

Por padrão, ao exportar metadados do WCF para um serviço, o esquema WSDL e XSD precisa ser gerado. O substituto precisa ser adicionado ao componente responsável por gerar o esquema XSD para tipos de contrato de dados, XsdDataContractExporter. Para fazer isso, use um comportamento que implementa IWsdlExportExtension para modificar o WsdlExporter, ou modificar diretamente o WsdlExporter usado para exportar metadados.

Para usar um substituto para exportação de metadados
  1. Crie um novo WsdlExporter ou use o wsdlExporter parâmetro passado para o ExportContract método.

  2. Use a TryGetValue função para verificar se um XsdDataContractExporter foi definido.

  3. Se TryGetValue retornar false, crie um novo XsdDataContractExporter com os esquemas XML gerados a WsdlExporterpartir do , e adicione-o à coleção retornada pela State propriedade do WsdlExporter. Caso contrário, use o exportador retornado pelo out parâmetro do TryGetValue método.

  4. Se o XsdDataContractExporter não ExportOptions tiver definido, defina a Options propriedade como uma nova instância da ExportOptions classe.

  5. Defina a DataContractSurrogate propriedade do ExportOptions do para uma nova instância do substituto XsdDataContractExporter . As etapas subsequentes para exportar metadados não exigem alterações.

    WsdlExporter exporter = new WsdlExporter();
    //or
    //public void ExportContract(WsdlExporter exporter,
    // WsdlContractConversionContext context) { ... }
    object dataContractExporter;
    XsdDataContractExporter xsdInventoryExporter;
    if (!exporter.State.TryGetValue(typeof(XsdDataContractExporter),
        out dataContractExporter))
    {
        xsdInventoryExporter = new XsdDataContractExporter(exporter.GeneratedXmlSchemas);
    }
    else
    {
        xsdInventoryExporter = (XsdDataContractExporter)dataContractExporter;
    }
    
    exporter.State.Add(typeof(XsdDataContractExporter), xsdInventoryExporter);
    
    if (xsdInventoryExporter.Options == null)
        xsdInventoryExporter.Options = new ExportOptions();
    xsdInventoryExporter.Options.DataContractSurrogate = new InventorySurrogated();
    

Consulte também