WCF Client having problems recognizing ServiceKnownTypes?
Asked Answered
B

3

7

How would I tell the WCF service what KnownTypes to use when passing data back to the client?

I know I can use the [ServiceKnownType] attribute, which makes the service call run fine from a WCF Test Server, however it still fails from the client. Am I missing something here?

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

Error message from client is:

{"Element 'http://schemas.datacontract.org/2004/07/BaseClassZ' contains data from a type that maps to the name 'http://schemas.datacontract.org/2004/07/SubClassA'. The deserializer has no knowledge of any type that maps to this name. Consider using a DataContractResolver or add the type corresponding to 'SubClassA' to the list of known types - for example, by using the KnownTypeAttribute attribute or by adding it to the list of known types passed to DataContractSerializer."}

Serializing/Deserializing the object on the WCF server using a DataContractSerializer and a list of KnownTypes works fine.

UPDATE: It appears I can get the client to read the object correctly if I add KnownType attributes to the base class, but I am still looking for a way around this if possible since the base class is used for a lot of items and I don't want to have to modify the KnownType attributes on the base class anytime I add a new item.

[DataContract]
[KnownType(typeof(SubClassA))]
[KnownType(typeof(SubClassB))]
public class BaseClassZ 
{
    ...
}
Bridgetbridgetown answered 1/10, 2010 at 15:6 Comment(1)
I see plenty of MSDN documentation and samples that sure make it sound like this should be possible, but damn if I can get it to work! Adding a bounty...Tali
R
10

To avoid deterring your service code put the known types into web.config of the service:

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

If you want to do it by code you need to use this attribute on the service interface and not on the operation method but I would prefer the declarative approach:

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

UPDATE:

I've put up a sample project illustrating the use of web.config to configure known types which is my preferred approach. And another sample project demonstrating the second approach.


UPDATE 2:

After looking at your updated code with the Silverlight application client we notice the following 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);
}

Notice how the BeginGetSingle method contains the known type attributes while the BeginGetMany method doesn't. In fact those attributes should be placed on the service definition so that the class looks like this.

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

As this is an autogenerated class there could be a bug in the SLsvcUtil.exe and svcutil.exe as it exhibits the same behavior. Putting the known type attributes on their correct place solves the problem. The problem is that this class is autogenerated by a tool and if you try to regenerate it from the WSDL it will mess up again.

So it seems that if you have the following service definition:

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

    [OperationContract]
    BaseClassZ GetSingle();
}

And the 3 data contracts used here are shared between the client and the server when importing the definition of the service, the method that returns a collection doesn't get the correct known type attributes in the generated client proxy. Maybe this is by design.

Redundant answered 2/10, 2010 at 12:54 Comment(12)
Does class BaseClassZ {...} require [KnownType(typeof(SubClassA))] and [KnownType(typeof(SubClassB))] for your second example to work?Tali
@Aardvark, no it doesn't. You may checkout the sample project I've put in place to illustrate it.Redundant
I bet this has something to do with the fact that I am reusing types from a shared assembly.Tali
@Aardvark, here's an example with shared assembly. Notice how known types are configured at app.config on the client.Redundant
I should have clarified. I'm trying to return a collection of objects (IEnumerable<BaseClass>). The base class and its derived types exist in a their own assembly referenced in both the client and server. When I add the client proxy it uses that existing type, but can't deserialize the results into the derived classes. (maybe I should spawn off a whole new question since it gets slightly more complicated on why i'm doing this?)Tali
In my example the base and derived classes reside in their own assembly which is referenced from the client (in fact it is the service assembly itself). The only difference is that you are defining IEnumerable<BaseClass> as return type while in my example I use directly BaseClass but this is completely unrelated. If in my example you replace it with IEnumerable<BaseClass> it still works. Also obviously I have the data contracts and service contract and implementation in the same assembly which might not be your case but this is not the problem.Redundant
Maybe you could upload a sample solution illustrating the problem so that I can take a look at what might be the problem, but normally if you start from my example there shouldn't be any issues.Redundant
(thanks sooo much BTW) I think gave up too quickly with your last example, I think I just have to get the settings in app.config just right...Tali
I uploaded a project that repros my issue: jump.fm/BGCRU The client is silverlight, hopefully you can build that. I see the "single" call works, the "many" does not, it's obvious why in the reference.cs, but it is not clear why it is not generating it like that!!Tali
Using a command line (non-silverlight) app client seems to exhibit the same issue as long as you insure you reference that shared assembly before adding the web service reference.Tali
@Aardvark, please see my UPDATE 2.Redundant
@Darin Dimitrov, if you removed the GetSingle call, leaving only the BaseClassZ reference in the array return value, (sl)svcutil will still generate a broken client proxy, regardless of where I put the ServiceKnowType declarations in the interface. This seems like a bug. I may add a bogus parameter or whole method to trick the client proxy to generate the correct code. I may burn a MSDN support incident / try to poke some contacts in Redmond about this.Tali
C
1

I spent hours today on what, as best I can tell, is the exact same issue. The solution for me was to use the AddGenericResolver method from IDesign's ServiceModelEx library.

NOTE: .NET 4.0 required as it uses DataContractResolver

You can find it on IDesign Downloads page.

All I had to do in my case was add the following line of code:

Client.AddGenericResolver( typeof ( K2Source ) );

I hope this helps someone else out there save a few hours!

You can find more information in the book "Programming WCF Services: Mastering WCF and the Azure AppFabric Service Bus" by Juval Lowy

Curhan answered 21/9, 2011 at 6:30 Comment(0)
C
0

There is another way to do this. Rather than using "add service reference" you code the proxy classes. It is a little more coding initially but gives you a much more stable and robust solution. We have found that this saves us time in the long run.

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

Note: this only works if you have control of both the server and the client.

Campinas answered 2/10, 2010 at 12:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.