Cliente WCF tendo problemas para reconhecer ServiceKnownTypes?
-
27-09-2019 - |
Pergunta
Como eu diria ao serviço WCF quais KnownTypes usar ao passar dados de volta ao cliente?
Eu sei que posso usar o [ServiceKnownType]
atributo, que faz com que a chamada de serviço funcione bem em um servidor de teste WCF, mas ainda falha no cliente.Estou faltando alguma coisa aqui?
[OperationContract]
[ServiceKnownType(typeof(SubClassA))]
[ServiceKnownType(typeof(SubClassB))]
BaseClassZ GetObject();
A mensagem de erro do cliente é:
{"Element 'http://schemas.datacontract.org/2004/07/baseclassz' contém dados de um tipo que mapeia o nome 'http://schemas.datacontract.org/2004/07/subclassa'.O Deserializer não tem conhecimento de qualquer tipo que mapeie esse nome.Considere o uso de um datacontractresolver ou adicione o tipo correspondente a 'subclassa' à lista de tipos conhecidos - por exemplo, usando o atributo KnowtyPeattribute ou adicionando -o à lista de tipos conhecidos passados ao DataCOntractSerializer. "}
Serializar/desserializar o objeto no servidor WCF usando um DataContractSerializer e uma lista de KnownTypes funciona bem.
ATUALIZAR: Parece que posso fazer com que o cliente leia o objeto corretamente se adicionar atributos KnownType à classe base, mas ainda estou procurando uma maneira de contornar isso, se possível, já que a classe base é usada para muitos itens e não quero modificar os atributos KnownType na classe base sempre que adicionar um novo item.
[DataContract]
[KnownType(typeof(SubClassA))]
[KnownType(typeof(SubClassB))]
public class BaseClassZ
{
...
}
Solução
Para evitar dissuadir seu código de serviço, coloque os tipos conhecidos no web.config do serviço:
<system.runtime.serialization>
<dataContractSerializer>
<declaredTypes>
<add type="SomeNs.BaseClassZ, SomeAssembly">
<knownType type="SomeNs.SubClassA, SomeAssembly" />
<knownType type="SomeNs.SubClassB, SomeAssembly" />
</add>
</declaredTypes>
</dataContractSerializer>
</system.runtime.serialization>
Se você quiser fazer isso por código você precisa usar este atributo na interface de serviço e não no método de operação, mas eu preferiria a abordagem declarativa:
[ServiceContract]
[ServiceKnownType(typeof(SubClassA))]
[ServiceKnownType(typeof(SubClassB))]
public interface IFoo
{
[OperationContract]
BaseClassZ GetObject();
}
ATUALIZAR:
Eu coloquei um projeto de amostra ilustrando o uso de web.config para configurar tipos conhecidos, que é minha abordagem preferida.E outro projeto de amostra demonstrando a segunda abordagem.
ATUALIZAÇÃO 2:
Depois de analisar seu código atualizado com o aplicativo cliente Silverlight, notamos a seguinte definição:
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")]
[System.ServiceModel.ServiceContractAttribute(ConfigurationName="ServiceReference1.IService1")]
public interface IService1 {
[System.ServiceModel.OperationContractAttribute(AsyncPattern=true, Action="http://tempuri.org/IService1/GetMany", ReplyAction="http://tempuri.org/IService1/GetManyResponse")]
System.IAsyncResult BeginGetMany(System.AsyncCallback callback, object asyncState);
System.Collections.ObjectModel.ObservableCollection<MyCommonLib.BaseClassZ> EndGetMany(System.IAsyncResult result);
[System.ServiceModel.OperationContractAttribute(AsyncPattern=true, Action="http://tempuri.org/IService1/GetSingle", ReplyAction="http://tempuri.org/IService1/GetSingleResponse")]
[System.ServiceModel.ServiceKnownTypeAttribute(typeof(MyCommonLib.SubClassA))]
[System.ServiceModel.ServiceKnownTypeAttribute(typeof(MyCommonLib.SubClassB))]
System.IAsyncResult BeginGetSingle(System.AsyncCallback callback, object asyncState);
MyCommonLib.BaseClassZ EndGetSingle(System.IAsyncResult result);
}
Observe como o BeginGetSingle
método contém os atributos de tipo conhecidos enquanto o BeginGetMany
método não.Na verdade, esses atributos devem ser colocados na definição de serviço para que a classe fique assim.
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")]
[System.ServiceModel.ServiceContractAttribute(ConfigurationName="ServiceReference1.IService1")]
[System.ServiceModel.ServiceKnownTypeAttribute(typeof(MyCommonLib.SubClassA))]
[System.ServiceModel.ServiceKnownTypeAttribute(typeof(MyCommonLib.SubClassB))]
public interface IService1
{
[System.ServiceModel.OperationContractAttribute(AsyncPattern=true, Action="http://tempuri.org/IService1/GetMany", ReplyAction="http://tempuri.org/IService1/GetManyResponse")]
System.IAsyncResult BeginGetMany(System.AsyncCallback callback, object asyncState);
System.Collections.ObjectModel.ObservableCollection<MyCommonLib.BaseClassZ> EndGetMany(System.IAsyncResult result);
[System.ServiceModel.OperationContractAttribute(AsyncPattern=true, Action="http://tempuri.org/IService1/GetSingle", ReplyAction="http://tempuri.org/IService1/GetSingleResponse")]
System.IAsyncResult BeginGetSingle(System.AsyncCallback callback, object asyncState);
MyCommonLib.BaseClassZ EndGetSingle(System.IAsyncResult result);
}
Como esta é uma classe gerada automaticamente, pode haver um bug no SLsvcUtil.exe e svcutil.exe
pois exibe o mesmo comportamento.Colocar os atributos de tipo conhecidos em seus lugares corretos resolve o problema.O problema é que essa classe é gerada automaticamente por uma ferramenta e se você tentar regenerá-la a partir do WSDL ela irá bagunçar novamente.
Parece que se você tiver a seguinte definição de serviço:
[ServiceContract]
[ServiceKnownType(typeof(SubClassA))]
[ServiceKnownType(typeof(SubClassB))]
public interface IService1
{
[OperationContract]
BaseClassZ[] GetMany();
[OperationContract]
BaseClassZ GetSingle();
}
E os 3 contratos de dados usados aqui são compartilhados entre o cliente e o servidor ao importar a definição do serviço, o método que retorna uma coleção não obtém os atributos de tipo conhecido corretos no proxy do cliente gerado.Talvez isso seja intencional.
Outras dicas
Passei horas hoje sobre o que, da melhor maneira, eu posso dizer, é exatamente o mesmo problema. A solução para mim foi usar o método AddGenericResolver na biblioteca ServiceModelex da Idesign.
Nota: .NET 4.0 necessário como usa DataContractResolver
Você pode encontrar Página de downloads do Idesign.
Tudo o que tive que fazer no meu caso foi adicionar a seguinte linha de código:
Client.AddGenericResolver( typeof ( K2Source ) );
Espero que isso ajude alguém por aí a economizar algumas horas!
Você pode encontrar mais informações no livro "Programação WCF Services: Mastering WCF e The Azure Appfabric Service Bus", de Juval Lowy
Há outra maneira de fazer isso. Em vez de usar "Adicionar referência de serviço", você code as classes proxy. É um pouco mais de codificação inicialmente, mas oferece uma solução muito mais estável e robusta. Descobrimos que isso nos economiza tempo a longo prazo.
Ver: http://www.dnrtv.com/default.aspx?shownum=122
Nota: Isso só funciona se você tiver controle do servidor e do cliente.