问题
I am trying to pass a class with a Dictionary property over WCF and it is failing for one method but works for another. When the class is returned inside a List
, it works. But when the class is returned inside a DataTable
, the client just says the connection was disconnected and no error shows up.
Here is the class causing issues:
[DataContract]
public class DetailLog
{
[DataMember]
public string Name
{
get;
set;
}
[DataMember]
public string SubAction
{
get;
set;
}
[DataMember]
public Dictionary<string, string> Fields
{
get;
set;
}
[DataMember]
public string UserName
{
get;
set;
}
}
I started out creating a method that works without issue:
public List<DetailLog> GetDetailLog(List<int> IDs, List<int> actionTypeIds, List<int> userIds, DateTime beginDate, DateTime endDate)
Then we needed to create some very dynamic reports so I used a DataTable which we have used previously for other dynamic reports.
But I needed to pass along the DetailLog class so I created a DataTable column of that type:
public DataTable GetCustomDetailReport(int CustomReportID, List<CustomReportFilter> reportFilters)
{
DataTable data = new DataTable();
...
data.Columns.Add("DetailLog", typeof(DetailLog));
...
}
This method would would exit fine on the WCF host side but the client side would error about the connection being lost. I tried adding ServiceKnownType for the OperationContract in the interface but it did not fix it:
[OperationContract]
[ServiceKnownType(typeof(DetailLog))]
DataTable GetCustomUserAuditReport(int CustomReportID, List<CustomReportFilter> reportFilters);
I cannot really debug the serialization when the method returns the DataTable so I added this code to the end of the GetCustomDetailReport() to catch the error.
DataContractSerializer ser = new DataContractSerializer(typeof(DataTable), new List<Type> { typeof(DetailLog) });
ser.WriteObject(Stream.Null, data);
When I did, I saw an exception
Cannot serialize member ... of type System.Collections.Generic.Dictionary`2 because it implements IDictionary.
回答1:
Your issue is as follows:
Dictionary<TKey, TValue>
is supported by the data contract serializer used in WCF, as explained in the docs. This is why yourDetailLog
class can be sent over the wire successfully by WCF as a root object.This serializer also supports IXmlSerializable to allow types to manually serialize themselves to XML.
DataTable implements
IXmlSerializable
.Internally,
DataTable
serializes non-primitive entries using XmlSerializer -- a completely different serializer that uses a completely different code base.XmlSerializer does not support dictionaries. Thus your
DetailLog
cannot be sent over the wire by WCF when nested in aDataTable
.As an unrelated problem, you also need to set the data table name, serialization will throw an exception if you do not:
data.TableName = "CustomDetailReport"; // For instance
To work around the issue with dictionaries, you need to make your DetailLog
class serializable by both serializers. The question How to serialize/deserialize to Dictionary<int, string> from custom XML not using XElement? gives a variety of ways to serialize its dictionary property, including using a proxy array property:
[XmlType("KeyValue"), XmlRoot("KeyValue")]
public class SerializableKeyValuePair<TKey, TValue>
{
public TKey Key { get; set; }
public TValue Value { get; set; }
}
public static class SerializableKeyValuePairExtensions
{
public static SerializableKeyValuePair<TKey, TValue> ToSerializablePair<TKey, TValue>(this KeyValuePair<TKey, TValue> pair)
{
return new SerializableKeyValuePair<TKey, TValue> { Key = pair.Key, Value = pair.Value };
}
}
[DataContract]
public class DetailLog
{
[DataMember]
public string Name { get; set; }
[DataMember]
public string SubAction { get; set; }
[DataMember]
[XmlIgnore]
public Dictionary<string, string> Fields { get; set; }
[IgnoreDataMember]
[XmlArray("Fields")]
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DebuggerBrowsable(DebuggerBrowsableState.Never)]
public SerializableKeyValuePair<string, string>[] FieldsArray
{
get
{
return Fields == null ? null : Fields.Select(p => p.ToSerializablePair()).ToArray();
}
set
{
Fields = value == null ? null : value.ToDictionary(p => p.Key, p => p.Value);
}
}
[DataMember]
public string UserName { get; set; }
}
WCF should now be able to send your DataTable
over the wire successfully.
来源:https://stackoverflow.com/questions/34316613/cannot-serialize-member-of-type-system-collections-generic-dictionary2-beca