WCF Serialization Part 2a: NameValueCollection Denied!
As we all know, IDictionaries aren't serializable. This is has been a cause of much concern and consternation throughout the years. Throughout these trying times, however, we had one shining beacon in the night - the NameValueCollection in the System.Collections.Specialized namespace was a completely serializable implementation of a string/string dictionary. Used by the framework for everything from configuration settings to ASP query strings.
Now WCF comes along, and with it much serialization goodness, including the ability to easily serialize IDictionaries over the wire. While this is extremely convenient, we have to remember that IDictionaries are still not really serializable - they're just special-cased in WCF. This means that if we want to transfer a dictionary over the wire and then persist it to disk (using the Caching Application Block, for instance) we're still out of luck.
So I found myself coming back to the familiar old NameValueCollection (NVC from now on, for brevity) and passing that over the wire. Imagine my surprise, then, to realize that WCF fails to serialize an NVC in a service operation.
The error received is a System.Runtime.Serialization.InvalidDataContractException', saying that:
Type 'System.Collections.Specialized.NameObjectCollectionBase' is an invalid collection type since it does not have a valid Add method with parameter of type 'System.Object'
This is true - the NVC (and its base type, NameObjectCollectionBase) doesn't implement IList, only ICollection. Unlike ICollection<T>, ICollection doesn't specify an Add() method - this is specifically added by its children, IList or IDictionary. This means that the NVC is free to add its own Add method, and in our case adds two overloads - one receiving string/string, the other receiving a whole NVC to merge.
Now let's go back to Sowmy Srinivasan's blog entry as linked above, we see that the WCF serialization engine uses these precedence rules:
- CLR built-in types
- Byte array, DateTime, TimeSpan, GUID, Uri, XmlQualifiedName, XmlElement and XmlNode array
- Enums
- Types marked with DataContract or CollectionDataContract attribute
- Types that implement IXmlSerializable
- Arrays and Collection classes including List<T>, Dictionary<K,V> and Hashtable.
- Types marked with Serializable attribute including those that implement ISerializable.
We can see that if I have an ICollection that's also marked as ISerializable (like the NVC), the built-in support for collections will kick in first, despite the type being explicitly marked for serialization.
This seems to be a bug in WCF's handling - on one hand it treats it as a Collection class, but then immediately dismisses it as unacceptable, but without letting it fall back on the 7th serialization option. Unfortunately, we can't mark a class to explicitly NOT take part in the built-in collection serialization, even if I mark it as [DataContract] instead of [CollectionDataContract].
Right now I have no solution for this problem. I'm using a Dictionary now with explicit implementation of IXmlSerializable, and manually serializing it before passing it on to my Cache manager.
In my next post I'll go over some of the deep digging I did to get to these conclusions.
2 Comments
Comments have been disabled for this content.
Jon said
I ran into the same issue. Maybe it'll be fixed in .NET 3.5 SP1. I'm not going to hold my breath though. I'm guessing that using a more mainstream collection type is the way to go. I'm guessing you would run into the same complexities with LINQ.
Alex Kwiatkowski said
This totally sucks!!! I've been trying to create a memcached provider for DotNetNuke 4.9 and they've gone and added a bunch of collections that inherit from NameObjectCollectionBase so i guess i've hit a dead end.