类库引用没有复制到运行项目的 bin 文件夹中

30

我有一个表示逻辑层的类库。在该库中,我添加了一个Google.Apis.Analytics.v3的NuGet包——它安装了该包及其所有依赖项。

我有一个控制台应用程序,使用常规引用使用该逻辑类库。一切都写好并编译完成了。

问题是,在运行时,它抛出了Google.Apis.dll未找到的异常。此DLL是与NuGet一起下载的依赖项。

检查BIN文件夹,我发现在类库bin文件夹中存在此DLL,但在控制台应用程序BIN文件夹中不存在(而其他相关的DLL存在)。这意味着并非所有引用都在编译期间被复制。

我在网上搜索了各种解决方法,但没有真正起作用的(例如手动编辑项目文件并删除该dll定义中的一个true xml行)。

最终,我做的是将相同的NuGet库添加到我的控制台应用程序中——虽然可以工作,但感觉有点肮脏,不是应该的方式。我认为控制台应用程序是客户端,应从逻辑类库获取其服务,而该类库应知道自己的东西,而不必让“客户端”担心它。

此外,该控制台应用程序不是唯一要使用该服务的应用程序,我还计划编写一个将使用该功能的Web应用程序——因此,我将需要在该Web应用程序中添加相同的NuGet包——再次感觉有些凌乱......

只是我这样吗?是否正确的做法?我正在考虑编写一个WCF项目来处理该功能,但这似乎有点过度,仅为了保持我的工作流程“更清洁”。

我是不是想太多了?

谢谢


1
你能确认一下在引用属性中,复制本地属性是否设置为“True”吗? - Michael Papworth
嗨,是的 - 它被设置为 true。我已经阅读过相关内容,并且其他人似乎也遇到了同样的行为。 - developer82
1
当您引用某个项目并将复制本地设置为true时,应该将引用的DLL文件复制到构建文件夹中。请检查Visual Studio编译日志,有可能复制失败(如果DLL文件仍在使用中,则有时会出现此问题)。 - ilansch
@ilansch - 我怎么查看日志?基本上,我已将该选项设置为true。如前所述,我已经阅读了相关信息,似乎其他人也遇到了同样的问题。从调用项目引用那个NuGet包是不好的吗?还是应该一直尝试解决它直到它工作? - developer82
你使用的是哪个版本的VS?这两个项目是否使用相同版本的.Net? - Bo TX
VS 2013。是的,这两个项目都使用相同的框架。 - developer82
3个回答

31

解释

举个例子,假设有项目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无法检测到X需要B,因此不会将其复制到项目Y的bin目录中;它只会复制X和A程序集。

解决方案

您有两个选项来解决此问题,两种选项都将导致程序集B被复制到项目Y的bin目录中:

  1. 在项目Y中添加对程序集B的引用。
  2. 在项目X的文件中添加虚拟代码,该代码使用程序集B。

我个人更喜欢第二个选项,理由如下:

  1. 如果将来添加了另一个引用项目X的项目,您不必记得还要包含对程序集B的引用(这是选项1需要做的)。
  2. 您可以有明确的注释说明虚拟代码为什么需要存在并且不应删除。因此,如果有人意外删除了代码(例如使用查找未使用代码的重构工具),您可以轻松地从源代码控制中看出代码是必需的并加以恢复。如果您使用第一种选项,并且有人使用重构工具清理未使用的引用,您就没有任何注释;您只会看到一个引用被从.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);
    }

1
如果你有以下依赖关系链: Lib1 <-- Lib2 <-- MyApp 简而言之,通过不做任何假设,构建系统避免引入不确定性/意外行为。
当你构建MyApp时,Lib2将会被复制到MyApp的bin目录中,但Lib1不会。你需要在MyApp中添加对Lib2和Lib1的引用,以便获取Lib1的dll文件并放置在MyApp的bin目录中(否则你会得到运行时错误)。很难(或者可能非常困难)确定最终位于Lib2的bin目录中的文件集合安全且适合复制到MyApp中。如果构建系统假设Lib2的bin目录中的所有内容都对MyApp安全,或者它为你隐式地引用了Lib1,那么它可能会无意中改变MyApp的行为。
想象一种解决方案,其中超过1个项目依赖于Lib2,但其中一个项目想使用Assembly.LoadFrom/Activator.CreateInstance/MEF等加载相邻的.dll文件(即插件),而另一个项目则不需要。自动复制操作可以将Lib2与插件dll一起抓取并复制到第一个和第二个项目的构建目录中(因为由于构建操作,它们位于Lib2的bin目录中)。这将改变第二个应用程序的行为。
或者,如果它更加智能,并在您引用Lib2时自动隐式引用Lib1(而不仅仅是复制bin目录内容),仍然可能会导致意外后果。如果MyApp已经依赖于Lib1,但它正在使用与Lib2要求的兼容的GAC'd/ngen'd副本,则添加对Lib2的引用会隐式地为您创建对Lib1的引用,这可能会改变加载的Lib1并更改应用程序的运行时行为。也许它可以检测到MyApp的bin目录中已经有一个Lib1并跳过它,但这样做就会做出假设:已经存在的Lib1是正确的。也许它是一个等待被Clean操作清除掉的旧的.dll文件,覆盖可能是正确的选择。
NuGet解决了您所描述的包依赖问题。如果Lib1和Lib2都有nuget包,而Lib2包依赖于Lib1包,那么当您将Lib2添加到MyApp中时,Lib1也会被添加。两个包的dll文件都会出现在MyApp的bin目录中。
最好的做法是稍微改变一下您的思路。不要想着:
- Lib2需要引用Lib1才能编译,所以我会添加对Lib1的引用
而是想:
- MyApp需要Lib2。无论Lib2需要什么,我都需要。所以MyApp和Lib2都需要引用Lib1。

0

如果你有数十个dll文件,使用一个后生成事件来进行复制会更容易:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接