6

Wie würde ich dem WCF-Dienst mitteilen, welche KnownTypes verwendet werden sollen, wenn Daten an den Client zurückgegeben werden?WCF-Client Probleme beim Erkennen von ServiceKnownTypes?

Ich weiß, dass ich das Attribut [ServiceKnownType] verwenden kann, das den Dienstaufruf von einem WCF-Testserver problemlos ausführen lässt, jedoch weiterhin vom Client fehlschlägt. Fehle ich hier etwas?

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

Fehlermeldung vom Client ist:

{ "Element 'http://schemas.datacontract.org/2004/07/BaseClassZ' enthält Daten von einem Typ, der Karten zu der Name ‚http://schemas.datacontract.org/2004/07/SubClassA‘. der Deserializer hat keine Kenntnis von jede Art, die diesem Namen zuordnet. Betrachten wir ein DataContractResolver oder fügen Sie den Typmit entsprechenden‚SubClassA‘ in die Liste der bekannten Typen - z. B. durch das Knowntype Attribut oder durch es in die Liste der bekannten Typen weitergegeben DataContractSerializer Hinzufügen "}

Serialisieren/Deserialisieren das Objekt auf Der WCF-Server, der einen DataContractSerializer und eine Liste von KnownTypes verwendet, funktioniert einwandfrei.

UPDATE: Es scheint, ich den Client bekommen kann das Objekt korrekt zu lesen, wenn ich hinzufügen Knowntype auf die Basisklasse-Attribute, aber ich bin immer noch auf eine Art und Weise dies umsah, wenn möglich, da die Basisklasse für ein verwendet wird, Viele Elemente und ich möchte nicht die KnownType-Attribute für die Basisklasse ändern müssen, wenn ich ein neues Element hinzufüge.

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

Ich sehe viel MSDN-Dokumentation und Beispiele, die Sie sicher, es so klingen möglich sein sollte, aber verdammt, wenn ich es zur Arbeit bringen kann! Hinzufügen eines Bounty ... – Aardvark

Antwort

10

Um zu vermeiden, Ihr Service-Code, die bekannten Typen in web.config des Dienst gestellt abzuschrecken:

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

Wenn Sie es durch Code tun wollen, müssen Sie auf der Service-Schnittstelle dieses Attribut verwenden, und nicht auf die Arbeitsweise, aber ich würde den deklarativen Ansatz bevorzugen:

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

UPDATE:

Ich habe eine sample project geschrieben, die die Verwendung von web.config veranschaulicht, um bekannte Typen zu konfigurieren, was mein bevorzugter Ansatz ist. Und ein weiterer sample project demonstriert den zweiten Ansatz.


UPDATE 2:

Nach dem Silverlight-Anwendung Client auf Ihrem aktualisierten Code suchen bemerken wir die folgende Definition:

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

Beachten Sie, wie die BeginGetSingle Methode der bekannten Art, während die Attribute enthält BeginGetMany Methode nicht. Diese Attribute sollten in der Servicedefinition platziert werden, damit die Klasse so aussieht.

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

Da dies eine automatisch generierte Klasse ist es könnte ein Fehler in den SLsvcUtil.exe und svcutil.exe sein, wie es das gleiche Verhalten zeigt. Die bekannten Typenattribute an der richtigen Stelle zu platzieren, löst das Problem. Das Problem ist, dass diese Klasse automatisch von einem Tool generiert wird und wenn Sie versuchen, es aus der WSDL neu zu generieren, wird es wieder versauen.

So scheint es, dass, wenn Sie die folgende Service-Definition:

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

    [OperationContract] 
    BaseClassZ GetSingle(); 
} 

Und die drei Datenverträge von hier zwischen dem Client und dem Server freigegeben, wenn die Definition des Dienstes zu importieren, die Methode, die zurückgibt Eine Auflistung erhält nicht die korrekten bekannten Typattribute im generierten Clientproxy. Vielleicht ist das mit Absicht.

+0

Benötigt die Klasse BaseClassZ {...} [KnownType (typeof (SubClassA))] und [KnownType (typeof (SubClassB))], damit Ihr zweites Beispiel funktioniert? – Aardvark

+1

@Aardvark, nein, tut es nicht. Sie können das [Beispielprojekt] (http://jump.fm/DQGQC) auschecken, das ich eingefügt habe, um es zu illustrieren. –

+0

Ich wette, das hat etwas damit zu tun, dass ich Typen aus einer gemeinsamen Assembly wiederverwende. – Aardvark

0

Es gibt einen anderen Weg, dies zu tun. Anstatt "Service-Referenz hinzufügen" zu verwenden, codieren Sie die Proxy-Klassen. Es ist ein wenig mehr Codierung zunächst aber gibt Ihnen eine viel stabilere und robuste Lösung. Wir haben festgestellt, dass uns dies auf lange Sicht Zeit spart.

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

Hinweis: dies funktioniert nur, wenn Sie die Kontrolle über beide haben den Server und den Client.

1

Ich verbrachte heute Stunden mit dem, was ich am besten sagen kann, ist genau das gleiche Problem. Die Lösung für mich war, die AddGenericResolver-Methode aus IDesigns ServiceModelEx-Bibliothek zu verwenden.

Hinweis: .NET 4.0 erforderlich, da es DataContractResolver verwendet

Sie es auf IDesign Downloads page finden.

Alles, was ich hatte in meinem Fall zu tun, war die folgende Codezeile hinzu:

Client.AddGenericResolver(typeof (K2Source)); 

ich das da draußen jemand hoffen, sonst ein paar Stunden sparen hilft!

Sie können mehr Informationen in dem Buch „Programming WCF Dienstleistungen: Mastering WCF und die Azure AppFabric Service Bus“ finden von Juval Löwy

Verwandte Themen