2011/02/04

WCF Serialization: Dynamically add derived types to web service ServiceKnownType attribute

It is a known issue that you need to add the list of derived types of your base class in order for the WCF serializer to recognize them.

Suppose you have a class hierarchy like the following:

public abstract class MyBaseClass
{
[DataMember]

public int id { getset; }

}

public class DerivedType : MyBaseClass
{
[DataMember]
public string Name { getset; }

}

Now in your WCF service definition, your code should like this:

[ServiceContract]
interface MyService
{
[OperationContract]
[ServiceKnownType(typeof(DerivedType)]
void HelloWorld(MyBaseClass param1);

}

This obviously creates a problem that anytime you add a new derived type; you need to add it to the ServiceKnownType attribute so that the WCF serializer recognizes the new types.

To solve this problem and allow the newly created derived types to be dynamically serialized without the need to modify the service definition code, you need to do the following:

Create a method that returns a list of the derived types as follows:

public static class TypesHelper
{
public static IEnumerable GetKnownTypes(ICustomAttributeProvider provider)
{

Assembly assembly = Assembly.GetAssembly(typeof(MyBaseClass));
List<Type> types = assembly.GetExportedTypes().Where(e => e.BaseType != null && e.BaseType.BaseType == typeof(MyBaseClass)).ToList();

return types;

}

}

Now add a call to the above created method in the ServiceKnownType attribute as follows:

[OperationContract]
[ServiceKnownType("GetKnownTypes", typeof(TypesHelper))]
void HelloWorld(MyBaseClass param1);

This way, we are benefiting from the ability to use our own method that dynamically supplies the derived types to the WCF serializer.

4 comments:

  1. This is a pretty good solution.

    Here is the one place that I am finding (this and other) solutions have not found a cure.

    [ServiceContract]
    public interface IMyServer
    {
    [OperationContract]
    [ServiceKnownType(typeof(Circle))]
    bool AddShape(Shape shape);
    }


    If "Shape(.cs) resides in MyInterfaces.dll, and "Cirle(.cs) : Shape" resides in MyConcretes.dll (where MyConcretes assembly references MyInterfaces assembly), then you are completely hosed because you'd have to create a circular references. :<

    ReplyDelete
  2. Thanks. Well, that case really depends on your architecture; what you could do of course to avoid circular references is to keep the entities (bases and concretes) defined in a common assembly (or assemblies). Also, it is better to keep those common entities external to both the service and the client and independent on them :)

    ReplyDelete
  3. granadaCoder you can declaratively add your service known types to your web.config file like this

    <system.runtime.serialization>
    <dataContractSerializer>
    <declaredTypes>
    <add type="MyNamespace.MyType, MyAssemblyName"></add>
    </declaredTypes>
    </dataContractSerializer>
    </system.runtime.serialization>

    This should solve the problem you have with different assembly references

    ReplyDelete
  4. This way you would have to manually add all the types you need to the web.config, which requires you to repeat that for each type you add in the future.

    For different assembly references, I would actually modify the function "GetKnownTypes" to return a union of all the types of the referenced assemblies, which does not require any further modification even if new types are added.

    ReplyDelete