Question

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 
{
    ...
}
Was it helpful?

Solution

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.

OTHER TIPS

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

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top