dotnet-nuget-authoring
NuGet package authoring for .NET library authors: SDK-style .csproj package properties (PackageId, PackageTags,
PackageReadmeFile, PackageLicenseExpression), source generator NuGet packaging with analyzers/dotnet/cs/ folder
layout and buildTransitive targets, multi-TFM packages, symbol packages (snupkg) with deterministic builds, package
signing (author signing with certificates, repository signing), package validation (EnablePackageValidation,
Microsoft.DotNet.ApiCompat.Task for API compatibility), and NuGet versioning strategies (SemVer 2.0, pre-release
suffixes, NBGV integration).
Version assumptions: .NET 8.0+ baseline. NuGet client bundled with .NET 8+ SDK. Microsoft.DotNet.ApiCompat.Task
8.0+ for API compatibility validation.
Scope
- SDK-style csproj package properties and metadata
- Source generator NuGet packaging with analyzers folder layout
- Multi-TFM packages and symbol packages (snupkg)
- Package signing (author and repository signing)
- Package validation (EnablePackageValidation, API compatibility)
- NuGet versioning strategies (SemVer 2.0, NBGV)
Out of scope
- Central Package Management, SourceLink, nuget.config -- see [skill:dotnet-project-structure]
- CI/CD NuGet push workflows -- see [skill:dotnet-gha-publish] and [skill:dotnet-ado-publish]
- CLI tool packaging and distribution -- see [skill:dotnet-cli-packaging]
- Roslyn analyzer authoring -- see [skill:dotnet-roslyn-analyzers]
- Release lifecycle and NBGV setup -- see [skill:dotnet-release-management]
Cross-references: [skill:dotnet-project-structure] for CPM, SourceLink, nuget.config, [skill:dotnet-gha-publish] for CI
NuGet push workflows, [skill:dotnet-ado-publish] for ADO NuGet push workflows, [skill:dotnet-cli-packaging] for CLI tool
distribution formats, [skill:dotnet-csharp-source-generators] for Roslyn source generator authoring,
[skill:dotnet-release-management] for release lifecycle and NBGV setup, [skill:dotnet-roslyn-analyzers] for Roslyn
analyzer authoring.
SDK-Style Package Properties
Every NuGet package starts with MSBuild properties in the .csproj. SDK-style projects produce NuGet packages with
dotnet pack -- no .nuspec file required.
Essential Package Metadata
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<PackageId>MyCompany.Widgets</PackageId>
<Version>1.0.0</Version>
<Authors>My Company</Authors>
<Description>A library for managing widgets with fluent API support.</Description>
<PackageTags>widgets;fluent;dotnet</PackageTags>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageProjectUrl>https://github.com/mycompany/widgets</PackageProjectUrl>
<RepositoryUrl>https://github.com/mycompany/widgets</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<!-- README displayed on nuget.org package page -->
<PackageReadmeFile>README.md</PackageReadmeFile>
<!-- Package icon (128x128 PNG recommended) -->
<PackageIcon>icon.png</PackageIcon>
<!-- Generate XML docs for IntelliSense -->
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<!-- Deterministic builds for reproducibility -->
<ContinuousIntegrationBuild Condition="'$(CI)' == 'true'">true</ContinuousIntegrationBuild>
</PropertyGroup>
<!-- Include README and icon in the package -->
<ItemGroup>
<None Include="README.md" Pack="true" PackagePath="\" />
<None Include="icon.png" Pack="true" PackagePath="\" />
</ItemGroup>
</Project>
```markdown
### Property Reference
| Property | Purpose | Example |
|----------|---------|---------|
| `PackageId` | Unique package identifier on nuget.org | `MyCompany.Widgets` |
| `Version` | SemVer 2.0 version | `1.2.3-beta.1` |
| `Authors` | Comma-separated author names | `Jane Doe, My Company` |
| `Description` | Package description for nuget.org | `Fluent widget management library` |
| `PackageTags` | Semicolon-separated search tags | `widgets;fluent;dotnet` |
| `PackageLicenseExpression` | SPDX license identifier | `MIT`, `Apache-2.0` |
| `PackageLicenseFile` | License file (alternative to expression) | `LICENSE.txt` |
| `PackageReadmeFile` | Markdown readme displayed on nuget.org | `README.md` |
| `PackageIcon` | Package icon filename | `icon.png` |
| `PackageProjectUrl` | Project homepage URL | `https://github.com/mycompany/widgets` |
| `PackageReleaseNotes` | Release notes for this version | `Added widget caching support` |
| `Copyright` | Copyright statement | `Copyright 2024 My Company` |
| `RepositoryUrl` | Source repository URL | `https://github.com/mycompany/widgets` |
| `RepositoryType` | Repository type | `git` |
### Directory.Build.props for Shared Metadata
For multi-project repos, set common properties in `Directory.Build.props`:
```xml
<!-- Directory.Build.props (repo root) -->
<Project>
<PropertyGroup>
<Authors>My Company</Authors>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageProjectUrl>https://github.com/mycompany/widgets</PackageProjectUrl>
<RepositoryUrl>https://github.com/mycompany/widgets</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<Copyright>Copyright 2024 My Company</Copyright>
</PropertyGroup>
</Project>
```text
Individual `.csproj` files then only set package-specific properties (`PackageId`, `Description`, `PackageTags`).
---
## Source Generator NuGet Packaging
Source generators and analyzers require a specific NuGet package layout. The generator DLL must be placed in the `analyzers/dotnet/cs/` folder, not the `lib/` folder. For Roslyn source generator authoring (IIncrementalGenerator, syntax/semantic analysis), see [skill:dotnet-csharp-source-generators]. This section covers NuGet *packaging* of generators only.
### Project Setup for Source Generator Package
```xml
<!-- MyCompany.Generators.csproj -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
<IsRoslynComponent>true</IsRoslynComponent>
<!-- Package metadata -->
<PackageId>MyCompany.Generators</PackageId>
<Description>Source generators for widget auto-registration.</Description>
<!-- Do NOT include generator DLL in lib/ folder -->
<IncludeBuildOutput>false</IncludeBuildOutput>
<SuppressDependenciesWhenPacking>true</SuppressDependenciesWhenPacking>
<!-- Generator must target netstandard2.0 for Roslyn host compat -->
<IsPackable>true</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" PrivateAssets="all" />
</ItemGroup>
<!-- Place generator DLL in analyzers folder -->
<ItemGroup>
<None Include="$(OutputPath)$(AssemblyName).dll"
Pack="true"
PackagePath="analyzers/dotnet/cs"
Visible="false" />
</ItemGroup>
</Project>
```text
### Adding Build Props/Targets
When a source generator needs to set MSBuild properties in consuming projects, use the `buildTransitive` folder:
```xml
<!-- build/MyCompany.Generators.props -->
<Project>
<PropertyGroup>
<MyCompanyGeneratorsEnabled>true</MyCompanyGeneratorsEnabled>
</PropertyGroup>
<ItemGroup>
<!-- Example: add additional files for generator to consume -->
<CompilerVisibleProperty Include="MyCompanyGeneratorsEnabled" />
</ItemGroup>
</Project>
```text
Include `buildTransitive` content in the package:
```xml
<!-- In the .csproj -->
<ItemGroup>
<!-- buildTransitive ensures props/targets flow through transitive dependencies -->
<None Include="build\MyCompany.Generators.props"
Pack="true"
PackagePath="buildTransitive\MyCompany.Generators.props" />
<None Include="build\MyCompany.Generators.targets"
Pack="true"
PackagePath="buildTransitive\MyCompany.Generators.targets" />
</ItemGroup>
```text
### Multi-Target Analyzer Package (Analyzer + Library)
When shipping both an analyzer and a runtime library in the same package:
```xml
<!-- MyCompany.Widgets.csproj (ships both runtime lib + analyzer) -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net8.0;netstandard2.0</TargetFrameworks>
<PackageId>MyCompany.Widgets</PackageId>
</PropertyGroup>
<!-- Reference generator project, but suppress its output from lib/ -->
<ItemGroup>
<ProjectReference Include="..\MyCompany.Widgets.Generators\MyCompany.Widgets.Generators.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false" />
</ItemGroup>
</Project>
```text
### NuGet Package Folder Layout
```text
MyCompany.Generators.1.0.0.nupkg
analyzers/
dotnet/
cs/
MyCompany.Generators.dll <-- generator/analyzer assembly
buildTransitive/
MyCompany.Generators.props <-- auto-imported MSBuild props
MyCompany.Generators.targets <-- auto-imported MSBuild targets
lib/
netstandard2.0/
_._ <-- empty marker (no runtime lib)
```text
---
## Multi-TFM Packages
Multi-targeting produces a single NuGet package with assemblies for each target framework. Consumers automatically get the best-matching assembly.
### When to Multi-Target
| Scenario | Approach |
|----------|----------|
| Library works on net8.0 only | Single TFM: `<TargetFramework>net8.0</TargetFramework>` |
| Library needs netstandard2.0 + net8.0 APIs | Multi-TFM: `<TargetFrameworks>netstandard2.0;net8.0</TargetFrameworks>` |
| Library uses net9.0-specific APIs (e.g., `SearchValues`) | Multi-TFM with polyfills or conditional code |
| Library targets .NET Framework consumers | Include `net472` or `netstandard2.0` TFM |
### Multi-TFM Configuration
```xml
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net8.0;net9.0</TargetFrameworks>
</PropertyGroup>
<!-- API differences per TFM -->
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
<PackageReference Include="System.Memory" Version="4.6.0" />
<PackageReference Include="System.Text.Json" Version="8.0.5" />
</ItemGroup>
</Project>
```json
### Conditional Compilation
```csharp
public static class StringExtensions
{
public static bool ContainsIgnoreCase(this string source, string value)
{
#if NET8_0_OR_GREATER
return source.Contains(value, StringComparison.OrdinalIgnoreCase);
#else
return source.IndexOf(value, StringComparison.OrdinalIgnoreCase) >= 0;
#endif
}
}
```text
### NuGet Package Folder Layout (Multi-TFM)
```text
MyCompany.Widgets.1.0.0.nupkg
lib/
netstandard2.0/
MyCompany.Widgets.dll
net8.0/
MyCompany.Widgets.dll
net9.0/
MyCompany.Widgets.dll
```text
---
## Symbol Packages and Deterministic Builds
Symbol packages (`.snupkg`) enable source-level debugging for package consumers via the NuGet symbol server.
### Enabling Symbol Packages
```xml
<PropertyGroup>
<!-- Generate .snupkg alongside .nupkg -->
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<!-- Deterministic builds (required for reproducible packages) -->
<Deterministic>true</Deterministic>
<ContinuousIntegrationBuild Condition="'$(CI)' == 'true'">true</ContinuousIntegrationBuild>
<!-- Embed source in PDB for debugging without source server -->
<EmbedUntrackedSources>true</EmbedUntrackedSources>
</PropertyGroup>
```text
The `snupkg` is pushed alongside the `nupkg` automatically when using `dotnet nuget push`:
```bash
# Push both .nupkg and .snupkg to nuget.org
dotnet nuget push "bin/Release/*.nupkg" \
--source https://api.nuget.org/v3/index.json \
--api-key "$NUGET_API_KEY"
```json
**SourceLink integration:** For source-level debugging with links to the actual source repository, configure SourceLink in your project. See [skill:dotnet-project-structure] for SourceLink setup -- do not duplicate that configuration here.
### Embedded PDB Alternative
For packages where a separate symbol package is undesirable:
```xml
<PropertyGroup>
<DebugType>embedded</DebugType>
</PropertyGroup>
```xml
This embeds the PDB directly in the assembly DLL. The tradeoff is larger package size but simpler distribution.
---
## Package Signing
NuGet supports author signing (proving package origin) and repository signing (proving it came from a specific feed).
### Author Signing with a Certificate
```bash
# Sign a package with a PFX certificate
dotnet nuget sign "MyCompany.Widgets.1.0.0.nupkg" \
--certificate-path ./signing-cert.pfx \
--certificate-password "$CERT_PASSWORD" \
--timestamper http://timestamp.digicert.com
# Sign with a certificate from the certificate store (Windows)
## Detailed Examples
See [references/detailed-examples.md](references/detailed-examples.md) for complete code samples and advanced patterns.
1---2name: dotnet-nuget-authoring3category: developer-experience4subcategory: nuget5description: Creates NuGet packages. SDK-style csproj, source generators, multi-TFM, symbols, signing.6license: MIT7targets: ['*']8tags: [foundation, dotnet, skill]9version: '0.0.1'10author: 'dotnet-agent-harness'11invocable: true12claudecode:13 allowed-tools: ['Read', 'Grep', 'Glob', 'Bash', 'Write', 'Edit']14codexcli:15 short-description: '.NET skill guidance for foundation tasks'16opencode:17 allowed-tools: ['Read', 'Grep', 'Glob', 'Bash', 'Write', 'Edit']18copilot: {}19geminicli: {}20antigravity: {}21---2223# dotnet-nuget-authoring2425NuGet package authoring for .NET library authors: SDK-style `.csproj` package properties (`PackageId`, `PackageTags`,26`PackageReadmeFile`, `PackageLicenseExpression`), source generator NuGet packaging with `analyzers/dotnet/cs/` folder27layout and `buildTransitive` targets, multi-TFM packages, symbol packages (snupkg) with deterministic builds, package28signing (author signing with certificates, repository signing), package validation (`EnablePackageValidation`,29`Microsoft.DotNet.ApiCompat.Task` for API compatibility), and NuGet versioning strategies (SemVer 2.0, pre-release30suffixes, NBGV integration).3132**Version assumptions:** .NET 8.0+ baseline. NuGet client bundled with .NET 8+ SDK. `Microsoft.DotNet.ApiCompat.Task`338.0+ for API compatibility validation.3435## Scope3637- SDK-style csproj package properties and metadata38- Source generator NuGet packaging with analyzers folder layout39- Multi-TFM packages and symbol packages (snupkg)40- Package signing (author and repository signing)41- Package validation (EnablePackageValidation, API compatibility)42- NuGet versioning strategies (SemVer 2.0, NBGV)4344## Out of scope4546- Central Package Management, SourceLink, nuget.config -- see [skill:dotnet-project-structure]47- CI/CD NuGet push workflows -- see [skill:dotnet-gha-publish] and [skill:dotnet-ado-publish]48- CLI tool packaging and distribution -- see [skill:dotnet-cli-packaging]49- Roslyn analyzer authoring -- see [skill:dotnet-roslyn-analyzers]50- Release lifecycle and NBGV setup -- see [skill:dotnet-release-management]5152Cross-references: [skill:dotnet-project-structure] for CPM, SourceLink, nuget.config, [skill:dotnet-gha-publish] for CI53NuGet push workflows, [skill:dotnet-ado-publish] for ADO NuGet push workflows, [skill:dotnet-cli-packaging] for CLI tool54distribution formats, [skill:dotnet-csharp-source-generators] for Roslyn source generator authoring,55[skill:dotnet-release-management] for release lifecycle and NBGV setup, [skill:dotnet-roslyn-analyzers] for Roslyn56analyzer authoring.5758---5960## SDK-Style Package Properties6162Every NuGet package starts with MSBuild properties in the `.csproj`. SDK-style projects produce NuGet packages with63`dotnet pack` -- no `.nuspec` file required.6465### Essential Package Metadata6667````xml6869<Project Sdk="Microsoft.NET.Sdk">70 <PropertyGroup>71 <TargetFramework>net8.0</TargetFramework>72 <PackageId>MyCompany.Widgets</PackageId>73 <Version>1.0.0</Version>74 <Authors>My Company</Authors>75 <Description>A library for managing widgets with fluent API support.</Description>76 <PackageTags>widgets;fluent;dotnet</PackageTags>77 <PackageLicenseExpression>MIT</PackageLicenseExpression>78 <PackageProjectUrl>https://github.com/mycompany/widgets</PackageProjectUrl>79 <RepositoryUrl>https://github.com/mycompany/widgets</RepositoryUrl>80 <RepositoryType>git</RepositoryType>8182 <!-- README displayed on nuget.org package page -->83 <PackageReadmeFile>README.md</PackageReadmeFile>8485 <!-- Package icon (128x128 PNG recommended) -->86 <PackageIcon>icon.png</PackageIcon>8788 <!-- Generate XML docs for IntelliSense -->89 <GenerateDocumentationFile>true</GenerateDocumentationFile>9091 <!-- Deterministic builds for reproducibility -->92 <ContinuousIntegrationBuild Condition="'$(CI)' == 'true'">true</ContinuousIntegrationBuild>93 </PropertyGroup>9495 <!-- Include README and icon in the package -->96 <ItemGroup>97 <None Include="README.md" Pack="true" PackagePath="\" />98 <None Include="icon.png" Pack="true" PackagePath="\" />99 </ItemGroup>100</Project>101102```markdown103104### Property Reference105106| Property | Purpose | Example |107|----------|---------|---------|108| `PackageId` | Unique package identifier on nuget.org | `MyCompany.Widgets` |109| `Version` | SemVer 2.0 version | `1.2.3-beta.1` |110| `Authors` | Comma-separated author names | `Jane Doe, My Company` |111| `Description` | Package description for nuget.org | `Fluent widget management library` |112| `PackageTags` | Semicolon-separated search tags | `widgets;fluent;dotnet` |113| `PackageLicenseExpression` | SPDX license identifier | `MIT`, `Apache-2.0` |114| `PackageLicenseFile` | License file (alternative to expression) | `LICENSE.txt` |115| `PackageReadmeFile` | Markdown readme displayed on nuget.org | `README.md` |116| `PackageIcon` | Package icon filename | `icon.png` |117| `PackageProjectUrl` | Project homepage URL | `https://github.com/mycompany/widgets` |118| `PackageReleaseNotes` | Release notes for this version | `Added widget caching support` |119| `Copyright` | Copyright statement | `Copyright 2024 My Company` |120| `RepositoryUrl` | Source repository URL | `https://github.com/mycompany/widgets` |121| `RepositoryType` | Repository type | `git` |122123### Directory.Build.props for Shared Metadata124125For multi-project repos, set common properties in `Directory.Build.props`:126127```xml128129<!-- Directory.Build.props (repo root) -->130<Project>131 <PropertyGroup>132 <Authors>My Company</Authors>133 <PackageLicenseExpression>MIT</PackageLicenseExpression>134 <PackageProjectUrl>https://github.com/mycompany/widgets</PackageProjectUrl>135 <RepositoryUrl>https://github.com/mycompany/widgets</RepositoryUrl>136 <RepositoryType>git</RepositoryType>137 <Copyright>Copyright 2024 My Company</Copyright>138 </PropertyGroup>139</Project>140141```text142143Individual `.csproj` files then only set package-specific properties (`PackageId`, `Description`, `PackageTags`).144145---146147## Source Generator NuGet Packaging148149Source generators and analyzers require a specific NuGet package layout. The generator DLL must be placed in the `analyzers/dotnet/cs/` folder, not the `lib/` folder. For Roslyn source generator authoring (IIncrementalGenerator, syntax/semantic analysis), see [skill:dotnet-csharp-source-generators]. This section covers NuGet *packaging* of generators only.150151### Project Setup for Source Generator Package152153```xml154155<!-- MyCompany.Generators.csproj -->156<Project Sdk="Microsoft.NET.Sdk">157 <PropertyGroup>158 <TargetFramework>netstandard2.0</TargetFramework>159 <EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>160 <IsRoslynComponent>true</IsRoslynComponent>161162 <!-- Package metadata -->163 <PackageId>MyCompany.Generators</PackageId>164 <Description>Source generators for widget auto-registration.</Description>165166 <!-- Do NOT include generator DLL in lib/ folder -->167 <IncludeBuildOutput>false</IncludeBuildOutput>168 <SuppressDependenciesWhenPacking>true</SuppressDependenciesWhenPacking>169170 <!-- Generator must target netstandard2.0 for Roslyn host compat -->171 <IsPackable>true</IsPackable>172 </PropertyGroup>173174 <ItemGroup>175 <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" PrivateAssets="all" />176 </ItemGroup>177178 <!-- Place generator DLL in analyzers folder -->179 <ItemGroup>180 <None Include="$(OutputPath)$(AssemblyName).dll"181 Pack="true"182 PackagePath="analyzers/dotnet/cs"183 Visible="false" />184 </ItemGroup>185</Project>186187```text188189### Adding Build Props/Targets190191When a source generator needs to set MSBuild properties in consuming projects, use the `buildTransitive` folder:192193```xml194195<!-- build/MyCompany.Generators.props -->196<Project>197 <PropertyGroup>198 <MyCompanyGeneratorsEnabled>true</MyCompanyGeneratorsEnabled>199 </PropertyGroup>200 <ItemGroup>201 <!-- Example: add additional files for generator to consume -->202 <CompilerVisibleProperty Include="MyCompanyGeneratorsEnabled" />203 </ItemGroup>204</Project>205206```text207208Include `buildTransitive` content in the package:209210```xml211212<!-- In the .csproj -->213<ItemGroup>214 <!-- buildTransitive ensures props/targets flow through transitive dependencies -->215 <None Include="build\MyCompany.Generators.props"216 Pack="true"217 PackagePath="buildTransitive\MyCompany.Generators.props" />218 <None Include="build\MyCompany.Generators.targets"219 Pack="true"220 PackagePath="buildTransitive\MyCompany.Generators.targets" />221</ItemGroup>222223```text224225### Multi-Target Analyzer Package (Analyzer + Library)226227When shipping both an analyzer and a runtime library in the same package:228229```xml230231<!-- MyCompany.Widgets.csproj (ships both runtime lib + analyzer) -->232<Project Sdk="Microsoft.NET.Sdk">233 <PropertyGroup>234 <TargetFrameworks>net8.0;netstandard2.0</TargetFrameworks>235 <PackageId>MyCompany.Widgets</PackageId>236 </PropertyGroup>237238 <!-- Reference generator project, but suppress its output from lib/ -->239 <ItemGroup>240 <ProjectReference Include="..\MyCompany.Widgets.Generators\MyCompany.Widgets.Generators.csproj"241 OutputItemType="Analyzer"242 ReferenceOutputAssembly="false" />243 </ItemGroup>244</Project>245246```text247248### NuGet Package Folder Layout249250```text251252MyCompany.Generators.1.0.0.nupkg253 analyzers/254 dotnet/255 cs/256 MyCompany.Generators.dll <-- generator/analyzer assembly257 buildTransitive/258 MyCompany.Generators.props <-- auto-imported MSBuild props259 MyCompany.Generators.targets <-- auto-imported MSBuild targets260 lib/261 netstandard2.0/262 _._ <-- empty marker (no runtime lib)263264```text265266---267268## Multi-TFM Packages269270Multi-targeting produces a single NuGet package with assemblies for each target framework. Consumers automatically get the best-matching assembly.271272### When to Multi-Target273274| Scenario | Approach |275|----------|----------|276| Library works on net8.0 only | Single TFM: `<TargetFramework>net8.0</TargetFramework>` |277| Library needs netstandard2.0 + net8.0 APIs | Multi-TFM: `<TargetFrameworks>netstandard2.0;net8.0</TargetFrameworks>` |278| Library uses net9.0-specific APIs (e.g., `SearchValues`) | Multi-TFM with polyfills or conditional code |279| Library targets .NET Framework consumers | Include `net472` or `netstandard2.0` TFM |280281### Multi-TFM Configuration282283```xml284285<Project Sdk="Microsoft.NET.Sdk">286 <PropertyGroup>287 <TargetFrameworks>netstandard2.0;net8.0;net9.0</TargetFrameworks>288 </PropertyGroup>289290 <!-- API differences per TFM -->291 <ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">292 <PackageReference Include="System.Memory" Version="4.6.0" />293 <PackageReference Include="System.Text.Json" Version="8.0.5" />294 </ItemGroup>295</Project>296297```json298299### Conditional Compilation300301```csharp302303public static class StringExtensions304{305 public static bool ContainsIgnoreCase(this string source, string value)306 {307#if NET8_0_OR_GREATER308 return source.Contains(value, StringComparison.OrdinalIgnoreCase);309#else310 return source.IndexOf(value, StringComparison.OrdinalIgnoreCase) >= 0;311#endif312 }313}314315```text316317### NuGet Package Folder Layout (Multi-TFM)318319```text320321MyCompany.Widgets.1.0.0.nupkg322 lib/323 netstandard2.0/324 MyCompany.Widgets.dll325 net8.0/326 MyCompany.Widgets.dll327 net9.0/328 MyCompany.Widgets.dll329330```text331332---333334## Symbol Packages and Deterministic Builds335336Symbol packages (`.snupkg`) enable source-level debugging for package consumers via the NuGet symbol server.337338### Enabling Symbol Packages339340```xml341342<PropertyGroup>343 <!-- Generate .snupkg alongside .nupkg -->344 <IncludeSymbols>true</IncludeSymbols>345 <SymbolPackageFormat>snupkg</SymbolPackageFormat>346347 <!-- Deterministic builds (required for reproducible packages) -->348 <Deterministic>true</Deterministic>349 <ContinuousIntegrationBuild Condition="'$(CI)' == 'true'">true</ContinuousIntegrationBuild>350351 <!-- Embed source in PDB for debugging without source server -->352 <EmbedUntrackedSources>true</EmbedUntrackedSources>353</PropertyGroup>354355```text356357The `snupkg` is pushed alongside the `nupkg` automatically when using `dotnet nuget push`:358359```bash360361# Push both .nupkg and .snupkg to nuget.org362dotnet nuget push "bin/Release/*.nupkg" \363 --source https://api.nuget.org/v3/index.json \364 --api-key "$NUGET_API_KEY"365366```json367368**SourceLink integration:** For source-level debugging with links to the actual source repository, configure SourceLink in your project. See [skill:dotnet-project-structure] for SourceLink setup -- do not duplicate that configuration here.369370### Embedded PDB Alternative371372For packages where a separate symbol package is undesirable:373374```xml375376<PropertyGroup>377 <DebugType>embedded</DebugType>378</PropertyGroup>379380```xml381382This embeds the PDB directly in the assembly DLL. The tradeoff is larger package size but simpler distribution.383384---385386## Package Signing387388NuGet supports author signing (proving package origin) and repository signing (proving it came from a specific feed).389390### Author Signing with a Certificate391392```bash393394# Sign a package with a PFX certificate395dotnet nuget sign "MyCompany.Widgets.1.0.0.nupkg" \396 --certificate-path ./signing-cert.pfx \397 --certificate-password "$CERT_PASSWORD" \398 --timestamper http://timestamp.digicert.com399400# Sign with a certificate from the certificate store (Windows)401402## Detailed Examples403404See [references/detailed-examples.md](references/detailed-examples.md) for complete code samples and advanced patterns.