9

上下文如下

  1. 我想通过将代码移动到不同的项目来重构代码
  2. 其中一些代码包含用于跨多个端点发送和接收数据的可序列化 DTO
  3. 如果我移动代码,序列化会中断(因此它与我的应用程序的旧版本不向后兼容)

这个问题的一个解决方案是 SerializationBinder,它允许我在某种意义上从一种类型“重定向”到另一种类型。

因此,我想创建一个 SerializationBinder 来满足这一需求。但是,它必须满足以下要求

  1. SerializationBinder 的输入应该是旧类型到新类型映射的列表。映射应包括旧程序集名称(无版本,无公钥令牌)和类型的旧全名(命名空间和名称)以及新程序集名称和类型的新全名
  2. 对于输入中的类型,应忽略程序集的版本号
  3. 如果我的类型恰好在泛型(列表、字典等)中,它应该处理泛型,而无需在输入中包含泛型
  4. 对于不在输入中的任何内容(即未移动的类型或 .NET 类型,例如数据集),它应该默认使用二进制序列化程序的开箱即用算法

这是可能的还是我在做梦?有什么东西已经这样做了吗?我认为这是一个常见问题。

到目前为止,我没有看到做 3 的简单方法,也根本没有做 4 的方法。

这是一个尝试

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
    }
}
4

3 回答 3

6

这可以工作(而不是你的覆盖)。

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);
        }

然后你只需要以你的方式实现函数 GetFlatTypeMapping (不用担心泛型参数)。

您必须做的是在被问到时返回typeof(List<>)typeof(Dictionary<,>)(或您想要使用的任何其他通用)。

注意:我说typeof(List<>)!不typeof(List<something>)……这很重要。

免责声明: 由于正则表达式“(?[^]] *)”,这个片段不支持嵌套的泛型类型List<List<string>>......你必须稍微调整一下才能支持它!

于 2013-11-12T11:06:33.990 回答
0

虽然它可能无法回答您的问题,但这可能会解决您的问题:

我能够将反序列化所需的所有程序集与对象结构一起序列化。诀窍是使用反射来检查对象结构的类型和定义它们的程序集。然后,您可以将程序集作为二进制数据写入序列化对象中,并将它们加载到 AppDomain 中,您可以使用它们来反序列化其余部分的对象结构。

您必须将 AppDomain 处理、根对象和一些基类放入一个不会更改的程序集中,并且根据此“锚点”在程序集中定义其他所有内容。

优点:

  • 只要锚组件不变,序列化就不会中断
  • 可用于混淆,例如一些需要的程序集可以隐藏在任何文件中
  • 可用于在线更新,例如随任何数据一起运送组件
  • 就我而言,泛型没有问题

缺点:

  • 作为引导程序的安全问题可用于注入代码(组装 SecurityEvidences 的一些额外工作)
  • AppDomain 边界是一些需要跨越的工作
  • 炸毁你的序列化数据(好的,用于文件 - 坏的,用于通信)

但是,除非您有不同的客户端版本,否则通信不会中断,然后您可以在握手时进行引导。

抱歉,我无法提供代码,因为它太复杂且太具体。但我在我的其他答案中分享了一些见解:

.net 中 DataContract 属性和 Serializable 属性之间的区别

DisallowApplicationBaseProbing = true 时需要连接 AssemblyResolve 事件

于 2013-11-07T17:03:52.757 回答
-2

必须使用 BinaryFormatter 进行反序列化是硬性要求吗?

如果使用 BinaryFormatter 进行序列化不是硬性要求,请考虑替代序列化方法,例如 JSON.Net 或 ProtoBuf.Net。其中任何一个都将创建与平台和版本无关的数据序列化。

或者,您可以自己执行二进制序列化(它比 BinaryFormatter 更快且更小,但通常代码密集度更高,因为您必须确保编写序列化器和反序列化器基本上彼此相同。

如果您必须使用 BinaryFormatter,请务必使用 FormatterAssemblyStyle.Simple 构建它,以解决版本控制问题。这告诉它不要对程序集版本进行迂腐检查。

于 2013-11-05T18:52:31.443 回答