WCF Serialization part 1: Interfaces, Base classes and the NetDataContractFormatSerializer

Tags: .NET, CodeSnippets, WCF, WinFX

One of WCF's goals is interoperability with standard protocol stacks, like WSE or other WS-* implementations. This led, I am told, to the design of WCF's default object serializer - the DataContractFormatSerializer. This handy little engine serializes primitive types, common objects, enums, Collections and anything that implements IXmlSerializable or ISerializable, giving us a much nicer out-of-the-box-and-over-the-wire experience for our objects.

Because of the aforementioned design goal, however, it has one little problem - the serialized data it generates does not contain information about the .NET CLR type from which this data was serialized. This makes sense when you have interoperability in mind - the datatypes you pass should be described in your service metadata, such as WSDL, and not rely on an implementation detail like the CLR type itself.

When we're writing closed systems, however, where we control both ends of the pipe and use WCF code, it can get limiting. Conside this scenario:

[ServiceContract]
interface MyService
{
   [OperationContract]
   void SendMessage (IMessage message);
}

This is a pretty realistic simplification of my current system, and we often have interfaces or abstract base classes defined in our service contracts.

What happens now? Let's I try to send a concrete IMessage implementations to this contract. When the service receives the messages and tries to deserialize the message parameter, it's stuck - there's no way to create an abstract IMessage from the serialized data, and the data carries no information on how to deserialize it.
If my contract defined this:

 [OperationContract]
   void SendMessage (MessageBase message);

I would be in the same jam - it would try to deserialize a DerivedMessage object into a MessageBase - at best, there would be loss of data. At worst (and as it happens) it simply fails to work.

The first solution that WCF's object model offered is to explicitly mark the service with the concrete types it can receive. This is a stop-gap measure that works, but takes the whole point out of using interfaces and inheritance trees:

[OperationContract]
[ServiceKnownType(typeof(DerivedMessage)]
void SendMessage (MessageBase message);

This would require me to add a ServiceKnownType attribute for every single concrete type, past, present and future. Obviously, not a good solution.

An alternate method involved a daring bit of magic:

[OperationContract]
[ServiceKnownType("MyKnownTypeMethod")]
void SendMessage (MessageBase message);

In this case, some backstage voodoo looks for a static method of that given name, calls it during the type's static constructor and asks it to return a list of Known Types. Not only is this totally unintuitive, it also simply doesn't work, at least for me. Maybe it's the beta, maybe it's me.

The third solution came to me from the future. I was shaking my fist at the heavens and asking why couldn't WCF simply serialize the typenames inside, like is done with Remoting. Interop isn't my concern, and it's silly to lose such a powerful feature. My answer came from Aaron Skonnard's blog, and from his August 2006 MSDN Magazine article, not quite yet published:

It seems the standard DataContractFormatSerializer has a shy little brother called the NetDataContractFormatSerializer. The 'Net' here, like in other parts of WCF, means ".NET". This is a formatter than can be used when both ends of the stream are running .NET, and it can thus embed the type information in the serialized blob. This makes all the fudging around with knowntypes completely unneccesary, and gives us the flexibility we had with earlier methods.

Using it, unfortunately, is a closely held secret. Skonnard's blog is the only mention of the name outside of the sketchy SDK documentation. It seems that we have to write our own OperationBehavior attribute (which Skonnard graciously supplies) and put it on our OperationContracts. The code can be found in his page, and usage is as follows:

[ServiceContract]
interface MyService
{
   [OperationContract]
   [NetDataContractFormat]
   void SendMessage (IMessage message);
}

And that's it. The new attribute will add code that will replace the Formatter used during serialization, and from now on we're on easy street.

Notice that we have to set this attribute on every operation we need it. I was originally frustrated with not getting it to work because I instinctively put the attribute not on the SendMessage operation but rather on the IMessage definition - it stood to reason that we put it on the serializee, not the serializer. Once I got my head wrapped around that, it turned out you can't put it on the ServiceContract but have to do it for each operation seperately.

It seems a lot of work, but it really is much simpler than the alternatives above. Thank you, Aaron, for revealing this hidden gem! 

 

47 Comments

Comments have been disabled for this content.