web-dev-qa-db-ja.com

クラスライブラリからの参照は、実行中のプロジェクトbinフォルダーにコピーされません

ロジックレイヤーを表すクラスライブラリがあります。そのライブラリに、Google.Apis.Analytics.v3用のnugetパッケージを追加しました。パッケージとそのすべての依存関係がインストールされました。

そのロジッククラスライブラリ(通常の参照)を使用するコンソールアプリケーションがあります。すべてがうまく書かれ、コンパイルされています。

問題は、実行時にGoogle.Apis.dllが見つからないという例外をスローしたことです。このDLLは、nugetでダウンロードされた依存関係です。

BINフォルダーを確認すると、クラスライブラリのbinフォルダーにはこのDLLが存在していましたが、コンソールアプリケーションのBINフォルダーには存在していませんでした(他の関連するDLLは存在していました)。これは、コンパイル中にすべての参照がコピーされたわけではないことを意味します。

オンラインで検索したところ、実際には機能しないあらゆる種類の回避策が見つかりました(プロジェクトファイルを手動で編集したり、そのdll定義の実際のxml行を削除したりするなど)。

私がやったことは、コンソールアプリケーションに同じnugetライブラリを追加することです-それは機能しますが、少し汚れているように感じ、本来あるべき方法ではありません。コンソールアプリは、そのロジッククラスライブラリからサービスを取得することになっているクライアントであり、「クライアント」が心配することなく、そのサービスを認識している必要があると思います。

また、そのサービスを使用するのはそのコンソールアプリだけではありません。また、その機能を使用するWebアプリも計画しています。そのため、同じnugetをそのWebアプリにも追加する必要があります。少し厄介です...

私だけでしょうか?それはそれについて行く正しい方法ですか?私はその機能を処理するためにWCFプロジェクトを作成することを考えていましたが、それは機能だけでは少しオーバーヘッドのようであり、私の意見では物事を「よりクリーン」に保つためにワークフローが遅くなる可能性があります。

私はそれを考えすぎているのでしょうか?

感謝

30
developer82

説明

サンプルシナリオでは、プロジェクトX、アセンブリA、およびアセンブリBがあるとします。アセンブリAはアセンブリBを参照するため、プロジェクトXにはAとBの両方への参照が含まれます。また、プロジェクトXにはアセンブリA(Aなど)を参照するコードが含まれます。 SomeFunction())。ここで、プロジェクトXを参照する新しいプロジェクトYを作成します。

したがって、依存関係チェーンは次のようになります。Y => X => A => B

Visual Studio/MSBuildはスマートになり、プロジェクトXで必要であると検出された参照のみをプロジェクトYに持ち込みます。これは、プロジェクトYでの参照汚染を回避するために行われます。問題は、プロジェクトXにはアセンブリBを明示的に使用するコード(B.SomeFunction()など)が実際には含まれていないため、VS/MSBuildはBが必要であることを検出しないことです。 Xによって、したがってプロジェクトYのbinディレクトリにコピーされません。 XアセンブリとAアセンブリのみをコピーします。

解決

この問題を解決するには2つのオプションがあり、どちらの場合もアセンブリBがプロジェクトYのbinディレクトリにコピーされます。

  1. プロジェクトYのアセンブリBへの参照を追加します。
  2. アセンブリBを使用するプロジェクトXのファイルにダミーコードを追加します。

個人的には、いくつかの理由でオプション2を好みます。

  1. 将来、プロジェクトXを参照する別のプロジェクトを追加する場合、アセンブリBへの参照も含めることを覚えておく必要はありません(オプション1の場合と同様)。
  2. ダミーコードが必要であり、削除しない理由を明示的にコメントすることができます。したがって、誰かが誤ってコードを削除した場合(たとえば、未使用のコードを探すリファクタリングツールを使用して)、ソース管理からコードが必要であることが簡単にわかり、コードを復元できます。オプション1を使用し、誰かがリファクタリングツールを使用して未使用の参照をクリーンアップする場合、コメントはありません。参照が.csprojファイルから削除されたことがわかります。

これは、この状況に遭遇したときに通常追加する「ダミーコード」のサンプルです。

    // DO NOT DELETE THIS CODE UNLESS WE NO LONGER REQUIRE Assembly A!!!
    private void DummyFunctionToMakeSureReferencesGetCopiedProperly_DO_NOT_DELETE_THIS_CODE()
    {
        // Assembly A is used by this file, and that Assembly depends on Assembly B,
        // but this project does not have any code that explicitly references Assembly B. Therefore, when another project references
        // this project, this project's Assembly and the Assembly A get copied to the project's bin directory, but not
        // Assembly B. So in order to get the required Assembly B copied over, we add some dummy code here (that never
        // gets called) that references Assembly B; this will flag VS/MSBuild to copy the required Assembly B over as well.
        var dummyType = typeof(B.SomeClass);
        Console.WriteLine(dummyType.FullName);
    }
31
deadlydog

次の依存関係チェーンがある場合:Lib1 <-Lib2 <-MyApp

TLDRバージョン:仮定を行わないことにより、ビルドシステムは不確実性/予期しない動作の導入を回避します。

MyAppをビルドすると、Lib2はMyAppのbinディレクトリにコピーされますが、Lib1はコピーされません。 MyAppのbinディレクトリにあるLib1のdllを取得するには、MyAppにLib2 and Lib1への参照を追加する必要があります(そうしないと、ランタイムエラーが発生します)。 MyAppにコピーするのに安全で適切なLib2のbinディレクトリにあるファイルの正確なセットを特定することは不可能です(またはおそらく本当に難しいでしょう)。ビルドシステムがLib2のbinディレクトリ内のすべてがMyAppにとって安全であると想定した場合、または暗黙的にLib1を参照した場合、MyAppの動作が意図せずに変更される可能性があります。

複数のプロジェクトがLib2に依存しているが、それらのプロジェクトの1つがAssembly.LoadFrom/Activater.CreateInstance/MEF/etcを使用して隣接する.dllファイルをロードしたいソリューションを想像してみてください。 (プラグイン)そして他のものはしません。自動コピー操作では、プラグインdllとともにLib2を取得し、それを1番目と2番目のプロジェクトのビルドディレクトリにコピーできます(ビルド操作の結果としてLib2のbinディレクトリにあるため)。これにより、2番目のアプリの動作が変わります。

あるいは、Lib2を参照したときにLib1を少し賢く暗黙的に参照した場合(そしてbinディレクトリの内容をコピーしただけではない場合)、意図しない結果が生じる可能性があります。 MyAppがすでにLib1に依存しているが、Lib2が必要とするものと互換性のあるGAC'd/ngen'dコピーを使用していた場合はどうなりますか。 Lib2への参照を追加すると、暗黙的にLib1への参照が作成された場合、ロードされたLib1が変更され、アプリケーションの実行時の動作が変更される可能性があります。 MyAppのbinディレクトリにすでにLib1があることを検出してスキップすることもできますが、その場合は、すでに存在するLib1が正しいものであると想定します。おそらく、クリーン操作によって消去されるのを待っている古い.dllであり、上書きは正しい動きでした。

NuGetは、パッケージの依存関係で説明している問題に対処します。 Lib1とLib2の両方にnugetパッケージがあり、Lib2パッケージがLib1パッケージに依存している場合、MyAppにLib2を追加すると、Lib1も追加されます。両方のpacakgesのdllは、最終的にMyAppのbinディレクトリに配置されます。

最善の方法は、考えを少し逆にすることです。考える代わりに:

  • Lib2をコンパイルするには、Lib1への参照が必要なので、Lib1への参照を追加します。

考える:

  • MyAppにはLib2が必要です。 Lib2が必要とするものは何でも、私は必要です。したがって、MyAppとLib2は両方ともLib1への参照を取得します。
1
scottt732

数十のdllがある場合は、ビルド後のイベントを使用してコピーを行う方が簡単です。

xcopy "$(ProjectDir)bin\*.dll" $(SolutionDir)MyTargetProject\bin\" /y
0
FrankyHollywood