Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
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 tipo 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 por meio do uso de DataContractSerializer a fim de realizar a conversão 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 XSD (XML Schema Documents). Após a importação, o código é criado a partir de metadados e o substituto pode ser usado nesse caso para personalizar o código gerado também.
Como funciona o substituto
Um substituto funciona mapeando um tipo (o tipo "original") para outro (o tipo "substituído"). O exemplo a seguir mostra o tipo Inventory original e um novo tipo alternativo 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; }
}
}
Implementação do IDataContractSurrogate
Para usar o contrato de dados substituto, implemente a IDataContractSurrogate interface.
Veja a seguir uma visão geral de cada método de IDataContractSurrogate, juntamente 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 é posteriormente usado para transformar a instância original em uma instância substituta chamando o GetObjectToSerialize método.
Durante a desserialização, o mapeamento retornado por este método é usado pelo serializador para desserializar em uma instância do tipo substituto. Posteriormente, ele chama GetDeserializedObject para transformar a instância substituta em uma instância do tipo original.
Na exportação, o tipo substituto retornado por esse método é refletido a fim de obter o contrato de dados a ser usado para gerar metadados.
Na importação, o tipo inicial é alterado para um tipo substituto, que é refletido a fim de que o contrato de dados seja usado em fins como o suporte de referência.
O Type parâmetro é o tipo do objeto que está sendo serializado, desserializado, importado ou exportado. O método GetDataContractType deverá retornar o tipo de entrada se o substituto não manipular o tipo. Caso contrário, retorne o tipo substituto apropriado. Se existirem vários tipos alternativos, vários mapeamentos poderão ser definidos nesse método.
O método GetDataContractType não é chamado para primitivos internos de contrato de dados, 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 type parâmetro e Inventory são comparáveis. Nesse caso, 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 na instância de tipo alternativo. O método é necessário para 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. Esse método transfere dados do tipo original para os campos do tipo substituído. Os campos podem ser mapeados diretamente para campos substitutos ou as manipulações dos dados originais podem ser armazenadas no substituto. Alguns usos possíveis incluem: mapear diretamente os campos, executar operações nos dados a serem armazenados nos campos substituídos ou armazenar o XML do tipo original no campo substituído.
O targetType parâmetro refere-se ao tipo declarado do membro. Esse parâmetro é o tipo alternativo 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 deverá 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 será chamado se o objeto for nulo. Vários mapeamentos alternativos para instâncias diferentes 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 somente uma vez para um objeto, pois todas as serializações subsequentes apenas gravam a referência no fluxo de dados. Se preserveObjectReferences estiver definido como false, o surrogado será chamado toda vez que uma instância é 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. Esse processo ocorre se o objeto é substituído ou não.
O exemplo acima converte os dados da instância Inventory para os da InventorySurrogated. Ele verifica o tipo do objeto e executa as manipulações necessárias para converter no tipo substituído. Nesse caso, os campos da Inventory classe são diretamente copiados para os campos de InventorySurrogated classe.
Método GetDeserializedObject
O GetDeserializedObject método converte a instância de tipo alternativo na instância de tipo original. Ele é necessário para a desserialização.
A próxima tarefa é definir a maneira como os dados físicos serão mapeados da instância substituta para a 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 de dados reverso para a desserialização do tipo substituto de volta para o tipo original. Semelhante ao GetObjectToSerialize método, alguns usos possíveis podem ser para trocar dados de campo diretamente, executar operações nos dados e armazenar dados XML. Ao desserializar, talvez você não obtenha sempre exatamente os valores de dados do original devido a alterações durante a conversão dos dados.
O targetType parâmetro refere-se ao tipo declarado do membro. Esse parâmetro é o tipo alternativo retornado pelo GetDataContractType método. O obj parâmetro refere-se ao objeto que foi desserializado. O objeto poderá ser convertido novamente em seu tipo original se ele for substituído. Esse método retornará o objeto de entrada se o substituto não manipular o objeto. Caso contrário, o objeto desserializado será retornado após a conclusão da conversão. Se existirem vários tipos alternativos, você poderá fornecer a conversão de dados de um tipo alternativo para o primário para cada um indicando cada tipo e sua conversão.
Ao retornar um objeto, as tabelas de objeto internas são atualizadas com o objeto retornado por esse substituto. Todas as referências subsequentes a uma instância obterão a instância substituta das tabelas de objetos.
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 membros conterá os mesmos valores de 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 do membro ou do 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";
}
}
Esse método (com duas sobrecargas) permite a inclusão de informações extras nos metadados no nível do membro ou do tipo. É possível incluir dicas sobre se um membro é público ou privado e comentários que seriam preservados em toda a exportação e importação do esquema. Essas informações seriam perdidas sem esse 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 é sobrecarregado e pode usar um Type parâmetro (clrtype parâmetro) ou MemberInfo (memberInfo parâmetro). O segundo parâmetro é sempre um Type (dataContractType parâmetro). Esse método é chamado para cada membro e tipo do tipo substituto dataContractType.
Qualquer uma dessas sobrecargas deve retornar null ou um objeto serializável. Um objeto não nulo será serializado como anotação no esquema exportado. No caso da sobrecarga Type, cada tipo exportado para o esquema é enviado a esse método no primeiro parâmetro com o tipo substituto como o parâmetro dataContractType. No caso da sobrecarga MemberInfo, cada membro exportado para o esquema envia as respectivas informações como o parâmetro memberInfo com o tipo substituto no segundo parâmetro.
Método GetCustomDataToExport (Type, Type)
O IDataContractSurrogate.GetCustomDataToExport(Type, Type) método é chamado durante a 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 a esse método para determinar se há dados adicionais que precisam ser incluídos no esquema.
Membro GetCustomDataToExport (MemberInfo, Type)
O IDataContractSurrogate.GetCustomDataToExport(MemberInfo, Type) é chamado durante a exportação para cada membro nos tipos que são exportados. Essa função permite que você personalize os comentários para os membros que serão incluídos no esquema após a exportação. As informações de cada membro da classe são enviadas a esse método para verificar se quaisquer dados adicionais precisam ser adicionados ao esquema.
O exemplo acima pesquisa no dataContractType cada membro do substituto. Em seguida, 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 suas restrições reais de acesso. Ao não usar essa implementação, o membro numpens seria público no esquema exportado, mesmo que ele tenha sido definido no substituto como privado. Por meio do uso desse método, no esquema exportado, o modificador de acesso pode ser gerado como privado.
Método GetReferencedTypeOnImport
Este método mapeia o Type do substituto para o tipo original. Esse método é opcional para 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 a fim de mapear o contrato de dados substituto para um tipo. Os parâmetros de cadeia de caracteres typeName e typeNamespace definem o nome e o namespace do tipo substituído. O valor de retorno de GetReferencedTypeOnImport é usado para determinar se um novo tipo precisa ser gerado. Esse método deve retornar um tipo válido ou nulo. Para tipos válidos, o tipo retornado será usado como um tipo referenciado no código gerado. Se nulo for retornado, nenhum tipo será referenciado e um novo tipo deverá ser criado. Se existirem vários substitutos, é possível executar o mapeamento para cada tipo alternativo de volta ao seu tipo inicial.
O customData parâmetro é o objeto originalmente retornado de GetCustomDataToExport. Isso customData é usado quando 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 com base na importação de esquema. Esse 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 CodeCompileUnit. Isso inclui alterar o nome, os membros, os atributos e muitas outras propriedades do CodeTypeDeclaration. Ao processar o CodeCompileUnit, é possível modificar as diretivas, os namespaces, os assemblies referenciados e vários outros aspectos.
O parâmetro CodeTypeDeclaration contém a declaração de tipo do código DOM. O CodeCompileUnit parâmetro permite modificação para processar o código. Ao retornar 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 instância CodeTypeDeclaration e CodeTypeMember inclui dados personalizados como a propriedade UserData, convertidos no tipo IDataContractSurrogate.
O exemplo acima executa algumas alterações no esquema importado. O código preserva 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, dados personalizados são inseridos nos metadados indicando quais membros são privados. O exemplo pesquisa os dados personalizados, verifica se o modificador de acesso é privado e 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
Esse método obtém tipos de dados personalizados definidos 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. Ele recebe um Collection<T> (o parâmetro customDataTypes), que é uma coleção de tipos. O método deve adicionar tipos conhecidos adicionais a essa coleção. Os tipos de dados personalizados conhecidos são necessários para habilitar a serialização e desserialização de dados personalizados usando o DataContractSerializer. Para obter mais informações, consulte Tipos conhecidos do contrato de dados.
Implementando um substituto
Para usar o substituto de contrato de dados no WCF, é necessário seguir alguns procedimentos especiais.
Para usar um substituto para serialização e desserialização
Use DataContractSerializer para realizar a serialização e a desserialização de dados com o substituto. O DataContractSerializer é criado pelo DataContractSerializerOperationBehavior. O substituto também deve ser especificado.
Para implementar serialização e desserialização
Crie uma instância do ServiceHost para o seu serviço. Para obter instruções completas, consulte Programação básica do WCF.
Para cada ServiceEndpoint do host de serviço especificado, encontre o respectivo OperationDescription.
Pesquise os comportamentos da operação para determinar se uma instância de DataContractSerializerOperationBehavior foi encontrada.
Se um DataContractSerializerOperationBehavior for encontrado, defina sua propriedade DataContractSurrogate para uma nova instância do substituto. Caso nenhum DataContractSerializerOperationBehavior seja encontrado(a), crie uma nova instância e configure o membro DataContractSurrogate do novo comportamento como uma nova instância do substituto.
Por fim, adicione esse novo comportamento aos comportamentos atuais da operação, 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 surrogato precisa ser adicionado ao componente responsável pela geração de código a partir do esquema XSD, XsdDataContractImporter. Para fazer isso, modifique diretamente os WsdlImporter metadados usados para importar.
Para implementar um substituto para importação de metadados
Importe os metadados usando a WsdlImporter classe.
Use o TryGetValue método para verificar se um XsdDataContractImporter foi definido.
Se o TryGetValue método retornar
false, crie um novo XsdDataContractImporter e defina sua Options propriedade como uma nova instância da ImportOptions classe. Caso contrário, use o importador retornado pelo parâmetrooutdo método TryGetValue.Se o XsdDataContractImporter não tiver o ImportOptions definido, então defina a propriedade como uma nova instância da classe ImportOptions.
Defina a propriedade DataContractSurrogate do ImportOptions de XsdDataContractImporter para uma nova instância do substituto.
Adicione XsdDataContractImporter à coleção retornada pela propriedade State do WsdlImporter (herdado da classe MetadataExporter.)
Use o método ImportAllContracts do WsdlImporter para importar todos os contratos de dados dentro do esquema. Durante a última etapa, o código é gerado com base nos esquemas carregados por meio de uma chamada ao 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 pela geração de esquema XSD para tipos de contrato de dados. XsdDataContractExporter Para fazer isso, use um comportamento que implementa IWsdlExportExtension para modificar o WsdlExporter, ou modifique diretamente os WsdlExporter metadados usados para exportar.
Para usar um substituto para exportação de metadados
Crie um novo WsdlExporter ou use o
wsdlExporterparâmetro passado para o ExportContract método.Use a TryGetValue função para verificar se um XsdDataContractExporter foi definido.
Se TryGetValue retornar
false, crie um novo XsdDataContractExporter com os esquemas XML gerados do WsdlExporter e adicione-o à coleção retornada pela propriedade State do WsdlExporter. Caso contrário, use o exportador retornado pelo parâmetrooutdo método TryGetValue.Se XsdDataContractExporter não tiver ExportOptions definido, defina a propriedade Options como uma nova instância da classe ExportOptions.
Defina a propriedade DataContractSurrogate do ExportOptions de XsdDataContractExporter para uma nova instância do substituto. 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();