Rico Suter's blog.
 


Sometimes a web service client wants to know if a collection navigation property (a navigation property with “many” references) of a received entity object has been loaded on the server (e.g. by using Entity Framework’s Include method). By default, not loaded collection navigation properties are serialized as empty collections (not null). This is why the client cannot differentiate between a not loaded property and an empty collection. In one of my projects, I needed to have this information to know if a property is already loaded or has to be lazy loaded using an additional web service call.

A way to accomplish the goal was to modify the WCF’s serialization process. The following listings show a simple to use service behavior. This service behavior changes the serialization process of the WCF service for all properties with type IRelatedEnd (implemented by EntityCollection).

[ServiceContract]
[NullEntityCollectionSerializer]
public MyService
{
    ...
}

The following listing shows the implementation of the service behavior. The behavior changes the

DataContractSerializerOperationBehavior behavior for each operation of the web service by adding a custom DataContractSurrogate and a custom DataContractResolver.

public class NullEntityCollectionSerializerAttribute : Attribute, IServiceBehavior
{
    public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) { }

    public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, 
        Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters) { }

    public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
    {
        foreach (var d in serviceDescription.Endpoints)
        {
            foreach (var operation in d.Contract.Operations)
            {
                var operationBehavior = operation.Behaviors.Find<DataContractSerializerOperationBehavior>();
                operationBehavior.DataContractSurrogate = new NullEntityCollectionDataContractSurrogate();
                operationBehavior.DataContractResolver = new NullSerializerProxyDataContractResolver();
            }
        }
    }
}

A DataContractResolver is used to tell the serializer which types can be serialized. The implemented custom resolver always returns true and thus the surrogate accepts the NullSerializer object returned by the serializer surrogate.

public class NullSerializerProxyDataContractResolver : DataContractResolver
{
    public override Type ResolveName(string typeName, string typeNamespace, Type declaredType, DataContractResolver knownTypeResolver)
    {
        return knownTypeResolver.ResolveName(typeName, typeNamespace, declaredType, knownTypeResolver);
    }

    public override bool TryResolveType(Type dataContractType, Type declaredType, DataContractResolver knownTypeResolver, out XmlDictionaryString typeName, out XmlDictionaryString typeNamespace)
    {
        knownTypeResolver.TryResolveType(dataContractType, declaredType, knownTypeResolver, out typeName, out typeNamespace);
        return true; 
    }
}

The custom serializer surrogate is used to change the serialization for some objects. The surrogate checks for all objects which implement

IRelatedEnd and returns a NullSerializer object if the collection has not been loaded. The NullSerializer object implements a custom serialization and simply writes a null value into the output xml.

public class NullSerializer : IXmlSerializable
{
    public XmlSchema GetSchema()
    {
        return null;
    }

    public void ReadXml(XmlReader reader)
    {

    }

    public void WriteXml(XmlWriter writer)
    {
        writer.WriteAttributeString("nil", "http://www.w3.org/2001/XMLSchema-instance", "true");
    }
}

public class NullEntityCollectionDataContractSurrogate : IDataContractSurrogate
{
    public XmlObjectSerializer Serializer { get; set; }

    public Type GetDataContractType(Type type)
    {
        if (type.GetInterface("IRelatedEnd") != null)
            return typeof(NullSerializer);
        return type;
    }

    public object GetObjectToSerialize(object obj, Type targetType)
    {
        if (obj is IRelatedEnd && !((IRelatedEnd)obj).IsLoaded)
            return new NullSerializer();
        return obj;
    }

    public object GetDeserializedObject(object obj, Type targetType)
    {
        return obj;
    }

    public Type GetReferencedTypeOnImport(string typeName, string typeNamespace, object customData)
    {
        return null;
    }

    public CodeTypeDeclaration ProcessImportedType(CodeTypeDeclaration typeDeclaration, CodeCompileUnit compileUnit)
    {
        return typeDeclaration;
    }

    public object GetCustomDataToExport(Type clrType, Type dataContractType)
    {
        return null;
    }

    public object GetCustomDataToExport(MemberInfo memberInfo, Type dataContractType)
    {
        return null;
    }

    public void GetKnownCustomDataTypes(Collection<Type> customDataTypes) { }
}

The complete source code can be found here.



Discussion