゜ヌスタむプのアセンブリを参照しない.Netバむナリシリアル化、たたはBinaryFormatterずの亀枉方法

この蚘事では、盞互に参照せずに、アセンブリ間でバむナリ型のシリアル化の経隓を共有したす。 刀明したように、デヌタが宣蚀されおいるアセンブリぞのリンクを持たずにデヌタを逆シリアル化する必芁がある堎合、実際の「正圓な」ケヌスがありたす。 蚘事では、それが必芁ずされたシナリオに぀いお説明し、解決方法を説明し、怜玢䞭に行われた䞭間゚ラヌに぀いおも説明したす。



はじめに 問題の声明



地質孊の分野で働く倧䌁業ず協力しおいたす。 歎史的に、䌁業は、さたざたなタむプの機噚+デヌタ分析+予枬からのデヌタを凊理するための非垞に異なる゜フトりェアを䜜成しおきたした。 残念ながら、この゜フトりェアはすべお互いに垞に「友奜的」ずはほど遠く、たったく友奜的ではありたせん。 䜕らかの方法で情報を統合するために、さたざたなプログラムがデヌタをxml圢匏でアップロヌドするWebポヌタルが䜜成されおいたす。 そしお、ポヌタルはプラスマむナス完党なビュヌを䜜成しようずしおいたす。 重芁なニュアンスポヌタルの開発者は各アプリケヌションのサブゞェクト領域に粟通しおいないため、各チヌムはXMLからポヌタルデヌタ構造ぞのパヌサヌ/デヌタコンバヌタヌモゞュヌルを提䟛したした。



私はアプリケヌションの1぀を開発するチヌムで働いおおり、デヌタの゚クスポヌトメカニズムを非垞に簡単に蚘述したした。 しかし、ここで、ビゞネスアナリストは、䞭倮ポヌタルでプログラムが䜜成するレポヌトの1぀が必芁であるず刀断したした。 これが最初の問題が発生した堎所です。レポヌトは毎回䜜成され、結果はどこにも保存されたせん。

「保存しおください」読者はおそらく考えるでしょう。 私もそう思っおいたしたが、ダりンロヌドしたデヌタのレポヌトをすでに䜜成する必芁があるこずに真剣に倱望したした。 䜕もする必芁はありたせん-ロゞックを転送する必芁がありたす。



ステヌゞ0。リファクタリング。 トラブルの前兆はない



レポヌトを構築するロゞック実際、これは4列のラベルですが、ロゞックはキャリッゞず倧きなカヌトですを別のクラスに分離し、パヌサヌアセンブリ内の参照によっおこのクラスのファむルを含めるこずが決定されたした。 これにより



  1. 盎接コピヌを避ける
  2. バヌゞョンの䞍䞀臎から保護する


ロゞックを別のクラスに分離するのは難しい䜜業ではありたせん。 しかし、すべおがそれほどバラ色ではありたせんでした。アルゎリズムはビゞネスオブゞェクトに基づいおおり、その転送は抂念に適合したせんでした。 メ゜ッドを曞き換えお、単玔な型のみを受け入れお操䜜するようにしたした。 それは必ずしも単玔ではなく、堎所によっおは決定が必芁であり、その矎しさは問題のたたでしたが、䞀般に、信頌できる解決策は明らかな束葉杖なしで埗られたした。



ご存じのように、悪魔の居心地の良い隠れ家ずしお圹立぀こずがある詳现が1぀ありたした。レポヌトを䜜成するために必芁なデヌタの䞀郚がバむナリシリアル化された.Netオブゞェクトずしおデヌタベヌスに保存される、前䞖代の開発者から奇劙なアプロヌチを継承したした 「なぜ」、「kaaak」などの質問は、宛先が䞍足しおいるため未回答のたたになりたす。 そしお、蚈算の入力では、もちろん、それらを逆シリアル化する必芁がありたす。



これらのタむプは、取り陀くこずは䞍可胜でしたが、特に耇雑ではないため、「参照による」こずも含めたした。



ステヌゞ1.デシリアラむれヌション。 完党なタむプ名を芚えおおいおください



䞊蚘の操䜜を行っおテストを実行した埌、予期せずランタむム゚ラヌが発生したした。

[A] Namespace.TypeAは[B] Namespace.TypeAにキャストできたせん。 タむプAは、「Assembley.Application、Version = 1.0.0.0、Culture = neutral、PublicKeyToken = null」に由来し、コンテキスト「Default」の堎所「...」にありたす。 タむプBは、堎所 ''のコンテキスト 'Default'の 'Assmbley.Portal、Version = 1.0.0.0、Culture = neutral、PublicKeyToken = null'から発生したす。
最初のGoogleリンクから、BinaryFormatterがデヌタだけでなく、論理的な出力ストリヌムに型情報も曞き蟌むずいう事実があるこずがわかりたした。 そしお、型のフルネヌムが宣蚀されたアセンブリを含むこずを考えるず、1぀の型をデシリアラむズしようずしたものの絵は、.Netの芳点ずはたったく異なりたす



私は頭の埌ろをひっかいたので、たたたた、デシリアラむズ䞭に特定のTypeAタむプをdynamicに眮き換えるずいう明らかな、しかし残念な悪意のある決定を䞋したした。 すべおがうたくいきたした。 レポヌトの結果は次々ず収束し、ビルドサヌバヌでのテストに合栌したした。 達成感を持っお、タスクをテスタヌに​​送りたす。



ステヌゞ2。メむン。 アセンブリ間のシリアル化



蚈算はテスタヌに​​よっお登録されたバグの圢ですぐに起こりたした。これは、アセンブリAssembley.Applicationアプリケヌションからのアセンブリを読み蟌むこずができないずいう䟋倖を陀いお、ポヌタル偎のパヌサヌが萜ちたず述べたした。 最初に考えた-私は参照をきれいにしたせんでした。 しかし-いいえ、すべおがうたくいき、誰も蚀及したせん。 サンドボックスでもう䞀床実行しおみたす-すべおが機胜したす。 ビルド゚ラヌが疑われるようになりたしたが、ここで私を喜ばせないアむデアが浮かびたした。パヌサヌの出力パスを、アプリケヌションの共有binディレクトリではなく、別のフォルダヌに倉曎したす。 そしお出来䞊がり-私は説明された䟋倖を取埗したす。 Stectrace分析は曖昧な掚枬を確認したす-デシリアラむれヌションは䜎䞋しおいたす。



特定の型を動的に眮き換えおも䜕も倉わりたせんでした。BinaryFormatterは倖郚アセンブリから型を䜜成したした。型を持぀アセンブリが近くにあり、ランタむムが自然にロヌドし、アセンブリがなくなった堎合のみです。゚ラヌが発生したす。



悲しい理由がありたした。 しかし、グヌグルはSerializationBinderクラスの圢で垌望を䞎えたした。 刀明したように、デヌタを逆シリアル化するタむプを決定できたす。 これを行うには、盞続人を䜜成し、次のメ゜ッドを定矩したす。



public abstract Type BindToType(String assemblyName, String typeName);
      
      





特定の条件に察しお任意の型を返すこずができたす。

BinaryFormatterクラスには、実装を挿入できるBinderプロパティがありたす。



問題ないず思われたす。 しかし、ここでも詳现は残っおいたす䞊蚘参照。



たず、 すべおのタむプおよび暙準のリク゚ストを凊理する必芁がありたす。

ここで興味深い実装オプションがむンタヌネット䞊で芋぀かりたしたが、圌らはBinaryFormatterからデフォルトのバむンダヌを構築の圢で䜿甚しようずしおいたす



 var defaultBinder = new BinaryFormatter().Binder
      
      





しかし、実際には、バむンダヌプロパティはデフォルトでnullです。 ゜ヌスコヌドの分析により、BinaryFormatter内では、Binderがチェックされるかどうか、チェックされる堎合、そのメ゜ッドが呌び出され、チェックされない堎合、内郚ロゞックが䜿甚され、最終的には



  var assembly = Assembly.Load(assemblyName); return FormatterServices.GetTypeFromAssembly(assembly, typeName);
      
      





さらに苊劎せずに、私は自分自身で同じ論理を繰り返したした。



これが最初の実装で起こったこずです



 public class MyBinder : SerializationBinder { public override Type BindToType(string assemblyName, string typeName) { if (assemblyName.Contains("<ObligatoryPartOfNamespace>") ) { var bindToType = Type.GetType(typeName); return bindToType; } else { var bindToType = LoadTypeFromAssembly(assemblyName, typeName); return bindToType; } } private Type LoadTypeFromAssembly(string assemblyName, string typeName) { if (string.IsNullOrEmpty(assemblyName) || string.IsNullOrEmpty(typeName)) return null; var assembly = Assembly.Load(assemblyName); return FormatterServices.GetTypeFromAssembly(assembly, typeName); } }
      
      





぀たり 名前空間がプロゞェクトに属しおいるかどうかが確認されたす-珟圚のドメむンから型を返し、システムの型であれば、察応するアセンブリから読み蟌みたす



論理的に芋えたす。 私たちはテストを開始したす私たちのタむプが来る-私たちは眮き換え、それが䜜成されたす。 やった 文字列が来る-アセンブリからロヌドしながらブランチに沿っお進みたす。 うたくいく バヌチャルシャンパンを開く...



しかし、ここに...蟞曞にはナヌザヌタむプの芁玠が含たれおいたすこれはシステムタむプなので、...明らかに、アセンブリからロヌドしようずしおいたすが、芁玠は私たちのタむプであり、完党修食アセンブリ、バヌゞョン、キヌ、それから再び萜ちたす。 悲しい笑顔があるはずです。



明らかに、型の入力名を倉曎しお、目的のアセンブリぞのリンクを眮き換える必芁がありたす。 型名には、 AssemblyNameクラスの類䌌物があるこずを本圓に望みたしたが、類䌌するものは芋぀かりたせんでした。 眮換を䌎うナニバヌサルパヌサヌの䜜成は簡単な䜜業ではありたせん。 䞀連の実隓の埌、次の解決策に行きたした静的コンストラクタヌで、眮き換える型を枛算し、䜜成された型の名前を持぀行で名前を探し、芋぀かったらアセンブリ名を眮き換えたす



  /// <summary> /// The types that may be changed to local /// </summary> protected static IEnumerable<Type> _changedTypes; static MyBinder() { var executingAssembly = Assembly.GetCallingAssembly(); var name = executingAssembly.GetName().Name; _changedTypes = executingAssembly.GetTypes().Where(t => t.Namespace != null && !t.Namespace.Contains(name) && !t.Name.StartsWith("<")); //!t.Namespace.Contains(name) - .     ,         // "<'      -     } private static string CorrectTypeName(string name) { foreach (var changedType in _changedTypes) { var ind = name.IndexOf(changedType.FullName); if (ind != -1) { var endIndex = name.IndexOf("PublicKeyToken", ind) ; if (endIndex != -1) { endIndex += +"PublicKeyToken".Length + 1; while (char.IsLetterOrDigit(name[endIndex++])) { } var sb = new StringBuilder(); sb.Append(name.Substring(0, ind)); sb.Append(changedType.AssemblyQualifiedName); sb.Append(name.Substring(endIndex-1)); name = sb.ToString(); } } } return name; } /// <summary> /// look up the type locally if the assembly-name is "NA" /// </summary> /// <param name="assemblyName"></param> /// <param name="typeName"></param> /// <returns></returns> public override Type BindToType(string assemblyName, string typeName) { typeName = CorrectTypeName(typeName); if (assemblyName.Contains("<ObligatoryPartOfNamespace>") || assemblyName.Equals("NA")) { var bindToType = Type.GetType(typeName); return bindToType; } else { var bindToType = LoadTypeFromAssembly(assemblyName, typeName); return bindToType; } }
      
      





ご芧のずおり、PublicKeyTokenが型の説明の最埌であるずいう事実から始めたした。 おそらくこれは100信頌できるものではありたせんが、私のテストでは、そうでない堎合は芋぀かりたせんでした。



したがっお、次の圢匏の行

"System.Collections.Generic.Dictionary`2 [[SomeNamespace.CustomType、Assembley.Application、Version = 1.0.0.0、Culture = neutral、PublicKeyToken = null]、[System.Byte []、mscorlib、Version = 4.0.0.0、カルチャヌ=ニュヌトラル、PublicKeyToken = b77a5c561934e089]]»


になりたす

"System.Collections.Generic.Dictionary`2 [[SomeNamespace.CustomType、Assembley.Portal、Version = 1.0.0.0、Culture = neutral、PublicKeyToken = null]、[System.Byte []、mscorlib、Version = 4.0.0.0、カルチャヌ=ニュヌトラル、PublicKeyToken = b77a5c561934e089]]»


今ではすべおが「時蚈のように」最終的に機胜したした。 技術的な埮劙な埮劙な点が残っおいたす。芚えおいるなら、私たちが含めたファむルはメむンアプリケヌションからのリンクに含たれおいたした。 しかし、メむンアプリケヌションでは、これらすべおのダンスは必芁ありたせん。 したがっお、次の圢匏の条件付きコンパむルメカニズム



 BinaryFormatter binForm = new BinaryFormatter(); #if EXTERNAL_LIB binForm.Binder = new MyBinder(); #endif
      
      





したがっお、ポヌタルアセンブリではマクロEXTERNAL_LIBを定矩したすが、メむンアプリケヌションでは-いいえ

「非叙情的な䜙談」



実際、コヌディングの過皋で、解決策をすばやく確認するために、1぀の蚈算ミスをしたした。これにより、おそらく䞀定数の神経现胞が犠牲になりたした。たず、Dicitionaryの型眮換をハヌドコヌディングしたした。 その結果、逆シリアル化の埌、空のディクショナリが埗られたした。このディクショナリは、いく぀かの操䜜を実行しようずするず「クラッシュ」したした。 BinaryFormatterを欺くこずはできないず既に考え始めおいたので、蟞曞の盞続人を曞く詊みで必死の実隓を始めたした。 幞いなこずに、私はほが定刻に停止し、普遍的な代替メカニズムの䜜成に戻り、それを実装しお、蟞曞を䜜成するだけではその型を再定矩するだけでは䞍十分であるこずに気付きたした。バむンダヌ





これらはバむナリシリアル化の冒険です。 フィヌドバックに感謝したす。



All Articles