Data Contract Hierarchy and KnownTypes

Data Contract Hierarchy

Every class in a hierarchy needs to apply the DataContractAttribute because it is opt in and not inheritable.
So if you derive a class it needs to apply the DataContractAttribute also (same is still true for DataMemberAttribute).

[DataContract]
class MyBaseType
{ 
[DataMember]
string Member;
}
[DataContract]
class MySubType
{ 
[DataMember]
string OtherMember;
}

If you do not opt in, this will at load time result in an InvalidDataContractException. Yet even though you create this hierarchy you can not apply it in an Service like this:

[ServiceContract]
interface IMyService
{
// cannot use MySubType here
[OperationContract]
void MyOperation(MyBaseType baseType);
}

This is again due to SOAP as the underlying Messagechannel. You do not pass a reference to an object but in essence an xml data object. So you do not get the advantage of virtual tables for sub and basetypes that the .Net runtime supports.
To overcome this, you can WCF tell which types are allowed by applying the KnownTypeAttribute.

[AttributeUsage(AttributeTargets.Struct | AttributeTargets.Class, AllowMultiple=true)]
public sealed class KnownTypeAttribute : Attribute
{
public KnownTypeAttribute(Type type);
}

To utilize this add to this to the base class like so:

[DataContract]
[KnownTypeAttribute(typeof(MySubType))]
class MyBaseType
{
[DataMember] 
string Member;
}

The KnownTypeAttribute can be applied as often as you would like. (see AllowMultiple=true).
Another technique to address multiple known types is to do it programmatically. For this you can derive from the abstract DataContractResolver class and override the TryResolveType and TryResolveName methods.

public abstract class DataContractResolver
{
protected DataContractResolver();
public abstract bool TryResolveType(Type type, Type declaredType, 
DataContractResolver knownTypeResolver, out XMLDictionaryString typeName, 
out XMLDictionaryString typeNamespace);
public abstract Type ReolveName(string typeName, string typeNamespace, 
Type declardType,DataContractResolver knownTypeResolver); 
}

If the TryResolve method returns false, the WCF call fails. Else it counts as if KnownTypeAttribute was supplied.
To let your Resolver have any effect you need to attach it for each operation on the client or the service endpoint. It is a local implementation detail and therefore you need to implement it as a behavior either programmatically with the ServicBehaviorAttribute or in the .config file.

You can find the DataContractResolver in the DataContractSerialierOperationBehavior, which in turn you can find in the OperationDescriptionCollection of the ContractDescription. The ContractDescription itself can be found as a member of the ServiceEndpoint.

var host = new ServiceHost(typeof(MyService)); // implmentation of IMyService
foreach (ServiceEndpoint endpoint in host.Description.Endpoints)
{
foreach (OperationDescription opDescription in endpoint.Contract.Operations)
{
var behavior = opDescription.Behaviors.Find<DataContractSerialierOperationBehavior>();
behavior.DataContractResolver = new MyResolver(); // derived class from DataContractResolver
}
}

To shortcut this you can also use the following constellation, that I tend to use.

[ServiceContract]
public interface MyTypeResolvingService
{
[OperationContract]
[ServiceKnownTypeAttribute("GetTypes", typeof(StaticKnownTypeResolver)]
void MyMethod(object o);
}
static class StaticKnownTypeResolver
{
internal static IEnumerable<Type> GetTypes(ICustomAttributeProvider provider)
{
return new Type[] { /* resolve your types */};
}
}

Versioning

Any client should be able to consume any version of a service. This results in three main versioning scenarios:

  1. New Members
  2. Missing members
  3. Forward and Backward compability with versioning roundtripping

New Members are the easiest, because those are simply igonred in older versions during deserialization.
For Missing Members simply the default value is deserialized, which is null for reference types and can get yucky, as we all know the pain of a NullReferenceException.
Here you can again utilize the OnDeserializing technique with the Initialization method I showed you before.

To mark a new member as a required member, you can mark it with the IsRequired flag. If a client receives this new Member, than it is ignored. Does the Service expect this type, then it throws an NetDispatcherFaultException. This is useful if the service cannot function without this information.

RoundTripping

This standard versioning handling is not optimal. When you have multiple services interacting, and some do know the newest version and some do not, then sometimes the members will serialize the members and others will not. This is called a roundtripping issue because data is lost in all those transformations. To cope with such a scenario, you can simply let your Dataobjects implement the IExtensibleDataObject interface.

public interface IExtensibleDataObject
{
ExtensionDataObject ExtensionData {get;set;}
}

What this does is, if you have unrecognized members in your SOAP message, WCF will store those in the ExensionDatas internal linked list. On the way out those stored objects are again serialized and send with the next message. To activate this behavior in your Dataclass, simply add the ExtensionDataObject with the explicit interface implementation.
This in turn can also be turned off on the ServiceBehaviorAttribute with setting the IgnoreExtensionObject to true.

.Net Types and Collections as DataContracts

Enums are alway serializeable so you do not need the DataContractAttribute. You can exclude certain enum members by applying the EnumMemberAttribute to all enum members, except for those you want to exclude.

[AttributeUsage(AttributeTargets.Field, Inherited =false)]
public sealed class EnumMemberAttribute : Attribute
{
public string Vale { get;set; }
}
[DataContract]
public enum MyEnum
{
[EnumMember]
Member_1,
[EnumMember]
Member_2,
NotIncludedMember
}

Delegates and events are serializeable, yet the internal invocation list is serialized with the delegate. This is not a desired behavior for serviceorientation and so you would try to avoid serializing events/delegates. So best is to decorate the events with a NonSerializedAttribute.

Because Generics are a CLR concept, you cannot serialize and use them in WCF. Yet in DataContract classes you can use it, and bind them in the operationcontract, if the bound type is serializeable. (Whereas the bound type is the Type that is specified in the angle brackets.)

[DataContract]
public class MyGenericType<T>
{
[DataMember]
public T Member{get;set;}
}
[ServiceContract]
public class MyService
{
[OperationContract]
void MyMethod(MyGenericType<int> genType);
}

But be aware that if you import the contract on the client side, the name of the class might be looking strange. This is because the type name and namespace of the Type are being combined to some sort of quasi unique hash. This helps WCF to figure out how to serialize/deserialize the value.

Collections

In .Net everything that implements IEnumerable or IEnumerable<T> is a collection. But of course those are CLR concepts, and so they are not marshalable for WCF. Yet because collections are quite useful, WCF supports extra marshaling rules for collections.

WCF uses for each collection an array by default. But to marshal a collection, this collection must be Serializeable and have a method with one of the following signatures:

    public void Add(object obj);
public void Add(T item);

Yet it is only important for marshaling, and need not to be implemented. This is true for custom collections as well.

CollectionDataContractAttribute

But as you might expect,this aproach is not quite what one wants. This is because you need the SerializableAttribute instead of the serviceoriented DataContractAttribute, also there might be a semantic difference between the choosen collection and the marshaled array of WCF. This semantic difference might even be the reason why the collection was choosen in the first place. Also there is no compile time check if the Add methodsignature exists.

WCF’s go at this is the CollectionDataContractAttribute.

[AttributeUsage(AttributeTargets.Struct|AttributeTargets.Class, Inherited = false)]
public class CollectionDataContractAttribute : Attribute
{
public string Name { get; set;}
public string Namespace { get; set;} 
}

This Attribute is analogous to the DataContractAttribute and similarly it does not make the collection serializeable. Instead the collection is exposed as a generic LinkedList. You still have to apply the Add method signature.
One cannot apply the DataContract and CollectionDataContractAttribute at the same time.

Dictionaries

Hashtables/Dictionaries by their very nature do not marshal very good as arrays or lists. But because they are so handy, they get an own implementation in WCF.

If the colelction implements IDictionary and is a serializeable collection, it will be marshaled as an Dictionary<object, object>. If the collection supports the IDictionary<K, V> it will be marshaled as such, if the typeparameters are bound at compiletime. This is true for custom dictionaries with the CollectionDataContractAttribute.

Summary

As we have seen, WCF supplies a quite thought out methods to make your custom types serializeable for transaction to other services.

To make your types serializeable for WCF, so they map onto SOAP message, you need to apply the DataContractAttribute on your custom Type. This will not serialize the members of that Type though. For this you’ll need to decorate each member with the DataMemberAttribute.

WCF also allows you to supply DataContracts in a form of hierarchies. To resolve this hierarchis to map onto SOAP, you can supply the KnownTypeAttribute on the BaseType. I also showed some more sophisticated methods in this post.

Versioning of this DataContracts, can be handled by implementing IExtensibleObject interface on your DataContracts. Another way is to silently ignore new members, or to throw an exception if the schemas do not match anymore but you need the information of the newer version.

The last section explored the use of Collections in WCF. This can be done by using the build in collections, that are serializeable, but get transferred as array. To support your collections, you can use the CollectionDataContractAttribute.

 

Categories: Contracts

0 Comments

Leave a Reply

Avatar placeholder