类库中的引用不会复制到正在运行的项目 bin 文件夹中
Posted
技术标签:
【中文标题】类库中的引用不会复制到正在运行的项目 bin 文件夹中【英文标题】:References from class library are not copied to running project bin folder 【发布时间】:2013-12-15 08:27:45 【问题描述】:我有一个代表我的逻辑层的类库。在该库中,我为 Google.Apis.Analytics.v3 添加了一个 nuget 包 - 它安装了该包及其所有依赖项。
我有一个使用该逻辑类库的控制台应用程序(常规参考)。一切编写和编译都很好。
问题是在运行时它抛出了一个异常,即 Google.Apis.dll 没有找到。此 DLL 是随 nuget 一起下载的依赖项。
检查 BIN 文件夹,我发现在类库 bin 文件夹中存在此 DLL,但在控制台应用程序 BIN 文件夹中不存在(而其他相关 DLL 存在)。所以这意味着在编译期间并非所有引用都被复制。
我在网上搜索过,发现了各种不起作用的解决方法(例如手动编辑项目文件并删除该 dll 定义上的真正 xml 行)。
我最终做的是将相同的 nuget 库添加到我的控制台应用程序中 - 它可以工作,但感觉有点脏,而不是应有的方式。我认为控制台应用程序是应该从该逻辑类库中获取其服务的客户端,该逻辑类库应该知道它的内容,而“客户端”不会担心它。
此外,该控制台应用程序并不是唯一将使用该服务的应用程序,我还计划开发一个将使用该功能的网络应用程序 - 所以我还需要向该网络应用程序添加相同的 nuget -再次,感觉有点乱……
只有我一个人吗?这是正确的方法吗?我正在考虑编写一个 WCF 项目来处理该功能 - 但这对于功能而言似乎有点开销,并且可能会减慢我的工作流程,只是为了让事情变得“更干净”。
我是不是想多了?
谢谢
【问题讨论】:
能否确认在引用属性中,设置了Copy Local属性True
嗨,是的 - 它设置为 true。我已经阅读过它,其他人似乎也遇到了同样的行为。
当您引用某些内容并将本地复制设为 true 时,它应该将引用的 DLL 复制到您的构建文件夹,检查 Visual Studio 编译日志,可能复制失败(如果 DLL 仍在使用)
@ilansch - 我如何查看日志?基本上,我确实检查了该选项是否正确。如前所述,我读过它,似乎其他人也遇到了同样的问题。从调用项目中引用那个 nuget 这么糟糕吗?还是我应该与它抗争直到它起作用?
你用的是什么版本的VS?两个项目是否使用相同版本的 .Net?
【参考方案1】:
说明
对于示例场景,假设我们有项目 X、程序集 A 和程序集 B。程序集 A 引用程序集 B,因此项目 X 包含对 A 和 B 的引用。此外,项目 X 包含引用程序集 A (例如 A.SomeFunction())。现在,您创建一个引用项目 X 的新项目 Y。
所以依赖链看起来像这样:Y => X => A => B
Visual Studio / MSBuild 试图变得聪明,只将引用引入项目 Y,它检测到项目 X 需要它;它这样做是为了避免项目 Y 中的引用污染。问题是,由于项目 X 实际上不包含任何明确使用程序集 B 的代码(例如 B.SomeFunction()),VS/MSBuild 没有检测到 B 是必需的通过 X,因此不会将其复制到项目 Y 的 bin 目录中;它只复制 X 和 A 程序集。
解决方案
你有两个选项来解决这个问题,这两个选项都会导致程序集 B 被复制到项目 Y 的 bin 目录中:
-
在项目 Y 中添加对程序集 B 的引用。
将虚拟代码添加到项目 X 中使用程序集 B 的文件中。
出于几个原因,我个人更喜欢选项 2。
-
如果您将来添加另一个引用项目 X 的项目,则不必记住还包括对程序集 B 的引用(就像您必须使用选项 1 一样)。
您可以让 cmets 明确说明为什么需要存在虚拟代码而不是删除它。因此,如果有人不小心删除了代码(比如使用查找未使用代码的重构工具),您可以轻松地从源代码管理中看到代码是必需的并恢复它。如果您使用选项 1 并且有人使用重构工具清理未使用的引用,那么您没有任何 cmets;您只会看到从 .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);
【讨论】:
【参考方案2】:如果你有以下依赖链: Lib1
TLDR 版本:通过不做假设,构建系统避免引入不确定性/意外行为。
当您构建 MyApp 时,Lib2 将为您复制到 MyApp 的 bin 目录,但 Lib1 不会。您需要在 MyApp 中添加对 Lib2 和 Lib1 的引用,以便在 MyApp 的 bin 目录中获取 Lib1 的 dll(否则会出现运行时错误)。确定最终在 Lib2 的 bin 目录中的确切文件集是不可能的(或者可能真的很难),这些文件可以安全且适合复制到 MyApp 的。如果构建系统假设 Lib2 的 bin 目录中的所有内容对 MyApp 都是安全的,或者如果它为您隐式引用 Lib1,它可能会无意中改变 MyApp 的行为。
想象一个解决方案,其中多个项目依赖于 Lib2,但其中一个项目希望使用 Assembly.LoadFrom/Activator.CreateInstance/MEF/etc 加载相邻的 .dll 文件。 (一个插件),另一个没有。自动复制操作可以抓取 Lib2 以及插件 dll 并将其复制到第一个和第二个项目的构建目录(因为它在 Lib2 的 bin 目录中作为构建操作的结果)。这将改变第二个应用的行为。
或者,如果它更聪明一点,并且在您引用 Lib2 时为您隐式引用 Lib1(并且不只是复制 bin 目录内容),它仍然可能导致意想不到的后果。如果 MyApp 已经依赖于 Lib1,但它使用的是与 Lib2 所需的副本兼容的 GAC'd/ngen'd 副本怎么办。如果添加对 Lib2 的引用为您隐式创建了对 Lib1 的引用,则可能会更改加载的 Lib1 并更改应用程序的运行时行为。它可能会检测到 MyApp 的 bin 目录中已经有一个 Lib1 并跳过它,但是它会假设已经存在的 Lib1 是正确的。也许它是一个陈旧的 .dll 等待被清理操作擦除,而覆盖是正确的举动。
NuGet 解决了您描述的包依赖关系问题。如果 Lib1 和 Lib2 都有 nuget 包,并且 Lib2 包依赖于 Lib1 包,那么当您将 Lib2 添加到 MyApp 时,也会添加 Lib1。两个 pacakges 的 dll 最终都会在 MyApp 的 bin 目录中。
最好的办法是稍微颠倒一下你的想法。而不是思考:
Lib2 需要对 Lib1 的引用才能编译,所以我将添加对 Lib1 的引用想想:
MyApp 需要 Lib2。无论 Lib2 需要什么,我都需要。所以 MyApp 和 Lib2 都得到了对 Lib1 的引用。【讨论】:
【参考方案3】:如果您有 10 个 dll,则使用 postbuild 事件进行复制会更容易:
xcopy "$(ProjectDir)bin\*.dll" $(SolutionDir)MyTargetProject\bin\" /y
【讨论】:
以上是关于类库中的引用不会复制到正在运行的项目 bin 文件夹中的主要内容,如果未能解决你的问题,请参考以下文章
Visual Studio/MSBuild 将引用的类库的 app.config 作为 *.dll.config 复制到当前项目的 bin 文件夹