I want my service to be able to accept and return types derived from BaseType
without actually knowing what those types will be. I have almost got a solution using a custom DataContractResolver
based on the SharedTypeResolver from this excellent blog post.
The missing piece of the puzzle is that the types my service will handle might not be shared and known to the service but I still want to accept them and be aware of what the type should have been. I have come up with the following example of a service that acts as a stack. You can push and pop any type derived from BaseType
provided you use the SharedTypeResolver
and the types are shared between client and server.
[DataContract]
public class BaseType
{
[DataMember]
public string SomeText { get; set; }
public override string ToString()
{
return this.GetType().Name + ": " + this.SomeText;
}
}
[DataContract]
public class DerivedType : BaseType
{
[DataMember]
public int SomeNumber { get; set; }
public override string ToString()
{
return base.ToString() + ", " + this.SomeNumber;
}
}
[ServiceContract]
public interface ITypeStack
{
[OperationContract]
void Push(BaseType item);
[OperationContract]
BaseType Pop();
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class TypeStackService : ITypeStack
{
private Stack<BaseType> stack = new Stack<BaseType>();
public void Push(BaseType item)
{
this.stack.Push(item);
}
public BaseType Pop()
{
return this.stack.Pop();
}
}
This is obviously greatly a simplified example of the problem I'm having. A client can quite merrily push and pop BaseType
or DerivedType
because both client and server know about them, but if the client pushes UnsharedType
which the service does not know I get an error as you would expect.
The formatter threw an exception while trying to deserialize the message: There was an error while trying to deserialize parameter http://tempuri.org/:item. The InnerException message was 'Error in line 1 position 316. Element 'http://tempuri.org/:item' contains data from a type that maps to the name 'TestWcfClient, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null:TestWcfClient.UnsharedType'. The deserializer has no knowledge of any type that maps to this name. Consider changing the implementation of the ResolveName method on your DataContractResolver to return a non-null value for name 'TestWcfClient.UnsharedType' and namespace 'TestWcfClient, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.'. Please see InnerException for more details.
My current thinking is to add IExtensibleDataObject
to BaseType
to hold the values from an unshared type and make an unshared type look like BaseType
to the service on deserialization when an item is pushed; the opposite would need to happen when an item is popped. I'm just not sure how to go about it. My thoughts so far on possible approaches:
- Further customisation to
DataContractResolver
which might involve aTypeDelegator
- Using
IDataContractSurrogate
in place of an unshared type - Somehow retain the serialized XML the service received when an item is pushed, then use this in the reply when an item is popped
- Using a message inspector to manipulate the messages
I've no idea if any of these will work, what would be involved or what is the best solution. Do you?