无痛本地开发,同时还引用 NuGet 包

Posted

技术标签:

【中文标题】无痛本地开发,同时还引用 NuGet 包【英文标题】:Pain-free local development while also referencing NuGet packages 【发布时间】:2015-02-26 23:12:44 【问题描述】:

我正在尝试发布和使用类库的版本化 NuGet 包,同时避免本地开发的麻烦。这是一个示例 Visual Studio 解决方案布局:

| Libraries
  | LibraryA
  | LibraryB
  | LibraryC
| Applications
  | ApplicationD
  | ApplicationE

这是一个包含共享类库和多个应用程序的单一解决方案。当前,应用程序对类库的引用是本地解决方案中的引用。

我想做的是将库(A、B、C)发布为版本化的 NuGet 包,然后应用程序根据需要(D、E)引用这些包。这允许对共享库的更改独立于对已部署应用程序的更新。如果没有这个,更改一个库可能会导致二进制文件在十几个或更多应用程序中发生变化,所有这些都需要在技术上进行测试。这是不可取的,使用 NuGet 进行版本控制可以解决此问题。

但是,假设我想同时更新 LibraryA 和 ApplicationD 的内容。为了在我们切换到 NuGet 后执行此操作,我必须对 LibraryA 进行更改,提交它们,等待创建包,告诉 ApplicationD 更新其对 LibraryA 的引用,然后在 ApplicationD 中进行测试或开发。这比使用本地解决方案中的引用同时使用两者要复杂得多。

有什么更好的方法可以为我的共享类库获得版本化 NuGet 包的稳健性,同时即使跨多个项目和应用程序也能保持开发简单?我发现的唯一其他解决方案都涉及过多的开销或令人头疼的问题,例如必须不断更改 NuGet 包和本地项目之间的 ApplicationD 引用。

编辑:为了澄清前提,这个问题假设如下:

架构(解决方案和项目组织)无法进行重大重组 共享库将以不平凡的频率发生变化 更改共享库不能强制更新任何应用程序 应用程序可以引用不同版本的共享库

【问题讨论】:

"让我们说我想同时更新 LibraryA 和 ApplicationD 的内容" 如果这是一个问题,那么我强烈建议不要使用 NuGet 包。当程序集足够稳定以至于很少更改并且它们的更改过程与系统的其余部分分开时,应将它们转换为 NuGet 包。 @Euphoric 对于如何更新共享 LibraryA,您有什么建议吗?它可以被 50 个独立的应用程序使用,而不必在所有 50 个应用程序上强制使用新版本并触发大量回归测试?您提到的稳定性是一个很好的理想目标,但对于这个用例来说并不实用。当然,我对其他方法/工具持开放态度。 我认为您的库不稳定并且依赖于其他 50 个库这一事实是问题所在。最好的方法是重新设计架构以增加稳定性,然后创建一个单独的解决方案,包括它自己的需求、变更请求和开发计划。如果你真的有 50 个应用程序依赖它,那么这不会是那么大的努力。 您可以拥有特定于版本的 NuGet 引用。您需要确保共享 LibraryA 具有合理的版本控制方案,并且使用它的每个应用程序都可以在准备好时升级(或不升级)。您只需确保所有版本都可用,就像 nuget.org 一样。 @Andrew 是的!引用特定版本的能力是我首先使用 NuGet 解决此问题的原因。 【参考方案1】:

虽然需要一些工作,但可以手动编辑 .csproj 文件,以便通过将 Condition 属性添加到适当的引用来设置条件引用。

编辑我已将这些条件移至 ItemGroups,因为这似乎是我提到的生产代码的工作方式,并且有人提到这可能是 VS 2013 中的一个问题。

<ItemGroup Condition="'$(Configuration)' == 'Debug Local'">
    <!-- Library A reference as generated by VS for an in-solution reference, children unmodified -->
    <ProjectReference>...
</ItemGroup>

<ItemGroup Condition="'$(Configuration)' == 'Debug NuGet'">
    <!-- Library A reference as generated by NuGet, child nodes unmodified --> 
    <Reference Include="LibraryA">...
</ItemGroup>

这将允许您在项目 D 和 E 上拥有“Debug NuGet”与“Debug Local”的配置,它们以不同的方式引用库。如果您有多个解决方案文件,它们的配置映射到项目中的适当配置,最终用户在大多数操作中将永远不会看到更多的“调试”和“发布”,因为这些是解决方案配置,并且只会需要打开完整的解决方案来编辑​​ A、B 和 C 项目。

现在,至于让 A、B 和 C 项目不碍事,您可以将它们设置在标记为子存储库的文件夹下(假设您使用的是支持此功能的 SCM,例如 Git)。大多数用户永远不需要提取子存储库,因为他们没有访问 ABC 项目,而是从 NuGet 获取。

维护方面,我可以保证 VS 不会编辑条件引用,并且会在编译期间尊重它们 - 我已经通过 VS 2010 和 2013(EDIT:专业版,虽然我有在工作中使用相同的条件参考项目进行了深入研究。请记住,与 VS 相比,可以使引用与版本无关,使 NuGet 成为唯一需要维护版本的地方,并且可以像任何其他 NuGet 包一样完成。虽然我抱有希望,但我还没有测试过 NuGet 是否会与条件引用发生冲突。

编辑注意条件引用可能会导致有关缺少 DLL 的警告,但实际上并不妨碍编译或运行。

编辑 对于仍在阅读本文的人,我现在(7/2019)听说 IDE 不再对这些更改友好,它或包管理器可能会覆盖它们.谨慎行事,并始终阅读您的提交!

【讨论】:

谢谢,了解这些条件引用很有用。但是,一旦我转换了现有的引用,所有未来的库引用都需要使用相同的双重方法手动设置,对吧?我担心这很难维护。 这在 NuGet 方面可能会中断,但在 VS 上很容易。我会将这些信息合并到我的答案中。 我刚刚在 VS2013 中做了类似的事情,我知道,至少对于 &lt;Reference&gt; 元素,IDE 不会优雅地处理对同一个库的多个引用,即使由于 @ 而只有一个引用处于活动状态。 987654324@ 属性。我不知道同时拥有&lt;ProjectReference&gt;&lt;Reference&gt; 是否会出现同样的问题。如果是这样,可以通过创建特定于配置的属性并使用标签中的属性值而不是创建多个标签来防止它。 @Andrew 实际上,仔细查看,似乎生产项目中的不同引用都在 &lt;ItemGroup Consition=...&gt; 元素内,所以我会相应地更新我的答案 我也有类似的想法,但是当我尝试 IDE 时,最终编写了项目文件并丢失了我的自定义编辑。我想是在我从包管理器更新 nuget 包的时候。很烦人。【参考方案2】:

.NET Core (2.x ++) 更新

.NET Core 2.x 实际上内置了这个功能!

如果您在项目 B 中有对项目 A 的项目引用,并且项目 A 是具有正确包信息的 .NET Standard 或 Core 项目(Properties -&gt; PackagePackage id 设置为您的 NuGet 包 ID),那么您可以在项目 B 的 .csproj 文件中有常规项目参考:

<ItemGroup>
  <ProjectReference Include="..\..\A\ProjectA.csproj" />
</ItemGroup>

当您打包 (dotnet pack) 项目 B 时,由于项目 A 中的 Package id,生成的 .nuspec 文件将设置为具有对该包 ID 的 NuGet 依赖项,以及您可能使用的其他 NuGet 引用有,而不是仅仅包括构建的 DLL 文件。

<dependencies>
  <group targetFramework=".NETStandard2.0">
    <dependency id="Project.A" version="1.2.3" exclude="Build,Analyzers" />
    <dependency id="Newtonsoft.Json" version="12.0.2" exclude="Build,Analyzers" />
  </group>
</dependencies>

【讨论】:

您将如何控制Project.A 的版本,在您的示例中为1.2.3 @magic_al : 使用的版本号在Properties -> Package,所以我认为可以只使用最后一个版本(这对我来说很合乎逻辑:因为它不是一个包即被引用但直接源代码,版本号必须与源中的一个匹配,不能与早期版本匹配)。【参考方案3】:

我知道这是一篇已有 2 年历史的帖子,但在面临同样情况时才发现它。也为 VS2015 找到了这个,我正在测试它。我会回来并相应地调整我的答案。

https://marketplace.visualstudio.com/items?itemName=RicoSuter.NuGetReferenceSwitcherforVisualStudio2015

【讨论】:

我们可以将这个模块用于我们的目的。它为每个 csproj (MyProj.nugetreferenceswitcher) 创建一个映射文件,该文件基本上列出了不同的引用及其项目/nuget 映射。它允许轻松地来回切换,但这样做需要几秒钟/分钟。 在问答部分。 “为什么我要引用一个已经在我的解决方案中的项目的 NuGet 包?这通常不是一个简单的项目引用,不需要 NuGet 吗?在某些情况下这可能是一个有效的场景,但更现实场景是当 NuGet 包的代码位于另一个解决方案中时,这就是为什么您将其称为 NuGet 包的原因。这个工具对此没有任何帮助,不是吗?这是真的:/【参考方案4】:

我也遇到了类似的问题。一种可行的方法是使用本地存储库(基本上只是本地的一个文件夹)并在库中添加构建后脚本。例如:假设您需要更新 LibraryA 的实现,然后在 LibraryA 的构建后事件中包含以下 3 个步骤:

    检查本地仓库是否有该版本的包;如果是,则删除它

    rd /s /q %userprofile%\.nuget\packages\LibraryA\@(VersionNumber) -Recurse -ErrorAction Ignore

    创建一个 nuget 包

    nuget pack LibraryA.csproj

    推送到本地仓库

    nuget push LibraryA@(VersionNumber) -Source %userprofile%\.nuget\packages

这些步骤将确保在每次构建后始终为该版本更新包(我们必须这样做,因为 nuget 包是不可变的)

现在在 ApplicationD 中,您可以指向本地存储库 (%userprofile%.nuget\packages) 以获取 LibraryA;这样每次构建 LibraryA 后,您都会在 ApplicationD 中收到它的更新版本

PS:为了获得你的库的版本号,你可以使用这个:Determine assembly version during a post-build event

【讨论】:

【参考方案5】:

不幸的是,真的没有办法两全其美。在我的公司内部,我们通过快速构建/部署过程在一定程度上减轻了它,这抵消了总是引用 NuGet 包的大部分负担。基本上,我们所有的应用程序都使用托管在本地 NuGet 存储库中的同一库的不同版本。由于我们使用自己的软件来构建、部署和托管包,因此可以非常快速地更新库,然后在另一个解决方案中更新其 NuGet 包。基本上,我们发现的最快的工作流程是这样的:

    对库进行更改 自动构建和部署版本为 1 的库到内部 NuGet 源 在消费者应用程序中更新 NuGet 包

从签入到更新消费项目的整个过程大约需要 3 分钟。 NuGet 存储库还有一个符号/源服务器,它极大地帮助调试。

【讨论】:

您是否为此使用 1.2.3-pre,还是发现它最容易坚持使用 1.2. 并且对整个事情不那么严格? 我们从不使用预发布版本,它只是在过去给我们带来了问题。但是,为了处理类似的情况,替代方法是将包从开发提要升级为生产提要,或类似的方式。 您找到跟踪依赖关系的好方法了吗?很多时候,我们会在许多不同应用程序或其他库引用的库 (NuGet pkg) 中修复错误。如果有办法知道我们需要更新哪些项目的引用,那就太好了。【参考方案6】:

在ApplicationD的属性中,进入“Reference Paths”选项卡,添加LibraryA的输出文件夹的路径。然后,如果您更改并构建 LibraryA,ApplicationD 的下一个构建将使用修改后的 LibraryA。

完成后,不要忘记删除“引用路径”并更新引用的 NuGet 包版本。

【讨论】:

【参考方案7】:

到目前为止,我不太干净但最快的解决方案是:

假设以下两个单独的解决方案:

VS 解决方案 1:包含作为 nuget 包发布的库:

Solution1
|_ my.first.library
|_ my.second.library

VS 解决方案 2:包含使用上述库中的一个或多个作为 PackageReferences 的应用程序:

Solution2
|_ my.first.application
|  |_ depends on nuget my.first.library (let us say v1.0.1)
|
|_ my.second.application

以防万一,我正在更改my.first.library

我进行如下操作:

    my.first.library 进行代码更改并重建 导航到my.first.library的构建输出目录(例如&lt;Solution1 directory&gt;/my.first.library/bin/debug/netstandard2.0)并复制.dll.pdb文件 导航到当前正在使用的 nuget 提要的 my.first.library 的本地目录(例如:C:\Users\user.name\.nuget\packages\my.first.library\1.0.1lib\netstandard2.0)并将其中的 .dll.pdb 文件替换为在步骤 1 中生成的文件(可能进行备份)。 更改会反映在 my.first.application 中。继续工作并在需要时重复步骤 1-4

优点:

完全本地化。无需辅助 nuget Feed。 对.csproj/.sln 文件的更改为零

注意:

虽然此解决方案为您提供了灵活性,但请确保在对它们进行操作之前清除您的 nuget 缓存,例如通过发布到 nuget 服务器。谢谢@Benrobot

【讨论】:

这个解决方案对我有用,但应该注意的是,CI/CD 管道可以减轻这种方法的危险。换句话说,如果您在本地构建并从本地机器发布二进制文件,这是一种非常危险的开发实践,因为您认为是 1.0.1 的东西原来是您正在调试的某个 DLL。 当然。好点子。将向解决方案添加警告。

以上是关于无痛本地开发,同时还引用 NuGet 包的主要内容,如果未能解决你的问题,请参考以下文章

VS NuGet加载本地程序包

Docker在Linux上运行NetCore系列使用私有Nuget与多个本地包引用运行ASPNetCore

在构建后事件上更新本地 nuget 包

Docker在Linux上运行NetCore系列使用私有Nuget与多个本地包引用运行ASPNetCore

如何正确引用nuget包,这样就不会对路径进行硬编码

如何搭建Nuget服务器