WCF service returning an array of dictionary<string, object>
I've been trying to use a SilverLight client to call an ASP.Net WCF service that would return a Dictionary<string, object>
.
That worked fine when the values in the dictionary were simple types like int
, string
or Guid
.
However, I now have a scenario where I need one of the values to be an array of Dictionary<string, object>
! It all compiles fine and the signature of the service has not changed but the service call now fails.
Any ideas how to fix it? I've trying to annotate my service class and methods with the KnownType
and ServiceKnownType
attributes but that didn't work.
Here is a piece of code:
[ServiceContract(Namespace = "")]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class Service1
{
[OperationContract]
[ServiceKnownType(typeof(Dictionary<string, object>))]
public Dictionary<string, object> GetObject()
{
return new Dictionary<string, object>()
{
{ "pty1", 1 },
{ "pty2", Guid.NewGuid() },
{ "pty3", "blah" },
{ "pty4", new Dictionary<string, object>[]
{
new Dictionary<string, object>()
{
{ "pty1", 4 },
{ "pty2", Guid.NewGuid() },
{ "pty3", "blah" },
}
,
new Dictionary<string, object>()
{
{ "pty1", 4 },
{ "pty2", Guid.NewGuid() },
{ "pty3", "blahblah" },
}
}
}
};
}
}
Thank you for your answers. I've turned on WCF tracing and, as suspected, there is a problem during serialization.
The problem is not the serialization of Dictionary<string, object>
but the one of Array
of Dictionary<string, object>
.
Here the exception logged by WCF service.
There was an error while trying to serialize parameter :GetObjectResult. The InnerException message was 'Type 'System.Collections.Generic.Dictionary`2[[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Object, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]][]' with data contract name 'ArrayOfArrayOfKeyValueOfstringanyType:http://schemas.microsoft.com/2003/10/Serialization/Arrays' is not expected. Add any types not known statically to the list of known types - for example, by using the KnownTypeAttribute attribute or by adding them to the list of known types passed to DataContractSerializer.'. Please see InnerException for more details.
I've also try to define a new DataContract
class with a single data member but it lead to the very same error.
Here is the code for that, followed by the exception logged by WCF logging.
[DataContract]
[KnownType(typeof(ObjectHolder))]
public class ObjectHolder
{
[DataMember]
public object Object { get; private set; }
public ObjectHolder(object obj)
{
this.Object = obj;
}
}
There was an error while trying to serialize parameter :GetObjectResult. The InnerException message was 'Type 'System.Collections.Generic.Dictionary`2[[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[SilverlightApplication7.Web.ObjectHolder, SilverlightApplication7.Web, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]][]' with data contract name 'ArrayOfArrayOfKeyValueOfstringObjectHolderWAwxSTlb:http://schemas.microsoft.com/2003/10/Serialization/Arrays' is not expected. Add any types not known statically to the list of known types - for example, by using the KnownTypeAttribute attribute or by adding them to the list of known types passed to DataContractSerializer.'. Please see InnerException for more details.
Again I've played with ServiceKnownType
for ObjectHolder
, ObjectHolder[]
and even ObjectHolder[][]
since the exception mentions an "ArrayOfArrayOfKeyValueOfstringObjectHolder
".
Still no solution yet.
First of all you should configure WCF tracing in app cofig file that can help you to understand what happends under the hood of service communications. In this case you can easily get all errors that occurs during communication process.
Now, lets try to solve your problem. I almost confident that problem in in known types. In service-oriented world you should manually define all concrete types that can participate in service contract, because DataContractSerializer doesn't provide such information in serialized objects.
In this case it mean, that you should do following:
[ServiceKnownType(typeof(string))]
[ServiceKnownType(typeof(Guid))]
[ServiceKnownType(typeof(int))] // but I think DataContractSerializer can deal himself with primitives
[ServiceKnownType(typeof(YourClass))] //UserDefined types you should add manually
public Dictionary<string, object> GetObject()
Also I recommended to you do not use object in service contract because it VERY error prone. Consider, that later you or one of your colleagues modifies one line of code:
new Dictionary<string, object>()
{
{ "pty1", 4 },
{ "pty2", Guid.NewGuid() },
{ "pty3", new SomeClass() }, //OOPS!!!
}
In this case, when your service tries to return this Dictionary you encounter runtime failure because DataContractSerializer doesn't expect SomeClass in this contract.
One way to solve this problem is to create separate type:
[DataContract]
[KnownType(typeof(Guid))]
[KnownType(typeof(SomeClass1))]
[KnownType(typeof(SomeClass2))]
public class MyType
{
private MyType(object obj) {
Object = obj;
}
public static MyType FromSomeClass1(SomeClass1 c1) {
return new MyType(c1);
}
public static MyType FromSomeClass2(SomeClass2 c2) {
return new MyType(c2);
}
public static MyType FromGuid(Guid guid) {
return new MyType(guid);
}
[DataMember]
public object Object { get; private set; }
}
In this case if you want to add some new type, you should add factory method and KnownTypeAttribute (this approach more verbose, but less error prone).
However, if your client and service written on WCF, you can sacrifice main service-oriented principles and use NetDataContractSerializer instead DataContractSerializer. NetDataContractSerializer is designed to complement DataContractSerializer. You can serialize a type using NetDataContractSerializer and deserialize with DataContractSerializer. But NetDataContractSerializer includes CLR type information in the serialized XML, whereas the DataContractSerializer does not. Therefore, the NetDataContractSerializer can used in serialization and deserialization with any CLR types without any KnownTypes stuff.
Try defining a class the has a single property. That property is a Dictionary of string, object.
Mark the class with DataContract / DataMember. Then define your interface using that class.
However, I now have a scenario where I need one of the values to be an array of dictionary !
The problem is that WCF doesn't know how to serializer / deserialize an array of objects that isn't marked with DataMember
attribute and / or isn't added to ServiceKnownType
s.
For example, if there is some service method, that takes an arbitrary object as a parameter
public interface IService
function DoSomething(Parameter as Object) as integer
end interface
and you want to pass, say an array of integers Integer()
, you must explicitly add this array of integers type to service known types.
<ServiceKnownType(GetType(Integer()))>
public interface IService
function DoSomething(Parameter as Object) as integer
end interface
Then service method can be called
dim Service as IService
dim Argument as Integer()
Service.DoSomething(Argument)
Any ideas how to fix it ?
Try adding <ServiceKnownType(GetType(Dictionary(Of String, Object)()))>
attribute.
精彩评论