WCF DataContractSerializer problems with dynamic proxies in Entity framework 4

This article was produced for Beta 2 & CTP2

Download the code

I were having trouble with serialization of my entities over WCF using net.tcp binding when letting the ObjectContext create dynamic proxies. To solve the problem I hade to extend the DataContractSerializerOperationBehavior and provide it with the dynamic proxies as “Known types”.

I also had to create a custom attribute that I could mark my methods (service operations) so that my custom DataContractSerializerOperationBehavior is invoked “only when needed”.

It was as simple as that. I also created an class that listens for the AppDomain.AssemblyLoad event and keeps track of the dynamic proxy types.

So if you are having these problems, feel free to use the code.

You also have to decorate your entities with the Serializable- and DataContract attribute with IsReference=true. Also you have to mark the members that should be serialized in your entites with the DataMember attribute.

The Interface that defines my service
I only mark the methods that uses entities that has a dynamic proxy with my custom EfDataContractSerializer attribute.

[ServiceContract]
public interface ISecurityService : IService
{
    [OperationContract]
    [EfDataContractSerializer]
    ServiceResponse<UserAccount> SetupNewUserAccount(UserAccount userAccount);

    [OperationContract]
    [EfDataContractSerializer]
    ServiceResponse<UserAccount> GetUserAccountByCredentials(string username, string password);

    [OperationContract]
    [EfDataContractSerializer]
    ServiceResponse UpdateUserAccount(UserAccount userAccount);

    [OperationContract]
    ServiceResponse SetNewPassword(string username, string currentPassword, string newPassword);
}

EfDataContractSerializerAttribute – For decorationg methods in your service

[Serializable]
public class EfDataContractSerializerAttribute : Attribute, IOperationBehavior
{
    private readonly EfDataContractSerializerOperationBehavior _innerOperationBehavior;
    
    public EfDataContractSerializerAttribute()
    {
    }

    public EfDataContractSerializerAttribute(OperationDescription operation)
    {
        _innerOperationBehavior = new EfDataContractSerializerOperationBehavior(operation);
    }

    public EfDataContractSerializerAttribute(OperationDescription operation, DataContractFormatAttribute dataContractFormatAttribute)
    {
        _innerOperationBehavior = new EfDataContractSerializerOperationBehavior(operation, dataContractFormatAttribute);
    }

    public void Validate(OperationDescription operationDescription)
    {
        if (_innerOperationBehavior == null)
            return;

        (_innerOperationBehavior as IOperationBehavior).Validate(operationDescription);
    }

    public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
    {
        ReplaceDataContractSerializerOperationBehavior(operationDescription);
    }

    public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
    {
        ReplaceDataContractSerializerOperationBehavior(operationDescription);
    }

    public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters)
    {
        if (_innerOperationBehavior == null)
            return;

        (_innerOperationBehavior as IOperationBehavior).AddBindingParameters(operationDescription, bindingParameters);
    }

    private void ReplaceDataContractSerializerOperationBehavior(OperationDescription description)
    {
        var operationBehavior = description.Behaviors.Find<DataContractSerializerOperationBehavior>();

        if (operationBehavior == null)
            return;

        description.Behaviors.Remove(operationBehavior);
        description.Behaviors.Add(new EfDataContractSerializerOperationBehavior(description));
    }
}

EfDataContractSerializerOperationBehavior – For populating the DataContractSerializer with known types

public class EfDataContractSerializerOperationBehavior : DataContractSerializerOperationBehavior
{
    public EfDataContractSerializerOperationBehavior(OperationDescription operation)
        : base(operation)
    {
    }

    public EfDataContractSerializerOperationBehavior(OperationDescription operation, DataContractFormatAttribute dataContractFormatAttribute)
        : base(operation, dataContractFormatAttribute)
    {
    }

    public override XmlObjectSerializer CreateSerializer(Type type, string name, string ns, IList<Type> knownTypes)
    {
        return new DataContractSerializer(type, name, ns, MergeKnownTypesWithDynamicProxyTypes(knownTypes));
    }

    public override XmlObjectSerializer CreateSerializer(Type type, XmlDictionaryString name, XmlDictionaryString ns, IList<Type> knownTypes)
    {
        return new DataContractSerializer(type, name, ns, MergeKnownTypesWithDynamicProxyTypes(knownTypes));
    }

    private static IEnumerable<Type> MergeKnownTypesWithDynamicProxyTypes(IEnumerable<Type> knownTypes)
    {
        var tmp = new HashSet<Type>();

        if (knownTypes != null)
            tmp.UnionWith(knownTypes);

        tmp.UnionWith(EfDynamicProxyAssemblies.GetTypes());

        return tmp;
    }
}

EfDynamicProxyAssemblies – For keeping track of currently generated dynamic proxies

public static class EfDynamicProxyAssemblies
{
    private static readonly object _lock = new object();
    private static readonly Dictionary<string, Assembly> _loadedAssemblies = new Dictionary<string, Assembly>();
    private static readonly HashSet<Assembly> _assembliesToExtract = new HashSet<Assembly>();
    private static readonly List<Type> _dynamixProxies = new List<Type>();

    static EfDynamicProxyAssemblies()
    {
        AppDomain.CurrentDomain.AssemblyLoad += new AssemblyLoadEventHandler(CurrentDomain_AssemblyLoad);
    }

    private static void CurrentDomain_AssemblyLoad(object sender, AssemblyLoadEventArgs args)
    {
        if (args == null || args.LoadedAssembly == null || string.IsNullOrEmpty(args.LoadedAssembly.FullName))
            return;

        lock (_lock)
        {
            if ((args.LoadedAssembly.FullName).StartsWith("EntityFrameworkDynamicProxies") &&
                !_loadedAssemblies.ContainsKey(args.LoadedAssembly.FullName))
            {
                _loadedAssemblies.Add(args.LoadedAssembly.FullName, args.LoadedAssembly);
                _assembliesToExtract.Add(args.LoadedAssembly);
                //Types aren't generated yet, so we can't add them to _dynamixEntityProxies
            }
        }
    }

    public static IEnumerable<Type> GetTypes()
    {
        lock(_lock)
        {
            var types = _assembliesToExtract.SelectMany(a => a.GetTypes()).ToList();
            _dynamixProxies.AddRange(types);

            _assembliesToExtract.Clear();
        }

        return _dynamixProxies;
    }
}

Enjoy!

//Daniel