Central Package Management in ConnectSoft Microservice Template¶
Purpose & Overview¶
Central Package Management (CPM) is a .NET feature that enables centralized version management for NuGet packages across a solution. In the ConnectSoft Microservice Template, CPM is a foundational standard that ensures version consistency, simplifies dependency management, and enforces governance across all projects.
Why Central Package Management?¶
Central Package Management offers several key benefits:
- Version Consistency: All projects use the exact same version of each dependency, eliminating version conflicts
- Simplified Maintenance: Update package versions in one place (
Directory.Packages.props) and all projects inherit the change - Easier Upgrades: Upgrade dependencies across the entire solution by updating a single file
- Governance: Centralized audit point for all dependencies and their versions
- Reduced Duplication: Eliminate repetitive version specifications across
.csprojfiles - Build Reproducibility: Ensures consistent builds across local development, CI/CD, and production environments
.NET Feature
Central Package Management was introduced in .NET SDK 5.0 and enhanced in .NET 6/⅞+. It uses Directory.Packages.props to centralize package version definitions.
Architecture Overview¶
CPM works by defining package versions centrally and referencing them by name in project files:
Solution Root
├── Directory.Packages.props (Central version definitions)
├── Directory.Build.props (Build-wide settings)
├── nuget.config (Package sources)
└── Projects/
├── Project1.csproj (References packages without versions)
├── Project2.csproj (References packages without versions)
└── ...
How It Works¶
- Central Definition: Package versions are defined in
Directory.Packages.propsat the solution root - Project References:
.csprojfiles reference packages by name only (no version) - Automatic Resolution: .NET SDK automatically resolves versions from
Directory.Packages.propsduring restore - Build Inheritance: All projects inherit build settings from
Directory.Build.props
Key Integration Points¶
| Component | Location | Responsibility |
|---|---|---|
| Directory.Packages.props | Solution root | Central package version definitions |
| Directory.Build.props | Solution root | Build-wide compiler and quality settings |
| nuget.config | Solution root | Package source configuration |
| .csproj files | Individual projects | Package references without versions |
Core Components¶
1. Directory.Packages.props¶
The central file that defines all NuGet package versions:
<Project>
<ItemGroup>
<!-- Testing -->
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.7.0" />
<PackageVersion Include="xunit" Version="2.4.2" />
<PackageVersion Include="Moq" Version="4.20.69" />
<!-- Logging -->
<PackageVersion Include="Serilog.AspNetCore" Version="6.0.1" />
<PackageVersion Include="Serilog.Sinks.Console" Version="4.1.0" />
<!-- Validation -->
<PackageVersion Include="FluentValidation" Version="11.5.0" />
<!-- gRPC -->
<PackageVersion Include="Grpc.AspNetCore" Version="2.55.0" />
<PackageVersion Include="Grpc.Tools" Version="2.57.0" />
<!-- ORM -->
<PackageVersion Include="NHibernate" Version="5.4.4" />
<PackageVersion Include="NHibernate.Caches.StackExchangeRedis" Version="5.4.0" />
<!-- Messaging -->
<PackageVersion Include="MassTransit" Version="8.1.2" />
<PackageVersion Include="MassTransit.RabbitMQ" Version="8.1.2" />
<!-- Feature Management -->
<PackageVersion Include="Microsoft.FeatureManagement" Version="3.0.0" />
<PackageVersion Include="Microsoft.FeatureManagement.AspNetCore" Version="3.0.0" />
<!-- AutoMapper -->
<PackageVersion Include="AutoMapper" Version="12.0.1" />
<PackageVersion Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.1" />
<!-- OpenTelemetry -->
<PackageVersion Include="OpenTelemetry.Exporter.Console" Version="1.6.0" />
<PackageVersion Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.6.0" />
</ItemGroup>
</Project>
Key Characteristics¶
- Exact Versions: Use specific version numbers (no wildcards like
*) - Organized by Purpose: Group related packages together (testing, logging, messaging, etc.)
- Solution-Wide: Applies to all projects in the solution
- Version Control: Should be checked into source control
2. Directory.Build.props¶
Defines build-wide settings that apply to all projects:
<Project>
<PropertyGroup>
<!-- Target Framework -->
<TargetFramework>net9.0</TargetFramework>
<!-- Language Version -->
<LangVersion>latest</LangVersion>
<!-- Nullable Reference Types -->
<Nullable>enable</Nullable>
<!-- Code Quality -->
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<WarningsAsErrors>CS1591</WarningsAsErrors> <!-- Missing XML documentation -->
<CodeAnalysisTreatWarningsAsErrors>true</CodeAnalysisTreatWarningsAsErrors>
<!-- Documentation -->
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591</NoWarn> <!-- Suppress missing XML doc warnings -->
<!-- Implicit Usings -->
<ImplicitUsings>enable</ImplicitUsings>
<!-- Deterministic Builds -->
<Deterministic>true</Deterministic>
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
<!-- Assembly Info -->
<Version>1.0.0</Version>
<Authors>ConnectSoft</Authors>
<Company>ConnectSoft</Company>
</PropertyGroup>
<!-- Common Package References (if needed) -->
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All" />
</ItemGroup>
</Project>
Key Settings¶
| Property | Purpose |
|---|---|
TargetFramework |
Ensures all projects target the same .NET version |
LangVersion |
Enables latest C# language features |
Nullable |
Enables nullable reference types across all projects |
TreatWarningsAsErrors |
Enforces code quality by treating warnings as errors |
GenerateDocumentationFile |
Automatically generates XML documentation |
Deterministic |
Ensures reproducible builds |
3. Project Files (.csproj)¶
Projects reference packages without version numbers:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Library</OutputType>
<RootNamespace>ConnectSoft.MicroserviceTemplate.Application</RootNamespace>
</PropertyGroup>
<ItemGroup>
<!-- No Version attribute - resolved from Directory.Packages.props -->
<PackageReference Include="FluentValidation" />
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" />
<PackageReference Include="Microsoft.FeatureManagement" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ConnectSoft.MicroserviceTemplate.DomainModel\ConnectSoft.MicroserviceTemplate.DomainModel.csproj" />
</ItemGroup>
</Project>
Benefits¶
- Cleaner Files: No version clutter in project files
- Automatic Resolution: Versions resolved from
Directory.Packages.props - Consistency: All projects use the same versions automatically
- Easier Maintenance: Update versions in one place
4. nuget.config¶
Configures package sources for the solution:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<!-- Public NuGet feed -->
<add key="NuGet" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
<!-- Private ConnectSoft feed (Azure DevOps) -->
<add key="ConnectSoftInternal" value="https://pkgs.dev.azure.com/connectsoft/_packaging/Internal/nuget/v3/index.json" />
<!-- Local development feed (optional) -->
<add key="Local" value="./packages" />
</packageSources>
<!-- Authentication for private feeds -->
<packageSourceCredentials>
<ConnectSoftInternal>
<add key="Username" value="%AZURE_DEVOPS_USERNAME%" />
<add key="ClearTextPassword" value="%AZURE_DEVOPS_PAT%" />
</ConnectSoftInternal>
</packageSourceCredentials>
<!-- Package source mapping (security) -->
<packageSourceMapping>
<packageSource key="NuGet">
<package pattern="*" />
</packageSource>
<packageSource key="ConnectSoftInternal">
<package pattern="ConnectSoft.*" />
</packageSource>
</packageSourceMapping>
</configuration>
Configuration¶
Enabling Central Package Management¶
CPM is automatically enabled when Directory.Packages.props exists in the solution root. No additional configuration is required.
Project File Structure¶
Solution Root/
├── Directory.Packages.props
├── Directory.Build.props
├── nuget.config
├── ConnectSoft.MicroserviceTemplate.sln
└── Projects/
├── ConnectSoft.MicroserviceTemplate.DomainModel/
│ └── ConnectSoft.MicroserviceTemplate.DomainModel.csproj
├── ConnectSoft.MicroserviceTemplate.ApplicationModel/
│ └── ConnectSoft.MicroserviceTemplate.ApplicationModel.csproj
└── ...
Adding a New Package¶
- Add to Directory.Packages.props:
- Reference in project file:
- Restore packages:
Conditional Package Management¶
Target Framework Conditions¶
Different versions for different target frameworks:
<ItemGroup>
<PackageVersion Include="System.Text.Json" Version="8.0.0"
Condition="'$(TargetFramework)' == 'net8.0'" />
<PackageVersion Include="System.Text.Json" Version="7.0.0"
Condition="'$(TargetFramework)' == 'net7.0'" />
</ItemGroup>
Feature Flags¶
Conditional packages based on build properties:
<Project>
<PropertyGroup>
<UseMassTransit>true</UseMassTransit>
<UseNServiceBus>false</UseNServiceBus>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="MassTransit" Version="8.1.2"
Condition="'$(UseMassTransit)' == 'true'" />
<PackageVersion Include="NServiceBus" Version="8.1.0"
Condition="'$(UseNServiceBus)' == 'true'" />
</ItemGroup>
</Project>
In project files:
<ItemGroup>
<PackageReference Include="MassTransit"
Condition="'$(UseMassTransit)' == 'true'" />
</ItemGroup>
Environment-Based Conditions¶
<ItemGroup>
<PackageVersion Include="Azure.Storage.Blobs" Version="12.17.0"
Condition="'$(Configuration)' == 'Release'" />
</ItemGroup>
Best Practices¶
Do's¶
- Always use exact versions
- Use specific version numbers:
Version="1.2.3" -
Avoid wildcards:
Version="1.*"orVersion="*" -
Organize packages by purpose
- Group related packages together
- Add comments for clarity
-
Example: Testing, Logging, Messaging, ORM sections
-
Keep Directory.Packages.props in source control
- Essential for reproducible builds
-
Enables team-wide version consistency
-
Use Directory.Build.props for common settings
- Avoid duplicating build settings in each project
-
Centralize compiler and quality settings
-
Configure package source mapping
- Restrict which packages come from which feeds
-
Enhances security and prevents supply chain attacks
-
Group related packages
-
Document complex versions
-
Use conditional packages wisely
- Only use conditions when necessary
- Prefer explicit feature flags over environment-based conditions
- Document why conditions are needed
Don'ts¶
-
Don't specify versions in .csproj files
-
Don't use wildcard versions
-
Don't duplicate package definitions
- Each package should appear once in
Directory.Packages.props -
Use conditions if different versions are needed for different scenarios
-
Don't forget to update both files
- When adding a package, update both
Directory.Packages.propsand the project's.csproj -
Missing either will cause build failures
-
Don't bypass CPM with local overrides
- Avoid using
Versionattribute in project files -
This breaks version consistency across the solution
-
Don't ignore build warnings
Directory.Build.propssetsTreatWarningsAsErrors=truefor a reason- Fix warnings before checking in code
CI/CD Integration¶
Azure DevOps Pipeline¶
# azure-pipelines.yml
trigger:
branches:
include:
- main
- develop
pool:
vmImage: 'windows-latest'
variables:
- name: DotNet.SDK.Version
value: '9.0.x'
steps:
- task: UseDotNet@2
inputs:
packageType: 'sdk'
version: '$(DotNet.SDK.Version)'
- task: DotNetCoreCLI@2
displayName: 'Restore packages'
inputs:
command: 'restore'
projects: '**/*.sln'
arguments: '--locked-mode'
- task: DotNetCoreCLI@2
displayName: 'Build solution'
inputs:
command: 'build'
projects: '**/*.sln'
arguments: '--configuration Release --no-restore'
- task: DotNetCoreCLI@2
displayName: 'Run tests'
inputs:
command: 'test'
projects: '**/*.Tests/**/*.csproj'
arguments: '--configuration Release --no-build --verbosity normal'
Key CI/CD Practices¶
- Use
--locked-modeduring restore - Ensures exact versions from
Directory.Packages.props -
Prevents version drift
-
Pin SDK version
- Ensure consistent .NET SDK version across all builds
-
Prevents build inconsistencies
-
Restore at solution level
- Ensures CPM applies to all projects
-
More efficient than per-project restore
-
Check for outdated packages
Version Management Workflow¶
Updating a Package Version¶
-
Update Directory.Packages.props:
Change to: -
Restore packages:
-
Build and test:
-
Commit changes:
- Commit both
Directory.Packages.propsand any project files that needed updates
Bulk Package Updates¶
For updating multiple packages:
- Use tools like
dotnet-outdatedor Renovate bot - Review breaking changes for each package
- Test thoroughly before committing
- Update in logical groups (e.g., all logging packages together)
Package Source Mapping¶
Package source mapping enhances security by restricting which packages can be downloaded from which sources:
<packageSourceMapping>
<!-- Public NuGet feed - all packages -->
<packageSource key="NuGet">
<package pattern="*" />
</packageSource>
<!-- Private feed - only ConnectSoft packages -->
<packageSource key="ConnectSoftInternal">
<package pattern="ConnectSoft.*" />
</packageSource>
</packageSourceMapping>
Benefits¶
- Security: Prevents supply chain attacks
- Compliance: Ensures internal packages only come from trusted sources
- Performance: Faster restores by directing packages to correct feeds
Troubleshooting¶
Common Issues¶
Issue: Package version not found¶
Symptom: NU1101: Unable to find package 'PackageName'. No packages exist with this id in source(s)
Solution:
- Verify package is defined in Directory.Packages.props
- Check nuget.config has correct package sources
- Ensure package source credentials are configured
Issue: Version conflicts¶
Symptom: NU1107: Version conflict detected for 'PackageName'
Solution:
- Check for duplicate PackageVersion entries in Directory.Packages.props
- Verify no projects specify Version attribute
- Review transitive dependencies that might conflict
Issue: Build warnings about versions¶
Symptom: Warning about package version mismatch
Solution:
- Ensure all projects use <PackageReference> without Version attribute
- Verify Directory.Packages.props is in solution root
- Run dotnet restore --force to clear cache
Issue: CI build fails but local works¶
Symptom: Build succeeds locally but fails in CI
Solution:
- Verify Directory.Packages.props is checked into source control
- Check CI has access to all package sources in nuget.config
- Ensure CI uses same SDK version as local
Migration to CPM¶
Migrating Existing Projects¶
-
Create Directory.Packages.props:
-
Remove versions from .csproj files:
-
Test build:
-
Verify all projects build successfully
Advanced Scenarios¶
Private Package Feeds¶
For internal packages hosted in Azure DevOps Artifacts:
<!-- Directory.Packages.props -->
<ItemGroup>
<PackageVersion Include="ConnectSoft.Common" Version="1.0.0" />
<PackageVersion Include="ConnectSoft.Extensions" Version="2.1.0" />
</ItemGroup>
<!-- nuget.config -->
<packageSources>
<add key="ConnectSoftInternal"
value="https://pkgs.dev.azure.com/connectsoft/_packaging/Internal/nuget/v3/index.json" />
</packageSources>
<packageSourceMapping>
<packageSource key="ConnectSoftInternal">
<package pattern="ConnectSoft.*" />
</packageSource>
</packageSourceMapping>
Multi-Target Framework (MTF)¶
When projects target multiple frameworks:
<Project>
<PropertyGroup>
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<!-- Framework-specific packages -->
<PackageVersion Include="System.Text.Json" Version="8.0.0"
Condition="'$(TargetFramework)' == 'net8.0'" />
<PackageVersion Include="System.Text.Json" Version="9.0.0"
Condition="'$(TargetFramework)' == 'net9.0'" />
</ItemGroup>
</Project>
Package Version Ranges (Advanced)¶
While CPM typically uses exact versions, you can use ranges with caution:
Version Ranges
Use version ranges sparingly. Exact versions provide better reproducibility and predictability.
Summary¶
Central Package Management in the ConnectSoft Microservice Template provides:
- ✅ Version Consistency: Single source of truth for all package versions
- ✅ Simplified Maintenance: Update versions in one file, apply everywhere
- ✅ Build Reproducibility: Consistent builds across all environments
- ✅ Governance: Centralized audit point for dependencies
- ✅ Clean Project Files: No version clutter in individual projects
- ✅ CI/CD Integration: Seamless integration with Azure DevOps pipelines
- ✅ Security: Package source mapping for supply chain security
By following these patterns and best practices, CPM becomes a powerful foundation for managing dependencies across large microservice solutions, ensuring consistency, maintainability, and reliability.