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 
{
    ...
}
Foi útil?

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.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top