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

2 thoughts on “WCF DataContractSerializer problems with dynamic proxies in Entity framework 4

  1. Will have a look at this. Thanks! Been googling for two days and when I’d finally given up and were searching for a different solution I came across this. Hope it can help me out.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s