Skip to content

Example: ASP.NET Core API client package packaging

package.md records how a Core API client package is built, what it contains, and where it is published. For an SDK-style project the .csproj is the packaging manifest, so the page reads identity values from the project and says what supplies any value the project does not set. Doc gen produces one per client at docs/clients/<client-slug>/package.md.

What reviewers should check

  • The project SDK is Microsoft.NET.Sdk. A Microsoft.NET.Sdk.Web project is an API host, never a client package.
  • ## Identity names the MSBuild property that supplied each value. A value the project does not set, typically Version, says what supplies it instead (the pipeline build number format) and is never printed as if it were declared.
  • The page uses .NET Core vocabulary only: <PackageReference>, <ProjectReference>, Program.cs. No packages.config, no AssemblyInfo.cs, and no nuspec is treated as the manifest. A nuspec beside an SDK project is a finding unless the project sets <NuspecFile>.
  • ## Dependencies leads with SAIF.NetCore.ServiceExtensions and SAIF.Http.ResponseValidator, then names any package-specific dependency. A dependency beyond the SAIF HTTP and service packages is carried into every consumer, so it is a real finding, not filler.
  • ## Bundled project references records the PrivateAssets metadata exactly. A reference marked PrivateAssets="All" is bundled into lib/<tfm> and not emitted as a package dependency; the unmarked case is raised in Tech debt.
  • ## Shipped content lists only files the project actually produces. Rows for symbols or a contracts assembly appear only when the project's packing properties and targets declare them.
  • The build AppType of CoreApi describes the repository, not the package. ## Feed and the publish trigger are evidenced by a pipeline or recorded as "Not determined from source". An inferred feed name is a defect.

Example

The document below is a complete, correct client-package.md for a fictional ASP.NET Core API client package.

View rendered example — the same document as a real page, so you can compare rendering as well as source.

---
title: Package
summary: SDK project manifest, version, dependencies, bundled project references, shipped content, feed, and build pipeline.
status: draft
owner: Premium Systems Team
last_reviewed: 2026-10-21
---

# Package

`SAIF.Premium.Api.Client` is an SDK-style `net8.0` class library that
`dotnet pack` turns into a NuGet package straight from its `.csproj`; there is no
nuspec. The project does not set a version: the `PremiumApi_Client` pipeline
supplies one from its build number, in the form `8.2026.1021.1`, and branch
builds carry a branch-name suffix. The package ships the compiled client, its
symbols, and the `SAIF.Premium.Api.Contracts` assembly bundled from a project
reference. The feed and the publish trigger are not stated in the repository.

## Identity

| Field | Value | Supplied by |
| --- | --- | --- |
| Package id | `SAIF.Premium.Api.Client` | `<PackageId>` in the project |
| Version | `8.2026.1021.1` (example: first build on 21 October 2026) | Pipeline build number (`name:` format `$(MajorVersion).$(Year:yyyy).$(Month)$(DayOfMonth).$(rev:r)`); no `<Version>` or `<PackageVersion>` in the project |
| Target framework(s) | `net8.0` | `<TargetFramework>` |
| Authors | `SAIF Corporation` | `<Authors>` |
| Description | Typed .NET 8 client for the SAIF Premium API (premium, class-code and rate-table endpoints). | `<Description>` |
| Packable | true | `<IsPackable>` |
| Pack on build | Not set | `<GeneratePackageOnBuild>` is absent |
| Project file | `src/SAIF.Premium.Api.Client/SAIF.Premium.Api.Client.csproj` | - |

## Package manifest (SDK project)

```xml
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <PackageId>SAIF.Premium.Api.Client</PackageId>
    <Authors>SAIF Corporation</Authors>
    <Description>Typed .NET 8 client for the SAIF Premium API (premium, class-code and rate-table endpoints).</Description>
    <IsPackable>true</IsPackable>
    <TargetsForTfmSpecificBuildOutput>$(TargetsForTfmSpecificBuildOutput);CopyProjectReferencesToPackage</TargetsForTfmSpecificBuildOutput>
    <AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="SAIF.NetCore.ServiceExtensions" Version="8.2.1" />
    <PackageReference Include="SAIF.Http.ResponseValidator" Version="8.0.3" />
    <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1" />
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\SAIF.Premium.Api.Contracts\SAIF.Premium.Api.Contracts.csproj" PrivateAssets="All" />
  </ItemGroup>

  <Target Name="CopyProjectReferencesToPackage" DependsOnTargets="ResolveReferences">
    <ItemGroup>
      <BuildOutputInPackage Include="@(ReferenceCopyLocalPaths->WithMetadataValue('ReferenceSourceTarget', 'ProjectReference'))" />
    </ItemGroup>
  </Target>
</Project>
```

| Manifest fact | Value | Source |
| --- | --- | --- |
| Project SDK | `Microsoft.NET.Sdk` | `src/SAIF.Premium.Api.Client/SAIF.Premium.Api.Client.csproj:1` |
| nuspec present | No | `src/SAIF.Premium.Api.Client/` and `scripts/` searched |
| Custom packing target | `CopyProjectReferencesToPackage` | `src/SAIF.Premium.Api.Client/SAIF.Premium.Api.Client.csproj:24` |

## Dependencies

| Dependency | Version | Purpose |
| --- | --- | --- |
| `SAIF.NetCore.ServiceExtensions` | 8.2.1 | `IHttpJsonClient` and `IEnvironmentClient` service surface |
| `SAIF.Http.ResponseValidator` | 8.0.3 | `HttpResponseValidator.ValidateHttpResponse` |
| `Microsoft.Extensions.DependencyInjection.Abstractions` | 8.0.1 | `IServiceCollection` and `AddTransient` for the `AddPremiumClient()` registration extension |

`Microsoft.Extensions.DependencyInjection.Abstractions` is the one dependency
beyond the SAIF HTTP and service packages. It is carried into every consumer, but
every consumer is a .NET 8 host that already references it, so it adds no
version constraint in practice.

See [Dependencies](dependencies.md) for the full dependency landscape including transitive and build-time references.

## Bundled project references

| Project reference | Metadata | Bundled into `lib/<tfm>` | Emitted as package dependency | Source |
| --- | --- | --- | --- | --- |
| `..\SAIF.Premium.Api.Contracts\SAIF.Premium.Api.Contracts.csproj` | `PrivateAssets="All"` | Yes (`.dll` and `.pdb`) | No | `src/SAIF.Premium.Api.Client/SAIF.Premium.Api.Client.csproj:19` |

`PrivateAssets="All"` keeps `dotnet pack` from emitting a dependency on
`SAIF.Premium.Api.Contracts`, which no feed publishes, and the
`CopyProjectReferencesToPackage` target copies its output into `lib/net8.0`
instead. Consumers therefore receive `PremiumSummary`, `PremiumEstimate` and the
other contracts inside this package and cannot take a different version of them.

## Shipped content

| Source | Target | Content |
| --- | --- | --- |
| `bin/Release/net8.0/SAIF.Premium.Api.Client.dll` | `lib/net8.0/SAIF.Premium.Api.Client.dll` | Compiled client |
| `bin/Release/net8.0/SAIF.Premium.Api.Client.pdb` | `lib/net8.0/SAIF.Premium.Api.Client.pdb` | Debug symbols (via `AllowedOutputExtensionsInPackageBuildOutputFolder`) |
| `SAIF.Premium.Api.Contracts.dll` | `lib/net8.0/SAIF.Premium.Api.Contracts.dll` | Bundled contracts assembly (via `BuildOutputInPackage`) |
| `SAIF.Premium.Api.Contracts.pdb` | `lib/net8.0/SAIF.Premium.Api.Contracts.pdb` | Bundled contracts symbols |

No source files, content files or build assets are packed.

## Build and release pipeline

| Attribute | Value |
| --- | --- |
| Pipeline | `pipelines/PremiumApi_Client.yml` |
| Build variables | `pipelines/build-vars.yml` (`AppType` = `CoreApi`, `AppName` = `PremiumApi`, `MajorVersion` = `8`) |
| Build number format | `$(MajorVersion).$(Year:yyyy).$(Month)$(DayOfMonth).$(rev:r)` |
| Shared template | `/Projects/Framework/build-and-release-nuget.yml@templates` |
| Template parameters | `nugetTargetFolder` = `$(appName)`, `nugetPackageSuffix` = `branchName` |
| Project packed | Not determined from source |

The shared template selects the project to pack; no template parameter names it.
`AppType` is `CoreApi` because the repository's primary application is the Premium
API, not because this package is an API client. `nugetPackageSuffix: branchName`
means a branch build is packed as a pre-release with the branch name as its
suffix, so consumers see one package version per branch build.

## Feed

| Field | Value |
| --- | --- |
| Feed | Not determined from source |
| Publish trigger | Not determined from source |

## Related documentation
- [Client index](index.md)
- [Overview](overview.md)
- [Architecture](architecture.md)
- [Client reference](reference.md)
- [Endpoint map](endpoint-map.md)
- [Consumption](consumption.md)
- [Contracts](contracts.md)
- [Dependencies](dependencies.md)
- [Business rules](business-rules.md)
- [Tech debt](tech-debt.md)

## Sources
- `src/SAIF.Premium.Api.Client/SAIF.Premium.Api.Client.csproj`
- `pipelines/PremiumApi_Client.yml`
- `pipelines/build-vars.yml`