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

67

我正在尝试发布和使用版本化的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的引用。

编辑:为了澄清前提条件,本问题假设以下内容:

  • 架构(解决方案和项目组织)不能被重大改组
  • 共享库将以非微不足道的频率进行更改
  • 更改共享库不会强制更新任何应用程序
  • 应用程序可以引用不同版本的共享库

1
假设我想同时更新LibraryA和ApplicationD的内容,如果这是一个问题的话,那么我强烈建议不要使用NuGet包。当程序集足够稳定且变更过程与系统的其余部分分离时,应将它们转换为NuGet包。 - Euphoric
1
@Euphoric,你有什么建议可以更新共享的LibraryA吗?这个库可能被50个不同的应用程序使用,而不需要强制在所有50个应用程序上触发新版本并引发大量回归测试。你提到的稳定性是一个很好的理想目标,但对于这种情况来说并不切实际。当然,我也乐意尝试其他方法和工具。 - jmsb
我认为问题在于你拥有一个不稳定且受其他50个库依赖的库。最好的方法是重新设计架构以增强稳定性,然后创建一个独立的解决方案,具有自己的要求、变更请求和开发计划。如果确实有50个应用程序依赖它,那么这并不需要太大的努力。 - Euphoric
2
您可以使用特定版本的NuGet引用。您需要确保共享的LibraryA具有合理的版本控制方案,并且每个使用它的应用程序都可以在准备好时升级(或不升级)。您只需确保所有发布版本都可用,就像nuget.org一样。 - Andrew
@Andrew 是的!能够引用特定版本是我使用 NuGet 来解决这个问题的原因。 - jmsb
显示剩余2条评论
7个回答

30

虽然需要付出一些努力,但可以手动编辑.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”配置,这些配置引用不同的库。如果您有多个解决方案文件,它们将其配置映射到其中的项目的适当配置,则最终用户大多数操作都只会看到“Debug”和“Release”,因为它们是解决方案配置,并且仅需要打开完整的解决方案以编辑A、B和C项目。
至于让A、B和C项目离开的方法,您可以将它们设置在标记为子存储库的文件夹下(假设您正在使用支持此功能的SCM,如Git)。由于用户不使用ABC项目而是从NuGet获取,因此大多数用户永远不需要拉取子存储库。
在维护方面,我可以保证VS不会编辑条件引用,并且在编译期间将予以尊重-我已经使用相同的条件引用项目通过了VS 2010和2013 ( EDIT :虽然我已经开始尝试使用express)。请记住,在VS中,引用可以使版本不可知,使得NuGet成为唯一需要维护版本的地方,这可以像任何其他NuGet包一样完成。虽然我很有希望,但我还没有测试过NuGet是否会与条件引用发生冲突。 EDIT 还要注意,条件引用可能会导致警告缺少DLL文件,但实际上并不妨碍编译或运行。 EDIT 对于仍在阅读本文的人,我现在(7/2019)听说IDE对这些更改不再那么友好,而IDE或Package Manager可能会覆盖它们。小心行事,并始终阅读提交!

2
谢谢,这些条件引用很有用。但是,一旦我转换了现有的引用,所有未来的库引用都需要手动使用相同的双重方法设置,是吗?我的担心是这会很难维护。 - jmsb
1
这可能是NuGet方面的问题,但在VS上很容易解决。我会将这个信息融入到我的答案中。 - David
1
我知道从刚在VS2013中做了类似的事情,至少对于<Reference>元素,即使只有一个引用是活动的,由于Condition属性,IDE也不能优雅地处理对同一库的多个引用。 我不知道同时拥有<ProjectReference><Reference>是否会出现相同的问题。 如果是这样,可以通过创建特定于配置的属性并在标记中使用属性值而不是创建多个标记来防止此问题。 - Andrew
我似乎记得尝试过同样的事情,但没有帮助。但是再说一遍,我不知道 ProjectReference 是否会表现出相同的行为,而且这只是一个令人讨厌的警告。 - Andrew
1
我有一个类似的想法,但当我尝试使用IDE时,最终写入了项目文件并且失去了我的自定义编辑。我认为这可能是在从包管理器更新nuget包时发生的。非常令人恼火。 - waxingsatirical
显示剩余4条评论

12

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

.NET Core 2.x实际上已经内置了此功能!

如果您在项目B中引用项目A,并且项目A是一个带有正确包信息的.NET标准或Core项目(在“属性”->“程序包”中将“程序包ID”设置为您的NuGet程序包ID),那么您可以在项目B的.csproj文件中拥有一个常规项目引用:

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

当你使用dotnet pack打包项目B时,由于项目A中的Package id,生成的.nuspec文件将会被设置为对该Package 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>

1
你会如何控制你示例中的 Project.A 版本,即 1.2.3 - magic_al
@magic_al:使用的版本号在“属性”->“包”中,因此我认为只使用最新版本是可能的(对我来说这很合理:由于引用的不是包而是直接源代码,版本号必须与源代码的版本相匹配,不能匹配到早期版本)。 - Xav987
1
这似乎并没有真正解决问题。Project.A是一个类库,我将把它放在NuGet包中。Project.B是一个应用程序,永远不会被打包成dotnet pack。此时,我认为只需将项目引用粘贴到其中,直到类库稳定下来,然后再替换引用,对我来说并不是什么大问题。 - Auspex
同意@Auspex。这是有用的信息,但并没有回答问题。 - Josh Noe

9

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

4
我也遇到过类似的问题。一种行之有效的做法是使用本地仓库(基本上就是本地文件夹),并在库中添加后构建脚本。例如:假设你需要更新LibraryA的实现,则为LibraryA的后构建事件包括以下3个步骤:
  1. 检查本地仓库是否有该版本的包; 如果有,则删除它
  2. rd /s /q %userprofile%\.nuget\packages\LibraryA\@(VersionNumber) -Recurse -ErrorAction Ignore

  3. 创建一个NuGet包
  4. nuget pack LibraryA.csproj

  5. 将其推送到本地仓库
  6. 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

2
在ApplicationD的属性中,转到“引用路径”选项卡,并添加LibraryA输出文件夹的路径。然后,如果您更改并构建LibraryA,则下一次构建ApplicationD将使用修改后的LibraryA。
完成后,请不要忘记删除“引用路径”并更新引用的NuGet包版本。
"Original Answer"翻译成"最初的回答"

2
很遗憾,没有办法兼顾两全其美。在我们公司内部,我们通过快速构建/部署流程来缓解始终引用NuGet包所带来的大部分负担。基本上,我们所有的应用程序都使用同一个库的不同版本,这些库托管在本地的 NuGet 仓库中。由于我们使用自己的软件来构建、部署和托管这些包,因此更新库的速度非常快,然后在另一个解决方案中更新其 NuGet 包。总体上,我们发现最快速的工作流程如下:
  1. 更改库内容
  2. 自动构建并部署版本号比当前版本高1的库到内部 NuGet feed
  3. 更新消费应用程序中的 NuGet 包
整个过程从代码提交到更新消费项目需要大约3分钟。NuGet 仓库还拥有符号/源服务器,这对于调试非常有帮助。

你是使用1.2.3-pre<Build>来做这个吗?还是觉得坚持使用1.2.<build>更容易,对整个版本号不那么严格? - steve
我们从不使用预发布版本控制,因为过去这种做法只会给我们带来问题。然而,为了处理类似的情况,另一种选择是将软件包从开发源推广到生产源,或者采取类似的方法。 - John Rasch
1
你找到了一个好的跟踪依赖关系的方法吗?很多时候,我们会在被许多不同应用程序或其他库引用的库(NuGet pkg)中进行错误修复。有一种方法可以知道我们需要更新哪些项目的引用,这将是非常好的。 - scuba88

2

我的解决方案并不十分干净,但是迄今为止最快的解决方案是:

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

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

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

VS Solution 2:包含应用程序,这些应用程序作为PackageReference消耗了上述一个或多个库:

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

如果我对my.first.library进行更改,我会按照以下步骤进行:

步骤如下:

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

优点:

  • 完全本地化。不需要次要的nuget feeds。
  • .csproj/.sln文件保持不变

注意:

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


这个解决方案对我来说是有效的,但需要注意的是,CI/CD管道可以减轻此方法的危险。换句话说,如果您在本地构建并从本地机器发布二进制文件,则这是一种非常危险的开发实践,因为您认为是1.0.1的东西可能会变成您正在调试的某个DLL。 - Benrobot
当然。好主意。将在解决方案中添加警告。 - Bahaa

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