Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create a SerializationBinder for the Binary Formatter that handles the moving of types from one assembly and namespace to another

The context is as follows

  1. I want to refactor code by moving it around to different projects
  2. Some of this code comprises of serializable DTOs that are used to send and receive data across multiple endpoints
  3. If I move the code around, serialization breaks (therefore it is not backward compatible with older versions of my application)

A solution to this problem is the SerializationBinder which allows me to "redirect" in a sense from one type to another.

I therefore want to create a SerializationBinder to meet this need. However it must do so by meeting the following requirements

  1. The inputs to the SerializationBinder should be a list of old type to new type mappings. The mapping should include the old assembly name (no version, no public key token) and the old full name of the type (namespace and name) as well as the new assembly name and new full name of the type
  2. For the types that are in the inputs, version numbers of assemblies should be ignored
  3. It should handle generics if my types happen to be in generics (List, Dictionary, etc) without needing to include the generics in the inputs
  4. For anything that is not in the inputs (i.e. types that have not moved or .NET types like dataset for example) it should default to using the out of the box algorithm of the binary serializer

Is this possible or am I dreaming? Is there something out there that already does this? I would assume this is a common problem.

So far I see no easy way of doing 3 and no way at all of doing 4.

Here is an attempt

public class SmartDeserializationBinder : SerializationBinder
{
    /// <summary>
    /// Private class to handle storing type mappings
    /// </summary>
    private class TypeMapping
    {
        public string OldAssemblyName { get; set; }
        public string OldTypeName { get; set; }
        public string NewAssemblyName { get; set; }
        public string NewTypeName { get; set; }
    }

    List<TypeMapping> typeMappings;

    public SmartDeserializationBinder()
    {
        typeMappings = new List<TypeMapping>();
    }

    public void AddTypeMapping(string oldAssemblyName, string oldTypeName, string newAssemblyName, string newTypeName)
    {
        typeMappings.Add(new TypeMapping()
        {
            OldAssemblyName = oldAssemblyName,
            OldTypeName = oldTypeName,
            NewAssemblyName = newAssemblyName,
            NewTypeName = newTypeName
        });
    }

    public override Type BindToType(string assemblyName, string typeName)
    {
        //Need to handle the fact that assemblyName will come in with version while input type mapping may not
        //Need to handle the fact that generics come in as mscorlib assembly as opposed to the assembly where the type is defined.
        //Need to handle the fact that some types won't even be defined by mapping. In this case we should revert to normal Binding... how do you do that?

        string alternateAssembly = null;
        string alternateTypeName = null;
        bool needToMap = false;
        foreach (TypeMapping mapping in typeMappings)
        {
            if (typeName.Contains(mapping.OldTypeName))
            {
                alternateAssembly = mapping.NewAssemblyName;
                alternateTypeName = mapping.NewTypeName;
                needToMap = true;
                break;
            }
        }

        if (needToMap)
        {
            bool isList = false;
            if (typeName.Contains("List`1"))
                isList = true;
            // other generics need to go here

            if (isList)
                return Type.GetType(String.Format("System.Collections.Generic.List`1[[{0}, {1}]]", alternateTypeName, alternateAssembly));
            else
                return Type.GetType(String.Format("{0}, {1}", alternateTypeName, alternateAssembly));
        }
        else
            return null; // this seems to do the trick for binary serialization, but i'm not sure if it is supposed to work
    }
}
like image 986
Mark Avatar asked Oct 29 '13 18:10

Mark


1 Answers

This could work (instead of your override).

public override Type BindToType(string assemblyName, string typeName)
        {
            var m = Regex.Match(typeName, @"^(?<gen>[^\[]+)\[\[(?<type>[^\]]*)\](,\[(?<type>[^\]]*)\])*\]$");
            if (m.Success)
            { // generic type
                var gen = GetFlatTypeMapping(m.Groups["gen"].Value);
                var genArgs = m.Groups["type"]
                    .Captures
                    .Cast<Capture>()
                    .Select(c =>
                        {
                            var m2 = Regex.Match(c.Value, @"^(?<tname>.*)(?<aname>(,[^,]+){4})$");
                            return BindToType(m2.Groups["aname"].Value.Substring(1).Trim(), m2.Groups["tname"].Value.Trim());
                        })
                    .ToArray();
                return gen.MakeGenericType(genArgs);
            }
            return GetFlatTypeMapping(assemblyName,typeName);
        }

Then you just have to implement your way the function GetFlatTypeMapping (not worrying of about generic arguments).

What you will have to do is to return typeof(List<>) and typeof(Dictionary<,>) (or any other generic you would like to use) when asked.

nb: I said typeof(List<>) ! not typeof(List<something>) ... that's important.

disclaimer: because of the regex "(?[^]]*)", this snipped does not support nested generic types like List<List<string>> ... you will have to tweak it a bit to support it !

like image 111
Olivier Avatar answered Sep 22 '22 18:09

Olivier