Yeah! It's part 10 of my Adventure series. Sorry, I'm not giving a party or anything, but it feels like a milestone to me.
In this episode we are going to look at an issue I ran into with WCF and Silverlight 2 when using a specific structure in the datacontract. In our case, we wanted to use a Dictionary<string,> that would contain another Dictionary<string,> as the value. Have a look at our data contract to have a clearer picture:
[DataContract]
[KnownType(typeof(Dictionary<string, object>))]
public class CompositeType
{
private Dictionary<string, object> _properties;
private string _stringValue = "Hello ";
public CompositeType()
{
_properties = new Dictionary<string, object>();
}
[DataMember]
public string StringValue
{
get { return _stringValue; }
set { _stringValue = value; }
}
[DataMember]
public Dictionary<string, object> Properties
{
get
{
return _properties;
}
}
}
As you can see, the dictionary type is a part of our DataContract, trough the use of the DataMember attribute. We also added that same type to our known types list, trough the use of the KnownTypes attribute. We could have used the ServiceKnownTypeAttribute
on our interface type as well, with the same result.
If we would now use a Silverlight 2 client with a service reference to this service and make a call to our service, everything seems fine...
...until, that is, we use a Dictionary<string>
instance as a value in the Properties
property. In that case an exception occurs, stating:
There was an error while trying to deserialize parameter http://tempuri.org/:GetDataUsingDataContractResult. The InnerException message was 'Error in line 1 position 584. Element 'http://schemas.microsoft.com/2003/10/Serialization/Arrays:Value' contains data of the 'http://schemas.microsoft.com/2003/10/Serialization/Arrays:ArrayOfKeyValueOfstringanyType' data contract. The deserializer has no knowledge of any type that maps to this contract. Add the type corresponding to 'ArrayOfKeyValueOfstringanyType
' 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
.'. Please see InnerException for more details.
If we wouldn't have added the dictionary type to our known types list, the service call would have timed out, without a proper exception, indicating that some problem would have occurred in the service.
I've been digging to see if I could come up with a solution. Googling the exception I got plenty of examples with derived types, but non with nested types and late binding, like I'm using. After some reading on datacontracts and digging into the reference code in the client, I figured that known types in the service might just not be distributed to the client. To prove my theory I started looking at the generated service client instance at runtime. I looked in the EndPoint property which contains the contract, which in turn contains the operations for the contract. As known types are usually scoped at the operations level, I checked to see if it contained any items in the KnownTypes
property and in fact it didn't. This means my theory is correct and I should add the dictionary type to the known types list of the operation. As I didn't want to do this in generated code, I decided to do it at runtime:
private Service1Client GetServiceClient()
{
Service1Client client = new Service1Client();
foreach (var operation in client.Endpoint.Contract.Operations)
{
operation.KnownTypes.Add(typeof(Dictionary<string, object>));
}
return client;
}
If I now run the client, everything runs like a charm. What we actually did, by adding these known types, is telling the DataContractSerializer
to include our dictionary type when deserializing any incoming xml messages.