程序集在构建过程中有两种不同的使用方式。 第一个是用于编译,它允许包使用者的代码针对程序集中的 API 进行编译,以及 Intellisense 提供建议。 第二个是运行时,其中程序集复制到 bin 目录,并在程序执行期间使用。 某些包作者希望在编译时只希望自己的程序集(或其程序集的子集)可供其包使用者使用,但需要为运行时提供其所有依赖项。 本文档介绍实现此结果的方法。
建议:每个包一个程序集
建议为每个程序集单独创建一个包,并设置与其他程序集的包依赖关系。 NuGet 还原项目时,它将执行资产选择并支持包括、排除和创建专用不同的资产类。 为了防止包的依赖项成为任何人使用包的编译时资产,可以将资产设为 compile 私有。 在生成的包中,将导致 compile 从依赖项中排除。 请注意,如果未提供任何资产,则默认的私有资产是contentfiles;build;analyzers。 因此,应在PrivateAssets="compile;contentfiles;build;analyzers"或PackageReference或ProjectReference中使用。
<ItemGroup>
<ProjectReference Include="..\OtherProject\OtherProject.csproj" PrivateAssets="compile;contentfiles;build;analyzers" />
<PackageReference Include="SomePackage" Version="1.2.3" PrivateAssets="compile;contentfiles;build;analyzers" />
</ItemGroup>
如果要从自定义 nuspec 文件创建包,而不是让 NuGet 自动生成包, nuspec 则应使用 exclude XML 属性。
<dependencies>
<group targetFramework=".NETFramework4.8">
<dependency id="OtherProject" version="3.2.1" exclude="Compile,Build,Analyzers" />
<dependency id="SomePackage" version="1.2.3" exclude="Compile,Build,Analyzers" />
</group>
</dependencies>
建议的解决方案有三个原因。
首先,有用的汇编程序通常会被新的汇编程序/软件包引用。 虽然实用工具程序集可能只供单个包使用,因此,如果第二个包将来想要使用“专用”实用工具程序集,则需要将实用工具程序集移动到新包中,并且需要更新旧包才能将其声明为依赖项, 或实用工具包需要同时寄送到现有包和新包中。 如果程序集附带两个不同的包,并且项目引用这两个包,如果两个包中有不同版本的实用工具程序集,NuGet 将无法帮助进行版本管理。
其次,使用你包的开发人员有时也想要使用你依赖项中的一些 API。 例如,请考虑包 Microsoft.ServiceHub.Client 版本 3.0.3078。 如果下载包并检查 nuspec 文件,可以看到它列出了以依赖项开头的两个包 Microsoft.VisualStudio. ,这意味着它在运行时需要它们,但它也会排除其编译资产。 这意味着使用 Microsoft.ServiceHub.Client 的项目在 IntelliSense 中不会提供 Visual Studio API,或者在生成项目时也无法使用,除非项目已显式安装这些包。 这是具有排除资产的包依赖项的优点。 使用你的包的项目如果也想要使用你的依赖项,他们可以添加对包的引用,使 API 可供项目自身使用。
最后,一些包作者在过去对于NuGet在他们的包支持多个目标框架且同时包含多个程序集时的程序集选择感到困惑。 如果主程序集支持与实用工具程序集不同的目标框架,则可能不太清楚要将所有程序集放入哪个 lib/ 目录。 通过按程序集名称分隔每个包,使得更直观地了解每个程序集应该放入哪个lib/文件夹。 请注意,这并不意味着具有 Package1.net48 和 Package1.net6.0 包。 在Package1中拥有lib/net48/Package1.dll和lib/net6.0/Package6.0,在Package2中拥有lib/netstandard2.0/Package2.dll和lib/net5.0/Package2.dll。 Nuget 恢复项目时,Nuget 将单独为这两个软件包执行资产选择。
另请注意,依赖包含/排除资产仅由使用 PackageReference 的项目使用。 使用 packages.config 包安装的任何项目都会安装其依赖项,并使其 API 也可用。
packages.config 仅 Visual Studio 的旧版 .NET Framework 项目模板支持。 SDK 样式项目(即使是面向 .NET Framework 的项目)也不支持 packages.config,因此支持依赖项包括/排除资产。
不建议:一个包中的多个程序集
PackageReference 并 packages.config 提供了不同的功能。 无论是为了支持使用PackageReference或packages.config,还是同时支持这两者的包使用者,都需要更改包的创作方式。
NuGet 的 MSBuild Pack 目标不支持自动在包中包含项目引用。 它只会将这些引用的项目列为包依赖项。
GitHub 上有一个讨论,社区成员分享了他们实现该结果的方法,这通常涉及使用 PackagePath MSBuild 项目元数据将文件灵活地放置在包中的任何位置,正如关于在包中包含内容的文档中所描述的,并使用SuppressDependenciesWhenPacking来避免项目引用变成包依赖项。 此外,社区开发的工具也可用作 NuGet 官方包的替代工具,支持此功能。
PackageReference 支持
当包使用者使用 PackageReference时,NuGet 会独立选择编译和运行时资产,如前所述。
编译资产首选 ref/<tfm>/*.dll (例如 ref/net6.0/*.dll),但如果不存在,它将回退到 lib/<tfm>/*.dll (例如 lib/net6.0/*.dll)。
运行时资产首选 runtimes/<rid>/lib/<tfm>/*.dll (例如(runtimes/win11-x64/lib/net6.0/*.dll)),但如果不存在,它将回退到 lib/<tfm>/*.dll。
由于 ref\<tfm>\ 中的程序集在运行时不使用,它们可以是 元数据仅程序集,以减小包大小。
packages.config 支持
使用 packages.config 管理 NuGet 包的项目通常会添加对 lib\<tfm>\ 目录中所有程序集的引用。
ref\ 目录已经添加以支持 PackageReference,因此使用 packages.config 时不考虑该目录。 若要显式设置项目中 packages.config 所引用的程序集,包必须在 nuspec 文件中使用 <references> 元素。 例如:
<references>
<group targetFramework="net45">
<reference file="MyLibrary.dll" />
</group>
</references>
MSBuild 包目标不支持该 <references> 元素。 使用 MSBuild 包时,请参阅 有关使用 .nuspec 文件打包的文档 。
注释
packages.config 项目使用名为 ResolveAssemblyReference 的进程将程序集复制到 bin\<configuration>\ 输出目录。 项目的程序集会被复制,然后构建系统查看引用程序集的程序集清单,并复制这些程序集,并对所有程序集递归重复此过程。 这意味着,如果任何程序集仅通过反射、MEF 或其他依赖注入框架加载,那么尽管它们位于 bin\<tfm>\ 中,也可能不会被复制到项目的 bin\<configuration>\ 输出目录。 这也意味着,这仅适用于 .NET 程序集,不适用于使用 P/Invoke 调用的本机代码。
同时支持PackageReference和packages.config
重要
如果包包含 nuspec <references> 元素且不包含程序集 ref\<tfm>\,NuGet 会将 nuspec <references> 元素中列出的程序集公布为编译和运行时资产。 这意味着当引用的程序集需要加载目录中的任何其他程序集 lib\<tfm>\ 时,将会出现运行时异常。 因此,请务必同时使用 nuspec <references> 来支持 packages.config ,以及复制文件夹中的 ref/ 程序集以支持 PackageReference 。
runtimes/包文件夹不需要使用,它已添加到上述部分,以便完成。
Example
我的包将包含三个程序集,MyLib.dllMyHelpers.dll以及MyUtilities.dll面向 .NET Framework 4.7.2 的程序集。
MyUtilities.dll 包含只供其他两个程序集使用的类,因此我不想使这些类在 IntelliSense 中可用,也不想在编译时提供给使用我的包的项目。 我的 nuspec 文件需要包含以下 XML 元素:
<references>
<group targetFramework="net472">
<reference file="MyLib.dll" />
<reference file="MyHelpers.dll" />
</group>
</references>
我需要确保我的程序包内容为:
lib\net472\MyLib.dll
lib\net472\MyHelpers.dll
lib\net472\MyUtilities.dll
ref\net472\MyLib.dll
ref\net472\MyHelpers.dll