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…

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