Preserving Object Reference in WCF and Resolve KnownTypes Error at Runtime

By default object references are not preserved by the DataContractSerializer, Values of an object referenced multiple times is serialized multiple times. Think about a Customer who has a Billing and Shipping addresses. Often those addresses are same and you would prefer to say- same as above. Preserving Object Reference is very similar to the address example where we would like to use shipping address as a pointer to reference mailing address.

The default serializer in WCF DataContractSerializer does not know how to serialize/deserialize types in the event of object inheritance and polymorphism. Developers are required to decorate the operation contracts or the parent objects with the knowntype attribute of the subclass (derived class). It’s very hard to keep track of children when you have so many children and possibly grandchildren. If you are NOT required to support interoperability, you can use NetDataContractSerializer to avoid KnownTypes issue. If you have to use DataContractSerializer to support interoperability, you can still lift some burden from developers by adding knownTypes using reflection. The example I am using here does loop though all the public types in the same assembly in search for knwonTypes but you can expand the search as per your requirements.

Well, writing this behavior itself does not do the job and you must wire up this behavior. How to wire up this event is part of another post but, in brief, you can add this as an attribute on operation contract. I attached them via IServiceBehavior/IEndpointBehavior implementation.

Let’s look at the codes…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel.Description;
using System.Runtime.Serialization;
using System.Xml;
using System.Reflection;
using System.Collections;
using System.ServiceModel;
using System.ServiceModel.Channels;
 
namespace WCF.Behaviors
{
///
/// Use this behavior on both client and server sides to preserve object references when interoperatibility is not a requirement.
/// This behavior will significantly increase the performence of WCF serialization between .Net CLR based client and service.
/// http://msdn.microsoft.com/en-us/library/aa730857(v=vs.80).aspx#netremotewcf_topic8
/// http://blogs.msdn.com/b/sowmy/archive/2006/03/26/561188.aspx
///
 
public class CustomDataContractSerializerOperationBehavior : DataContractSerializerOperationBehavior
{
private readonly OperationDescription operation = null;
private readonly static object syncObject = new object();
private static Hashtable dcKnownTypes = new Hashtable();
 
public CustomDataContractSerializerOperationBehavior(OperationDescription operationDescription) : base(operationDescription)
{
operation = operationDescription;
}
 
public override XmlObjectSerializer CreateSerializer(Type type, string name, string ns, IList knownTypes)
{
if (UseDataContractSerializer()==true)
{
if (type.IsArray)
{
ResolveKnownTypes(type.GetElementType(), ref knownTypes);
}
else
{
ResolveKnownTypes(type, ref knownTypes);
}
return new DataContractSerializer(type, name, ns, knownTypes);
}
else
{
return new NetDataContractSerializer(name, ns);
}
}
 
public override XmlObjectSerializer CreateSerializer(Type type, XmlDictionaryString name, XmlDictionaryString ns, IList knownTypes)
{
if (UseDataContractSerializer() == true)
{
if (type.IsArray)
{
ResolveKnownTypes(type.GetElementType(), ref knownTypes);
}
else
{
ResolveKnownTypes(type, ref knownTypes);
}
 
return new DataContractSerializer(type, name, ns, knownTypes,
0x7FFFFFFF,/* maxItemsInObjectGraph is 0x7FFFFFFF=2147483647 */
false,/* ignoreExtensionDataObject */
true,/* preserveObjectReferences */
null); /* dataContractSurrogate */
}
else
{
return new NetDataContractSerializer(name, ns);
}
}
 
private void ResolveKnownTypes(Type type, ref IList knownTypes)
{
if (type == null)
{
return;
}
 
if (IsSimpleTypeOrObject(type) == false)
{
lock (syncObject)
{
if (dcKnownTypes.Contains(type.FullName) == true)
{
knownTypes = dcKnownTypes[type.FullName] as IList;
return;
}
else
{
if (knownTypes == null)
{
knownTypes = new List();
}
 
//Resolve Subclasses
ResolveSubClasses(type, ref knownTypes);
 
if (knownTypes.Count > 0)
{
if (dcKnownTypes.Contains(type.FullName) == false)
{
dcKnownTypes.Add(type.FullName, knownTypes);
}
}
}
}
}
}
 
private void ResolveSubClasses(Type type, ref IList knownTypes)
{
//Get all the public types available on the Assembly of the object type
Type[] publicTypes = type.Assembly.GetExportedTypes();
 
//Get all the public properties on the object type
PropertyInfo[] propInfos = type.GetProperties(BindingFlags.Instance | BindingFlags.Public);
 
foreach (Type pubType in publicTypes)
{
Type unknownType = pubType;
 
//if the public type is arrary, then get the element type
if (pubType.IsArray == true)
{
unknownType = pubType.GetElementType();
}
 
if (unknownType == null)
{
continue; //to next iteration
}
 
if (unknownType.IsInterface==true)
{
continue; //knowntype is not needed for interface
}
 
//Is the unknownType in the loop a subclass of the object type? If yes, add it to KnownType.
if (unknownType.IsSubclassOf(type))
{
if (knownTypes.Contains(unknownType) == false)
{
knownTypes.Add(unknownType);
}
}
 
//Is the object type an interface? If so, check if the unknownType implements the interface.
if (type.IsInterface == true)
{
Type interfaceType = unknownType.GetInterface(type.Name);
 
//Is unknownType implements the object type interface? If so, add the unknownType to KnownType.
if (interfaceType != null)
{
if (knownTypes.Contains(unknownType) == false)
{
knownTypes.Add(unknownType);
}
}
}
 
if (propInfos != null)
{
//Loop through each of the public property available on the object type.
foreach (PropertyInfo prop in propInfos)
{
Type propType = prop.PropertyType;
 
//If the PropertyType is an Array, get it's element type instead.
if (propType.IsArray == true)
{
propType = propType.GetElementType();
}
 
if (propType == null)
{
continue; //to next iteration
}
 
if (IsSimpleTypeOrObject(propType) == false)
{
//Is PropertyType is an interface? If so, check if the unknownType implements the PropertyType interface.
if (propType.IsInterface == true)
{
Type interfacePropType = unknownType.GetInterface(propType.Name);
 
//Is unknownType implements the PropertyType interface? If so, add the unknownType to KnownType.
if (interfacePropType != null)
{
if (knownTypes.Contains(unknownType) == false)
{
knownTypes.Add(unknownType);
}
}
}
else if (unknownType == propType)
{
//PropertyType and unknownType are same. Add the unknownType to KnownType.
if (knownTypes.Contains(unknownType) == false)
{
knownTypes.Add(unknownType);
}
}
}
}
}
}
 
}
 
private static bool IsSimpleTypeOrObject(Type type)
{
return type.IsPrimitive || type.IsValueType || type == typeof(string) || type == typeof(DateTime) || type==typeof(object);
}
 
///
/// Checks the OperationContext.Current.IncomingMessageVersion.
/// If the MessageVersion is Soap11 returns true. Soap11 provides interoperatibility.
///
 
///
private bool UseDataContractSerializer()
{
bool retVal = false;
OperationContext opContext = OperationContext.Current;
if (opContext != null)
{
MessageVersion inMsgVer = opContext.IncomingMessageVersion;
if (inMsgVer != null)
{
if (inMsgVer == MessageVersion.Soap11)
{
retVal = true;
}
}
}
 
return retVal;
}
}
}

Acknowledgements:
http://msdn.microsoft.com/en-us/library/aa730857(v=vs.80).aspx#netremotewcf_topic8
http://blogs.msdn.com/b/sowmy/archive/2006/03/26/561188.aspx

Leave a Reply