6

¿Cómo le diría al servicio WCF qué KnownTypes usar cuando devuelva datos al cliente?WCF Cliente que tiene problemas para reconocer ServiceKnownTypes?

Sé que puedo usar el atributo [ServiceKnownType], que hace que la llamada de servicio se ejecute bien desde un servidor de prueba WCF, sin embargo, todavía falla en el cliente. ¿Me estoy perdiendo de algo?

[OperationContract] 
[ServiceKnownType(typeof(SubClassA))] 
[ServiceKnownType(typeof(SubClassB))] 
BaseClassZ GetObject(); 

mensaje de error desde el cliente es:

{ "Elemento 'http://schemas.datacontract.org/2004/07/BaseClassZ' contiene datos de un tipo que se asigna a el nombre 'http://schemas.datacontract.org/2004/07/SubClassA'. el deserializer no tiene conocimiento de cualquier tipo que se asigna a este nombre. Considere el uso de un DataContractResolver o añadir el tipo correspondiente al 'SubClassA' a la lista de tipos conocidos -. Por ejemplo, por el uso del atributo KnownTypeAttribute o por de añadir a la lista de tipos conocidos pasados ​​a DataContractSerializer "}

Serializar/deserializar el objeto en el servidor WCF que usa un DataContractSerializer y una lista de KnownTypes funciona bien.

ACTUALIZACIÓN: Parece que puedo conseguir que el cliente leer el objeto correctamente si añado KnownType atribuye a la clase base, pero todavía estoy buscando una forma de evitar esto si es posible ya que la clase base se utiliza para una muchos elementos y no quiero tener que modificar los atributos KnownType en la clase base cada vez que agregue un nuevo elemento.

[DataContract] 
[KnownType(typeof(SubClassA))] 
[KnownType(typeof(SubClassB))] 
public class BaseClassZ 
{ 
    ... 
} 
+0

veo un montón de documentación de MSDN y muestras que asegurarse de que suene como esto debería ser posible , pero maldita sea si puedo hacer que funcione! Agregar un bounty ... – Aardvark

Respuesta

10

Para evitar disuadir el código de servicio puso a los tipos conocidos en web.config del servicio:

<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> 

Si desea hacerlo por código que necesita para utilizar este atributo en la interfaz de servicio y no en el método de operación, pero yo preferiría el enfoque declarativo:

[ServiceContract] 
[ServiceKnownType(typeof(SubClassA))] 
[ServiceKnownType(typeof(SubClassB))] 
public interface IFoo 
{ 
    [OperationContract] 
    BaseClassZ GetObject(); 
} 

ACTUALIZACIÓN:

He puesto un sample project que ilustra el uso de web.config para configurar tipos conocidos, que es mi enfoque preferido. Y otro sample project demostrando el segundo enfoque.


ACTUALIZACIÓN 2:

Después de mirar el código actualizado con el cliente aplicación de Silverlight notamos la siguiente definición:

[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); 
} 

Aviso cómo el método BeginGetSingle contiene el tipo conocido atributos mientras que el BeginGetMany método no. De hecho, esos atributos se deben colocar en la definición del servicio para que la clase se vea así.

[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 se trata de una clase autogenerada podría haber un error en el SLsvcUtil.exe y svcutil.exe ya que presenta el mismo comportamiento. Poner los atributos de tipo conocidos en su lugar correcto resuelve el problema. El problema es que esta clase es autogenerada por una herramienta y si intentas regenerarla desde el WSDL se volverá a enredar.

Así que parece que si tiene la siguiente definición de servicio:

[ServiceContract] 
[ServiceKnownType(typeof(SubClassA))] 
[ServiceKnownType(typeof(SubClassB))] 
public interface IService1 
{ 
    [OperationContract] 
    BaseClassZ[] GetMany(); 

    [OperationContract] 
    BaseClassZ GetSingle(); 
} 

Y los 3 contratos de datos utilizados aquí son compartidas entre el cliente y el servidor al importar la definición del servicio, el método que devuelve una colección no obtiene los atributos de tipo conocidos correctos en el proxy del cliente generado. Tal vez esto es por diseño.

+0

¿La clase BaseClassZ {...} requiere [KnownType (typeof (SubClassA))] y [KnownType (typeof (SubClassB))] para que funcione su segundo ejemplo? – Aardvark

+1

@Aardvark, no, no es así. Puede verificar el [proyecto de muestra] (http://jump.fm/DQGQC) que puse en su lugar para ilustrarlo. –

+0

Apuesto a que esto tiene algo que ver con el hecho de que estoy reutilizando tipos de un ensamblado compartido. – Aardvark

0

Hay otra manera de hacerlo. En lugar de usar "agregar referencia de servicio", codifica las clases de proxy. Inicialmente tiene un poco más de codificación, pero le brinda una solución mucho más estable y sólida. Hemos descubierto que esto nos ahorra tiempo a largo plazo.

Ver: http://www.dnrtv.com/default.aspx?showNum=122

Nota: esto sólo funciona si usted tiene control tanto del servidor y el cliente.

1

Pasé horas hoy en lo que, lo mejor que puedo decir, es exactamente el mismo problema. La solución para mí fue utilizar el método AddGenericResolver de la biblioteca ServiceModelEx de IDesign.

NOTA: .NET 4.0 requiere, ya que utiliza DataContractResolver

Puede encontrarlo en IDesign Downloads page.

Todo lo que tenía que hacer en mi caso fue la siguiente línea de código:

Client.AddGenericResolver(typeof (K2Source)); 

espero que esto ayude a alguien más por ahí salvar a unas pocas horas!

Puede encontrar más información en el libro "Programación de servicios WCF: Mastering WCF y el Azure AppFabric Service Bus" por Juval Lowy

Cuestiones relacionadas