WCF Extensibility – Data Contract Resolver
This post is part of a series about WCF extensibility points. For a list of all previous posts and planned future ones, go to the index page.
Continuing on serialization, this post is about a new feature on the WCF serializers in .NET Framework 4.0, the DataContractResolver. Like the previous post on surrogates, there is already good documentation on MSDN, so this post will be short. But since I’ve seen an issue this week on the forums, I thought it’d be worth including the code here in this series.
In order to understand the data contract resolver, we need to take a step back and look at a common issue (and source of confusion) in WCF: known types. In order for WCF to be able to serialize or deserialize an object graph, it needs to know about all the objects in that graph. If the actual type of the object is the same as its declared type, then WCF already knows about it (the “baseInstance” member in the code below). If those types are different, then WCF doesn’t know by default about the type – in the case of the “derivedInstance” in the code below, WCF doesn’t know the MyDerived type, so it won’t serialize it unless we tell it that this type is known.
- public class MyBase { }
- public class MyDerived : MyBase { }
- [DataContract]
- public class MyType
- {
- [DataMember]
- public MyBase baseInstance = new MyBase();
- [DataMember]
- public MyBase derivedInstance = new MyDerived();
- }
There are many good descriptions on why we need the known types and how to set them (“official” MSDN documentation, Youssef Moussaoui’s MSDN blog, Mark Gravell’s Stack Overflow post, Richard Blewett’s blog, etc.) so I won’t go in detail here. But known types serves a purpose that we can declare, when the serialize is being created, which types are to be considered "valid” even though they’re not part of the object declaration. And the part in italic is important here – once the known type list is passed somehow to the serializer, it’s set and it cannot be changed during the serializer lifetime.
In the version 4.0 of the framework, WCF introduced a new concept, , the data contract resolver. Instead of defining the set of known types “statically” (either by specifying the types directly in a KnownTypeAttribute – or ServiceKnownTypeAttribute – or by specifying a method in those attributes which will be invoked when the serializer is being configured), the data contract resolver provides some hooks which allow us to specify, at the moment when the object is being serialized or deserialized, a mapping between the CLR type and the name / namespace in the XML which will be used to represent this “unknown” type. There is good documentation about this topic, including the MSDN reference page, the MSDN detail page, or Youssef Massaoui’s blog, so I won’t repeat that information here.
Class definition
- public abstract class DataContractResolver
- {
- public abstract Type ResolveName(
- string typeName, string typeNamespace,
- Type declaredType, DataContractResolver knownTypeResolver);
- public abstract bool TryResolveType(
- Type type, Type declaredType, DataContractResolver knownTypeResolver,
- out XmlDictionaryString typeName, out XmlDictionaryString typeNamespace);
- }
When the serializer is set up with a DataContractResolver, during serialization, if the serializer finds out an object where the actual type doesn’t match the declared type, it will ask the resolver whether it can process it, by calling TryResolveType. If the resolver can do that, then the serializer will use the resulting name and namespace to write out the XML for that type in the type hint attribute (an attribute with local name “type” and namespace “https://www.w3.org/2001/XMLSchema-instance”. During deserialization, if a type hint attribute is found, then the serializer will call the ResolveName method on the resolver, where it can decide which type to use.
How to use a data contract resolver
Just like with the data contract surrogates shown in last post, the DataContractResolver can be passed as a parameter to the constructor of the WCF serializers (both the DataContractSerializer and the DataContractJsonSerializer). The example below shows one simple example of a resolver in action. Notice that all resolver implementations should delegate the call to the known type resolver parameter passed to it in case it can’t handle the type itself.
- public class MyResolver : DataContractResolver
- {
- XmlDictionary dic = new XmlDictionary();
- const string ResolverNS = "sma.ll/DCRS";
- public override Type ResolveName(string typeName, string typeNamespace, Type declaredType, DataContractResolver knownTypeResolver)
- {
- if (typeNamespace == ResolverNS)
- {
- Type type = Assembly.GetExecutingAssembly().GetType(this.GetType().Namespace + "." + typeName, false);
- if (type != null)
- {
- return type;
- }
- }
- return knownTypeResolver.ResolveName(typeName, typeNamespace, declaredType, null);
- }
- public override bool TryResolveType(Type type, Type declaredType, DataContractResolver knownTypeResolver,
- out XmlDictionaryString typeName, out XmlDictionaryString typeNamespace)
- {
- if (type == typeof(MyOtherType))
- {
- typeName = dic.Add(type.Name);
- typeNamespace = dic.Add(ResolverNS);
- return true;
- }
- else
- {
- return knownTypeResolver.TryResolveType(type, declaredType, null, out typeName, out typeNamespace);
- }
- }
- }
- [DataContract]
- public class MyType
- {
- [DataMember]
- public object Value;
- public override string ToString()
- {
- return string.Format("MyType[Value={0}]", this.Value);
- }
- }
- [DataContract]
- public class MyOtherType
- {
- [DataMember]
- public string Str;
- public override string ToString()
- {
- return string.Format("MyOtherType[Value={0}]", this.Str);
- }
- }
- class SimpleResolverTest
- {
- public static void Test()
- {
- MyType obj = new MyType
- {
- Value = new MyOtherType { Str = "Hello World" },
- };
- MemoryStream ms = new MemoryStream();
- DataContractSerializer dcs = new DataContractSerializer(
- typeof(MyType), null, 65536, false, false, null, new MyResolver());
- dcs.WriteObject(ms, obj);
- Console.WriteLine("Serialized: {0}", Encoding.UTF8.GetString(ms.ToArray()));
- ms.Position = 0;
- object result = dcs.ReadObject(ms);
- Console.WriteLine("Result: {0}", result);
- }
- }
For setting the resolver in the context of WCF services, just like with the surrogates, you can use the DataContractResolver property on the DataContractSerializerOperationBehavior class. For an example of accessing this behavior, check the previous post.
Real world scenario: using dynamically-generated types
The scenario for the data contract resolver is when we want to delay the decision about known (safe?) types until it’s actually needed. I thought for a while but couldn’t think of an interesting scenario for this, until I stumbled upon this post on the forums, where the user wanted to use dynamic types (i.e., those created using the System.Reflection.Emit namespace). That’s a great example of a scenario where the “traditional” known type approach doesn’t work – the actual type may not even exist when the service / serializer is being created. So I created a sample with this scenario. The idea is passing an object between the client and the server which has a member of type System.Object, and the actual value of the field is of a dynamic type.
This is the holder object – nothing fancy here.
- [DataContract]
- public class MyHolder
- {
- [DataMember]
- public object MyObject { get; set; }
- public override string ToString()
- {
- return string.Format("MyHolder[MyObject={0}]", this.MyObject);
- }
- }
And in the service contract I wanted to have three scenarios: one where the client sends a request to the server with a dynamic type (which originally didn’t exist in the server) and receives the same type; one which the client just sends something new to the service, but receives a known type; and one which the server is the one who sends something back to the client that it doesn’t know yet.
- [ServiceContract(Name = "IDynamicContract", Namespace = "https://my.dynamic.contract")]
- public interface IDynamicContract
- {
- [OperationContract]
- MyHolder EchoHolder(MyHolder holder);
- [OperationContract]
- MyHolder GetHolder(string typeName, string[] propertyNames, object[] propertyValues);
- [OperationContract]
- string PutHolder(MyHolder holder);
- }
The server implementation is straightforward, although the GetHolder method uses a type builder library which I’ll mention later.
- public class DynamicService : IDynamicContract
- {
- public MyHolder EchoHolder(MyHolder holder)
- {
- Console.WriteLine("In service, holder = {0}", holder);
- return holder;
- }
- public MyHolder GetHolder(string typeName, string[] propertyNames, object[] propertyValues)
- {
- Console.WriteLine("In service, creating a type called {0}", typeName);
- Type type = DynamicTypeBuilder.Instance.GetDynamicType(typeName);
- object instance = Activator.CreateInstance(type);
- for (int i = 0; i < propertyNames.Length; i++)
- {
- type.GetProperty(propertyNames[i]).SetValue(instance, propertyValues[i], null);
- }
- return new MyHolder { MyObject = instance };
- }
- public string PutHolder(MyHolder holder)
- {
- Console.WriteLine("In service, holder = {0}", holder);
- return holder.ToString();
- }
- }
And before I go on with more code, the usual disclaimer: this is a sample for illustrating the topic of this post, this is not production-ready code. I tested it for a few types and it worked, but I cannot guarantee that it will work for all scenarios (please let me know if you find a bug or something missing). Also, for simplicity sake it doesn’t have a lot of error handling which a production-level code would (i.e., in the server GetHolder / PutHolder method, in the type builder, etc.), and the type builder library only supports a fixed set of types, chosen for this sample only – in a real usage if the types would be created from some external storage such as a database (if they were fixed they would simply be written as “normal” ones instead of dynamic).
Now onto the interesting code. The DynamicTypeBuilder helper class is a singleton which knows how to build some pre-defined types. All the types are created in the same (dynamic) assembly so we can verify whether a certain type exists or not. The types are cached so that we only create a given type once.
- public class DynamicTypeBuilder
- {
- internal const string AssemblyName = "ReflectionEmitAssembly";
- private static DynamicTypeBuilder instance;
- private static ModuleBuilder myModule;
- private Dictionary<string, Type> typeCache = new Dictionary<string, Type>();
- private Dictionary<string, Func<Type>> typeCreators = new Dictionary<string, Func<Type>>
- {
- { "Person", () => CreatePersonType() },
- { "Address", () => CreateAddressType() },
- { "Product", () => CreateProductType() },
- };
- static DynamicTypeBuilder()
- {
- AppDomain myDomain = AppDomain.CurrentDomain;
- AssemblyName myAsmName = new AssemblyName(AssemblyName);
- AssemblyBuilder myAssembly =
- myDomain.DefineDynamicAssembly(myAsmName,
- AssemblyBuilderAccess.RunAndSave);
- myModule =
- myAssembly.DefineDynamicModule(myAsmName.Name,
- myAsmName.Name + ".dll");
- }
- private DynamicTypeBuilder()
- {
- }
- public static DynamicTypeBuilder Instance
- {
- get
- {
- if (instance == null)
- {
- instance = new DynamicTypeBuilder();
- }
- return instance;
- }
- }
- public Type GetDynamicType(string typeName)
- {
- Type result = null;
- if (this.typeCache.ContainsKey(typeName))
- {
- result = this.typeCache[typeName];
- }
- else if (this.typeCreators.ContainsKey(typeName))
- {
- result = this.typeCreators[typeName]();
- this.typeCache.Add(typeName, result);
- }
- return result;
- }
- }
The types supported in this sample are simple DTO types, with a few properties, and a ToString implementation which will help showing their values. Each type is created using a helper method as shown below.
- private static Type CreatePersonType()
- {
- return CreateType(
- "Person",
- new string[] { "name", "age" },
- new string[] { "Name", "Age" },
- new Type[] { typeof(string), typeof(int) });
- }
- private static Type CreateAddressType()
- {
- return CreateType(
- "Address",
- new string[] { "street", "city", "zip" },
- new string[] { "Street", "City", "Zip" },
- new Type[] { typeof(string), typeof(string), typeof(string) });
- }
- private static Type CreateProductType()
- {
- return CreateType(
- "Product",
- new string[] { "id", "name", "price" },
- new string[] { "Id", "Name", "Price" },
- new Type[] { typeof(int), typeof(string), typeof(decimal) });
- }
CreateType is where the types are actually created. Each type is decorated with the DataContractAttribute and has fields and properties defined based on the parameters passed to the method. I’ll omit the detailed implementation of CreateProperty and OverrideToString here (they’re in the project on the code gallery), as they aren’t really related to the topic on this post.
- private static Type CreateType(string typeName, string[] fieldNames, string[] propertyNames, Type[] propertyTypes)
- {
- // public class Address
- TypeBuilder typeBuilder =
- myModule.DefineType(typeName, TypeAttributes.Public);
- // Add [DataContract] to the type
- CustomAttributeBuilder dataContractBuilder = new CustomAttributeBuilder(
- typeof(DataContractAttribute).GetConstructor(Type.EmptyTypes),
- new object[0]);
- typeBuilder.SetCustomAttribute(dataContractBuilder);
- List<PropertyBuilder> propertyBuilders = new List<PropertyBuilder>();
- // Define fields and properties
- for (int i = 0; i < fieldNames.Length; i++)
- {
- FieldBuilder field =
- typeBuilder.DefineField(
- fieldNames[i],
- propertyTypes[i],
- FieldAttributes.Private);
- propertyBuilders.Add(CreateProperty(typeBuilder, propertyNames[i], field));
- }
- // Override ToString
- OverrideToString(typeBuilder, propertyBuilders.ToArray());
- return typeBuilder.CreateType();
- }
Now onto the resolver. First: serialization (TryResolveType). When an instance of MyHolder is being serialized, if since the actual type of the MyObject property comes from the dynamic assembly (here we do a simple name comparison), we resolve that type using a constant namespace and the type name. If it doesn’t, then we delegate to the known type resolver which is passed to the method. During deserialization (ResolveName) we go the opposite way: if the namespace is our constant value, and we can load the dynamic type from our builder, then we return that type. If not, we again delegate the call to the resolver.
- public class DynamicTypeResolver : DataContractResolver
- {
- const string ResolverNamespace = "https://dynamic/" + DynamicTypeBuilder.AssemblyName;
- public override Type ResolveName(string typeName, string typeNamespace, Type declaredType, DataContractResolver knownTypeResolver)
- {
- if (typeNamespace == ResolverNamespace)
- {
- Type result = DynamicTypeBuilder.Instance.GetDynamicType(typeName);
- if (result != null)
- {
- return result;
- }
- }
- return knownTypeResolver.ResolveName(typeName, typeNamespace, declaredType, null);
- }
- public override bool TryResolveType(Type type, Type declaredType, DataContractResolver knownTypeResolver, out XmlDictionaryString typeName, out XmlDictionaryString typeNamespace)
- {
- if (type.Assembly.GetName().Name == DynamicTypeBuilder.AssemblyName)
- {
- XmlDictionary dic = new XmlDictionary();
- typeName = dic.Add(type.Name);
- typeNamespace = dic.Add(ResolverNamespace);
- return true;
- }
- else
- {
- return knownTypeResolver.TryResolveType(type, declaredType, null, out typeName, out typeNamespace);
- }
- }
- }
The infrastructure pieces are done, we can now test to see if everything works out fine. First the server: in a simple self-hosted service, we replace the resolver in all operations in the endpoint contract. Nothing fancy here.
- static void Main(string[] args)
- {
- string baseAddress = "https://" + Environment.MachineName + ":8000/Service";
- ServiceHost host = new ServiceHost(typeof(DynamicService), new Uri(baseAddress));
- ServiceEndpoint endpoint = host.AddServiceEndpoint(
- typeof(IDynamicContract), new BasicHttpBinding(), "");
- foreach (OperationDescription operation in endpoint.Contract.Operations)
- {
- operation.Behaviors.Find<DataContractSerializerOperationBehavior>()
- .DataContractResolver = new DynamicTypeResolver();
- }
- host.Open();
- Console.WriteLine("Host opened, press ENTER to close");
- Console.ReadLine();
- host.Close();
- }
The client starts in a similar way: define the channel factory, update the DataContractSerializerOperationBehavior to set the resolver, then call the server operations. The Echo operation is shown below.
- string endpointAddress = "https://" + Environment.MachineName + ":8000/Service";
- var factory = new ChannelFactory<Server.IDynamicContract>(
- new BasicHttpBinding(), new EndpointAddress(endpointAddress));
- foreach (var operation in factory.Endpoint.Contract.Operations)
- {
- operation.Behaviors.Find<DataContractSerializerOperationBehavior>()
- .DataContractResolver = new DynamicTypeResolver();
- }
- var proxy = factory.CreateChannel();
- Console.WriteLine("Created the proxy");
- object product = CreateProduct();
- Console.WriteLine("Product: {0}", product);
- Console.WriteLine("Calling the service passing the product...");
- MyHolder input = new MyHolder { MyObject = product };
- MyHolder output = proxy.EchoHolder(input);
- Console.WriteLine("Result: {0}", output);
- Console.WriteLine();
The PutHolder operation is similar to the first one: the client sends an object which doesn’t exist on the sever, and it’s then created by the resolver at the server process. The GetHolder is interesting in that in the client the type doesn’t exist yet, and we can show by enumerating the types in the dynamic assembly. But the operation call should work out just fine as well.
- Type[] dynamicTypes = product.GetType().Assembly.GetTypes();
- Console.WriteLine("Do we know about Person yet?");
- Console.WriteLine("All dynamic types so far");
- foreach (var dynamicType in dynamicTypes)
- {
- Console.WriteLine(" {0}", dynamicType.Name);
- }
- Console.WriteLine("Retrieving a new type from the server");
- output = proxy.GetHolder("Person", new string[] { "Name", "Age" }, new object[] { "John Doe", 33 });
- Console.WriteLine("Output: {0}", output);
- Console.WriteLine("Type of output value: {0}", output.MyObject.GetType());
- Console.WriteLine("Is it on the same assembly as the other types? {0}",
- output.MyObject.GetType().Assembly.Equals(address.GetType().Assembly));
And that’s it. The full code (including the methods not mentioned here) is on the code gallery sample (link to the code at the end of this post).
Final thoughts about data contract resolvers
Unlike with the data contract surrogates, resolvers can be used to change the name / namespace of primitive types. If you have, for example, a string object assigned to a member whose declared type is object, you can safely use a resolver to change the type name / namespace used in the type hint for the string. Another thing is that using a resolver makes the serialization slower than using the “standard” known types feature (since known types are static, they can be cached and the calls don’t need to be made all the time), so be aware that when using the resolver, the additional functionality comes at a price in terms of execution time.
Coming up
Wrapping up the serialization extensibility, with a short post on extensible data object.
Comments
- Anonymous
March 14, 2012
Hi, I seem to be facing an issue with deserializing the object in silverlight. I am using the generic resolver in my wcf service to resolve the dynamic known types and sending the entity object on a call back channel using duplex service. I get the message at the client end but the entity object is null. how can I overcome this deserializing issue. - Anonymous
March 27, 2012
Abhaye, Silverlight doesn't have the data contract resolver feature implemented, so that won't work. What you'll likely need to do is to change the contract on the SL side to receive the object as a XElement, and from there do the deserialization manually. - Anonymous
January 19, 2015
Hi Carlos.Thanks a lot for the post, it's very useful.What if we have some classes that are inherited from base classes that are not decorated with [DataContract] attribute? could we write a custom Resolver for them ? if yes, could u please have an example?Thanks a lot man :) - Anonymous
April 14, 2015
Hey Now Carlos Figueira;This is a stellar article on the Data Contract Resolver in WCF. It is a great source of info on the ServiceKnownType attribute too.Thank you for the info!Catto