用 NuGet.Packaging
创建 .nupkg 文件
直接调用
NuGet.Packaging是目前最稳定、可控的方式,比旧版
NuGet.Core或命令行更适配现代 SDK 风格项目。它不依赖 MSBuild,适合 CI 脚本或工具链集成。
常见错误是误用
NuGet.Packaging.Core(已废弃)或混淆
NuspecReader和
PackageBuilder的职责——前者只读元数据,后者才真正打包。 安装包:
NuGet.Packaging≥ 6.0(.NET 6+ 推荐 6.10+,修复了符号包路径 bug) 必须先生成
.nuspec或用
Manifest对象构造元数据;不能仅靠文件夹结构自动推断 内容文件需显式添加:用
builder.AddFiles(...),路径要相对于包根(如
lib/net6.0/MyLib.dll),不是绝对路径 若含
tools/或
build/目录,权限位不影响 Windows,但 Linux 下解包可能丢执行位——实际无影响,NuGet 不校验
var builder = new PackageBuilder();
builder.Id = "My.Package";
builder.Version = new NuGetVersion("1.0.0");
builder.Authors.Add("me");
builder.Description = "A demo";
builder.AddFile("bin/Release/MyLib.dll", "lib/net6.0/MyLib.dll");
using var nupkgStream = File.Create("My.Package.1.0.0.nupkg");
builder.Save(nupkgStream);
用 dotnet pack
解包 .nupkg 文件
dotnet pack只能打包,不能解包。想“解包”,得用
NuGet.Packaging的
PackageReader,或者临时解压——因为 .nupkg 就是 ZIP,但直接改后缀解压会丢签名(
_rels、
[Content_Types].xml等元数据仍存在,不影响内容读取)。
容易踩的坑是以为
nuget.exe的
unpack命令可用:它从 5.11 起已被移除,且不支持 v3 协议格式的包(即大多数新包)。 推荐做法:用
ZipFile.ExtractToDirectory快速查看内容,适用于调试或验证结构 若需读取元数据(如依赖项、作者、描述),必须用
PackageReader,否则解析
.nuspec容易漏掉自动生成的字段(如
<dependencies></dependencies>来自
PackageReference) 签名验证失败时(
NuGet.Protocol.Core.Types.FatalProtocolException),不是包损坏,而是你用了带签名的包但没引用
NuGet.Signing,跳过验证即可
using var stream = File.OpenRead("My.Package.1.0.0.nupkg");
using var reader = new PackageReader(stream);
var metadata = reader.NuspecReader.GetMinimalPackageMetadata();
Console.WriteLine(metadata.Id); // My.Package
处理带符号的 .nupkg(.snupkg)
符号包不是独立格式,只是普通 .nupkg 加了
symbols标识和特定目录结构(
src/、
symbol/)。创建时别手动塞
.pdb到任意路径——必须进
src/并保留原始项目结构,否则调试器找不到源码。
常见误解是认为
dotnet pack --include-symbols会自动处理所有符号:它只打包当前项目的
.pdb,不递归打包
ProjectReference的符号,那些得单独打包并发布到符号服务器。 用
PackageBuilder手动加符号:路径必须以
src/开头,例如
src/MyLib/Program.cs符号包文件名必须匹配主包名 +
.snupkg,且
Id和
Version完全一致,否则 NuGet.Server 拒绝索引 本地调试时,VS 默认不查本地文件夹里的 .snupkg——得在选项里启用“仅我的代码”关闭,并添加符号路径到
Tools → Options → Debugging → Symbols
跨平台兼容性与路径陷阱
Windows 上用反斜杠
构造包内路径(如lib et6.0My.dll)会导致 Linux/macOS 下读取失败:ZIP 规范强制使用正斜杠
/,
NuGet.Packaging内部会标准化,但手动拼接时若混用,
AddFiles可能静默跳过文件。
另一个隐形问题是长路径:.nupkg 内路径总长度超 260 字符时,Windows 下
ZipFile.CreateFromDirectory会报错,但
PackageBuilder不会——它走的是内存流,不经过文件系统路径检查。 所有包内路径一律用
/,用
Path.Replace("\", "/") 不可靠,应统一用 Path.GetRelativePath+
.Replace(Path.DirectorySeparatorChar, '/')避免在
id或
version中用非法字符(如
/、
),NuGet 会抛 <code>ArgumentException,但错误信息只说“无效的包标识符”,不指明哪部分 Linux 容器里运行打包代码时,若宿主机挂载路径含空格或中文,
File.OpenRead可能失败——不是编码问题,是 .NET 6+ 对 URI 解码更严格,建议用
Uri.EscapeDataString处理路径 解包和创建看似简单,但路径分隔符、符号包结构、签名验证这几处,实际跑通一次往往要卡半小时。尤其当包要上私有源或被其他团队引用时,少一个
/或多一个
src/层级,下游就拉不到依赖。
