4

特定の場所にドロップされ、アプリによってピックアップされる「プラグイン」によって実装できるいくつかの定義済みインターフェイスを提供することにより、既存のアプリに拡張性を追加することを検討しています。プラグインがより頻繁に更新および展開される一方で、アプリケーションのコアはめったに更新されません。

したがって、基本的には、次のようなセットアップがあります。

// in core assembly (core.dll)
public interface IReportProvider{
     string GenerateReport();
}

// in other assembly(plugin.dll)
public class AwesomeReport : IReportProvider {
     string GenerateReport(){ ... }
}

両方のプロジェクトはすべて同じビルド プロセスの一部であり、コア アプリは単独でデプロイされ、プラグインは後の段階でドロップされます。

私の問題は、アセンブリのバージョン管理と時間の経過に伴う解決です。core.dll v1 がデプロイされていて、プラグインをドロップしたいとしましょう。これは、plugin.dll が core.dll v1 を参照している場合にうまく機能します。ただし、plugin.dll が新しいバージョンの core.dll に対してコンパイルされている場合 (ビルドの一部として v2 など)、プラグインは core.dll v2 を参照しているため、ロードに失敗しますが、デプロイされたバージョンには core.dll しかありません。 v1.

これは合理的で予想される動作ですが、このプロジェクトのセットアップ方法にいくつかの問題点があります。つまり、プラグインの開発/更新は、ビルドを再度実行して新しいプラグインをドロップするだけでは実行できません (現在は新しいバージョンの依存関係)。

(新しいアセンブリを古いアセンブリに解決する際の潜在的な問題と、型定義の潜在的な不一致を認識しています。問題は、型定義の不一致に関連する問題の修正ではなく、高レベルのアセンブリ解決の問題の修正のみです。)

物事を機能させるためのオプションがいくつかありますが、どれも私が本当に望んでいるほど単純ではありません。

  1. binding-redirects を web.config に追加し、すべての core.dll v1+ 参照を core.dll v1 参照として解決するように指示します。
  2. インターフェイス定義を含む「contracts.dll」アセンブリを作成し、この特定のアセンブリのバージョン番号をビルド全体で変更しないようにします
  3. デプロイされたバージョンの core.dll に対してプラグインをビルドする (何らかの形でデプロイされたバージョンを開発中に参照する)

前述のように、これらのどれも私にとって本当に簡単に達成できるものではありません。

4

3 に答える 3

3

私はこの分野で非常に広範囲に取り組んできましたが、範囲内と範囲外の境界線を正確に設定する必要があることを一貫して発見しました。最大限の拡張性を求めるのはリスクですが、それには代償が伴います。そのコストは、規則、ベスト プラクティス、およびおそらく自動化されたビルド チェックの形でのコンパイル時間か、複雑なランタイムになります。

リストしたオプションに対処するには:

  1. バインディング リダイレクトは、ソリューションを実現するための部分的なツールにすぎません。これにより、プログラムで DLL のあるバージョンを別のバージョンに置き換えることができますが、メソッドが変更されたときに何が起こるかという問題を魔法のように解決することはできません。MissingMethodException? ここであなたが持っていないかもしれないものは、依存関係の連鎖です。 依存チェーン アプリケーションが依存関係 'A' をバージョン 1 オブジェクトとして処理している間、内部的にアプリケーションに戻されて v1.0 にキャストされる後のバージョンから何かを作成していることがわかります - 例外が発生します。これは扱いが難しい場合があり、リスクの 1 つにすぎません。

  2. コントラクト アセンブリのバージョンをビルド間で同じに保つ これはエレガントに機能しますが、複雑さをビルド時から実行時まで延期するだけです。変更によってバージョン間の互換性が損なわれないように注意する必要があります。言うまでもなく、アプリケーションが古くなるにつれて、これらのコントラクトに廃止したい宣言がたくさん集まります。最終的に、このファイルは開発者にとって大きく、扱いにくく、混乱を招くものになります。それは、あなたが持っているすべてのエンティティ コントラクトを考慮に入れることさえできません!

  3. これが何を意味するのか、そしてそれがあなたの問題空間をどのようにカバーするのか、私にはよくわかりません。

別の方法として、「SDK」のメジャー リリースごとに新しい契約を作成するという方法があります。これには、主要な機能を一定期間修正するという点で政治的な利点があります。つまり、機能要求を合理的な期待レベルに保つことができ、それを超えるもの (新しい世代の契約が必要) は次のメジャー リリースまで延期されます。 '。ただし、これには機能の設計に注意が必要です。最も明白な要件を先取りできるように、事前に考慮して契約を作成してください。しかし、これはとにかく言うまでもありません...「契約」と呼ばれます理由があります。新しいコントラクトはそれぞれ、バージョンの名前空間 (Company.Product.Contracts.v1_0、Company.Product.Contracts.v1_1) に存在します。

私は自分のコントラクトを連鎖させません (最後のコントラクトを継承するコントラクトの新しいバージョンごと)。そうすることで、バージョン番号を同じに保つという問題に戻ります。チェーンを完全に壊さない限り、機能を完全に取り除くことはできません。

プラグインが読み込まれると、サポートする機能のレベル (コントラクト バージョン) をホストに問い合わせることができます。また、古いホスト/新しいプラグインのシナリオの場合: プラグインをプログラムしてランタイム機能を減らし、より少ないホスト機能に対処します。または単に読み込みを拒否します。プラグインがホストにない機能を利用できるようにする魔法はないため、おそらくこれらのチェックを実行する必要があります。Microsoft の MAF フレームワークは、shim インフラストラクチャを使用してこれを達成しようとしますが、ほとんどの人にとって非常に複雑になります。

そのため、次の点を考慮する必要があります。

  1. 拡張性の要件を絞り込みます。達成したいことはすべて、継続的なメンテナンスに費用がかかります。
  2. 機能を廃止する方法を検討する
  3. コントラクトにはロジック コントラクトだけでなくエンティティも含まれることを忘れないでください。これらはロジック コントラクトとは少し異なる考慮事項があります。
  4. 各互換性チェックが実行時ではなくコンパイル時に実行される方がよいかどうか (またはその逆) を慎重に検討してください。
  5. バージョンナンバリング!アセンブリのバージョン番号は実行時の動作に最適です。ファイルのバージョン番号は、バージョン管理で DLL をソースまで追跡するのに役立ちます。両方を利用してください。
  6. カスタム DLL 解決を行っている場合は、app.config を使用してカスタム DLL の場所を定義します。アセンブリによってイベントが解決されるわけではありません。構成アプローチははるかに予測可能であり、読みやすい Xml で宣言されています。また、Fusion Log Viewer はプローブ チェーンのどこに DLL が挿入されたかを適切に報告しますが、assembly-resolve イベントはコード内のすべてのロジックとルールを非表示にします。唯一の本当の欠点は、app.config を使用すると、アプリケーションが構成ファイルを再読み込みする (通常はアプリを再起動する) まで変更が有効にならないことですが、プラグインの AppDomain 分離を行っている場合は、これを回避することもできます。

あなたの「core.dll」の問題に関係しています...あなたにとって、これは比較的単純な問題だと思います。コア コントラクト DLL 内のすべては、バージョンの名前空間 (上記の Company.Product.v1_0 などを参照) の下に存在する必要があるため、DLL にバージョン番号も含まれていることは実際には理にかなっています。これにより、bin フォルダーにデプロイされたときに DLL が相互に上書きされるという問題が解消されます。GAC に頼らないでください - これは長期的には苦痛です。残念なことに、開発者は常に GAC がすべてをオーバーライドすることを忘れているようであり、これはデバッグの悪夢になる可能性があります。これは、アクセス許可に関する展開シナリオにも影響を与える可能性があります。

DLL 名を同じに保つ必要がある場合は、アプリケーション内に「ローカル gac」を作成できます。これにより、相互に上書きしないように DLL を保存できますが、すべては次の方法で解決できます。ランタイム。app.config の「バインディング リダイレクト」を確認してください (ここで私の回答を参照してください))。これは、アプリケーションの bin フォルダーの下にある「疑似 GAC フォルダー構造」と組み合わせることができます。アプリは、カスタム アセンブリ解決コード ロジックなしで、必要な DLL の任意のバージョンを見つけることができます。以前にサポートされていたすべてのバージョンの core.dll をアプリケーションと共にデプロイします。アプリケーションがバージョン 9 になり、バージョン 7 および 8 のプラグインもサポートすることにした場合、Core.7.dll、Core.8.dll、および Core.9.dll のみを含める必要があります。プラグインのロード ロジックで古いバージョンの Core への依存関係を検出し、プラグインに互換性がないことをユーザーに警告する必要があります。

このトピックにはたくさんのことがあります。他に何かあなたの原因に関連するものを思いついたら、もう一度確認します...

于 2012-09-03T01:16:10.510 に答える
0

3つのポイント(より信頼性が高く適切だと思います)を回避する理由が何であれ、AssemblyResolveEventを利用できます。

core.dllに次のようなコードを含めます

static Assembly AppDomain_AssemblyResolve(object sender, ResolveEventArgs args)
       {
            //replace the if check with more reliable check such as matching the public key as well
            if (args.Name.Contains(Assembly.GetExecutingAssembly().GetName().Name))
            {
                Console.WriteLine("Resolved " + args.Name + " as " + Assembly.GetExecutingAssembly().GetName());
                return Assembly.GetExecutingAssembly();
            }

            return null;
       }

プラグインをロードする前に、上記のハンドラーをバインドしてください。現在のAppdomainにプラグインをロードする場合はAssemblyResolve、現在のドメインのプラグインにバインドします。

例えば

[SecurityPermission(SecurityAction.Demand, ControlAppDomain = true)]
public static void LoadPlugins()
{
    AppDomain.CurrentDomain.AssemblyResolve += AppDomain_AssemblyResolve;
    Assembly pluginAssembly = AppDomain.CurrentDomain.Load("MyPlugin");
}
于 2012-08-29T13:46:27.037 に答える
0

Binding redirectsを使用できる場合があります。

于 2012-08-29T16:37:41.253 に答える