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<>)and typeof(Dictionary<,>)(または使用したい他のジェネリック) を返すことです。

nb:私は言ったtypeof(List<>)!じゃないtypeof(List<something>)…それは重要です。

免責事項: 正規表現 "(?[^]]*)" のため、この抜粋はネストされたジェネリック型をサポートしていませんList<List<string>>... サポートするには、少し調整する必要があります!

于 2013-11-12T11:06:33.990 に答える
0

あなたの質問には答えないかもしれませんが、これはあなたの問題を解決するかもしれません:

オブジェクト構造と一緒に逆シリアル化に必要な 1 つを除くすべてのアセンブリをシリアル化することができました。秘訣は、リフレクションを使用して、それらが定義されている型とアセンブリのオブジェクト構造を検査することです。その後、シリアル化時にアセンブリをバイナリ データとしてオブジェクトに書き込み、それらを使用して残りを逆シリアル化できる AppDomain に読み込むことができます。オブジェクト構造の。

AppDomain 処理、ルート オブジェクト、およびいくつかの基本クラスを変更されないアセンブリに配置する必要があり、他のすべてはこの「アンカー」に応じてアセンブリで定義されます。

利点:

  • アンカー アセンブリが変更されない限り、シリアライゼーションは壊れません。
  • 難読化に使用できます。たとえば、必要なアセンブリの一部を任意のファイルに隠すことができます
  • オンライン更新に使用できます。たとえば、アセンブリを任意のデータとともに出荷します。
  • 私に関する限り、ジェネリックに問題はありません

欠点:

  • ブートストラップがコードを挿入するために使用される可能性があるため、セキュリティの問題 (アセンブリ SecurityEvidences のためのいくつかの余分な作業)
  • AppDomain の境界は越えなければならない作業です
  • シリアル化されたデータを爆破します(OK、ファイルの場合-悪い、通信の場合)

ただし、クライアントのバージョンが異なる場合を除き、通信は中断されず、ハンドシェイクでブートストラップを実行できます。

申し訳ありませんが、コードが複雑すぎて具体的すぎるため、コードを提供できません。しかし、私の他の回答でいくつかの洞察を共有します。

.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 に答える