๐ ๏ธ Runbook: ConnectSoft Library Template¶
This runbook documents how to use and manage the CI/CD pipeline and local development flow for projects generated with the ConnectSoft.LibraryTemplate.
๐งช Local Development¶
Prerequisites¶
- .NET SDK 8.0
- .NET SDK 9.0 (for multi-targeting)
- Visual Studio 2022 or JetBrains Rider
- Optional:
mssqllocaldb(for local testing)
Run Tests¶
๐ CI/CD Overview¶
The Azure DevOps pipeline supports:
- โ
Multi-targeting (
net8.0;net9.0) - โ
Code coverage enforcement (
Cobertura) - โ Build quality checks (threshold = 70%)
- โ NuGet package creation and publishing to Azure Artifacts
๐ ๏ธ Pipeline Structure¶
Complete breakdown of all pipeline steps:
| Step | Task | Purpose | Details |
|---|---|---|---|
| 1. Install .NET SDK 8 | UseDotNet@2 |
Install .NET 8 SDK | Version: 8.x, includes preview versions |
| 2. Install .NET SDK 9 | UseDotNet@2 |
Install .NET 9 SDK | Version: 9.x, includes preview versions |
| 3. NuGet Authenticate | NuGetAuthenticate@1 |
Authenticate with feeds | Authenticates with Azure Artifacts and other NuGet feeds |
| 4. Restore Packages | DotNetCoreCLI@2 |
Restore NuGet packages | Restores packages from restoreVstsFeed |
| 5. Check Deprecated Packages | DotNetCoreCLI@2 |
Validate packages | Runs dotnet list package --deprecated |
| 6. Check Vulnerable Packages | DotNetCoreCLI@2 |
Security check | Runs dotnet list package --vulnerable --include-transitive |
| 7. Build Solution | DotNetCoreCLI@2 |
Compile solution | Builds in Release configuration |
| 8. Start Local SQL DB | powershell |
Optional SQL LocalDB | Starts mssqllocaldb if useLocalSqlDb=true |
| 9. Run Tests | DotNetCoreCLI@2 |
Execute tests | Runs all test projects with code coverage collection |
| 10. Publish Coverage | PublishCodeCoverageResults@2 |
Report coverage | Publishes Cobertura XML to Azure DevOps |
| 11. Build Quality Check | BuildQualityChecks@10 |
Enforce coverage | Validates coverage threshold (master branch only) |
| 12. Pack NuGet | DotNetCoreCLI@2 |
Create package | Creates .nupkg file (master branch only) |
| 13. Push Package | DotNetCoreCLI@2 |
Publish package | Publishes to Azure Artifacts (master branch only) |
Detailed Step Explanations¶
Step 1-2: Install .NET SDKs¶
- task: UseDotNet@2
displayName: 'Install .NET Core SDK'
inputs:
version: '8.x'
includePreviewVersions: true
Purpose: Ensures both .NET 8 and .NET 9 SDKs are available for multi-targeting builds.
Why Both: The library targets both net8.0 and net9.0, so both SDKs are required.
Step 3: NuGet Authenticate¶
Purpose: Authenticates with Azure Artifacts feeds to restore and publish packages.
Requirements: Service connection must be configured in Azure DevOps.
Step 4: Restore Packages¶
- task: DotNetCoreCLI@2
displayName: 'dotnet restore packages for entire solution'
inputs:
command: 'restore'
vstsFeed: $(restoreVstsFeed)
projects: $(solution)
Purpose: Restores all NuGet package dependencies.
Feed Configuration: Uses restoreVstsFeed variable for package source.
Step 5-6: Package Validation¶
- task: DotNetCoreCLI@2
displayName: Check deprecated packages
inputs:
command: 'custom'
custom: 'list'
arguments: 'package --deprecated'
- task: DotNetCoreCLI@2
displayName: Check packages vulnerabilities
inputs:
command: 'custom'
custom: 'list'
arguments: 'package --vulnerable --include-transitive'
Purpose:
- Deprecated Check: Identifies packages that are deprecated
- Vulnerability Check: Identifies packages with known security vulnerabilities
Note: These steps don't fail the build but provide warnings.
Step 7: Build Solution¶
- task: DotNetCoreCLI@2
displayName: 'dotnet build entire solution'
inputs:
command: 'build'
projects: $(solution)
arguments: --configuration $(buildConfiguration)
Purpose: Compiles the solution in Release configuration.
Output: Builds both net8.0 and net9.0 target frameworks.
Step 8: Start Local SQL DB (Optional)¶
- powershell: |
if($env:useLocalSqlDb -eq $true) {
sqllocaldb start mssqllocaldb
}
displayName: 'Install Local SQL Db using power shell'
env:
useLocalSqlDb: $(useLocalSqlDb)
Purpose: Starts SQL LocalDB for integration tests that require a database.
When to Use: Set useLocalSqlDb=true if your tests need a database.
Step 9: Run Tests¶
- task: DotNetCoreCLI@2
displayName: 'dotnet test entire solution'
inputs:
command: test
projects: |
**\*test*.csproj
!**\obj\**
arguments: '--collect:"XPlat Code Coverage" --settings ConnectSoft.LibraryTemplate.runsettings'
publishTestResults: true
Purpose:
- Runs all test projects
- Collects code coverage using coverlet
- Publishes test results to Azure DevOps
Coverage Format: Cobertura XML
Step 10: Publish Coverage¶
- task: PublishCodeCoverageResults@2
displayName: 'Publish code coverage report'
inputs:
codeCoverageTool: 'Cobertura'
summaryFileLocation: '$(Agent.TempDirectory)/**/coverage.cobertura.xml'
Purpose: Publishes code coverage results to Azure DevOps for visualization.
View Coverage: Available in Azure DevOps under the "Code Coverage" tab.
Step 11: Build Quality Check¶
- task: mspremier.BuildQualityChecks.QualityChecks-task.BuildQualityChecks@10
displayName: 'Check build quality - with code coverage $(codeCoverageThreshold)'
inputs:
checkCoverage: true
coverageFailOption: fixed
coverageType: lines
coverageThreshold: '$(codeCoverageThreshold)'
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/master'))
Purpose: Enforces minimum code coverage threshold.
When: Only runs on master branch.
Failure: Build fails if coverage is below threshold.
Default: 0 (disabled). Set to 70 or higher for enforcement.
Step 12: Pack NuGet¶
- task: DotNetCoreCLI@2
displayName: 'dotnet pack - create nuget package'
inputs:
command: pack
packagesToPack: '**\$(Build.DefinitionName).csproj;!**\*Tests.csproj'
packDirectory: '$(build.artifactStagingDirectory)'
nobuild: true
versioningScheme: byBuildNumber
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/master'))
Purpose: Creates NuGet package (.nupkg file).
When: Only runs on master branch.
Versioning: Uses build number for package version.
Output: Package saved to artifact staging directory.
Step 13: Push Package¶
- task: DotNetCoreCLI@2
displayName: 'dotnet push package to azure devops artifacts'
inputs:
command: push
publishVstsFeed: $(publishVstsFeed)
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/master'))
Purpose: Publishes NuGet package to Azure Artifacts feed.
When: Only runs on master branch.
Feed: Uses publishVstsFeed variable.
๐ ๏ธ Pipeline Variables¶
Template Parameters¶
These variables are set during template instantiation:
| Variable | Type | Default | Description |
|---|---|---|---|
buildDefinitionNumber |
string | "" |
Azure DevOps build definition number for CI/CD integration. Replaces placeholder in azure-pipelines.yml and README badges. |
Pipeline Variables¶
These variables are defined in azure-pipelines.yml and can be overridden:
| Variable | Type | Default | Description |
|---|---|---|---|
majorMinorVersion |
string | 1.0 |
Major.minor version for semantic versioning |
semanticVersion |
counter | 0 |
Auto-incremented semantic version counter |
solution |
string | **/*.slnx |
Solution file pattern (prefers .slnx, falls back to .sln) |
buildPlatform |
string | Any CPU |
Build platform |
buildConfiguration |
string | Release |
Build configuration (Release/Debug) |
artifactName |
string | drop |
Artifact name for publishing |
publishVstsFeed |
string | Feed GUID | Azure Artifacts feed for publishing packages |
restoreVstsFeed |
string | Feed GUID | Azure Artifacts feed for restoring packages |
codeCoverageThreshold |
number | 0 |
Minimum code coverage percentage (0 = disabled, 70+ recommended) |
useLocalSqlDb |
bool | false |
Enable SQL LocalDB for integration tests |
Overriding Variables¶
In Azure DevOps UI: 1. Go to Pipeline โ Edit 1. Click "Variables" tab 1. Add or modify variables
In YAML (for pipeline-specific overrides):
Via Variable Groups:
๐งช Test Coverage Validation¶
Coverage Threshold¶
Fail threshold is defined by:
Default: 0 (disabled)
Recommended: 70 or higher
Setting Coverage Threshold¶
Option 1: Pipeline Variables (UI)
1. Go to Pipeline โ Edit
1. Click "Variables" tab
1. Add variable: codeCoverageThreshold = 70
Option 2: YAML File
Option 3: Variable Group
Coverage Collection¶
Coverage is collected using coverlet.collector:
Format: Cobertura XML
Location: $(Agent.TempDirectory)/**/coverage.cobertura.xml
Viewing Coverage¶
- Go to Azure DevOps โ Pipelines โ Your Pipeline
- Select a build run
- Click "Code Coverage" tab
- View coverage by file, class, or method
Coverage Best Practices¶
- Set Realistic Thresholds: Start with 70%, increase gradually
- Focus on Critical Code: Prioritize business logic coverage
- Exclude Generated Code: Use
[ExcludeFromCodeCoverage]attribute - Regular Reviews: Review coverage reports regularly
- Integration Tests: Include integration tests for critical paths
๐ซ Troubleshooting¶
Build Issues¶
NETSDK1045: net9.0 not supported¶
Error: NETSDK1045: The current .NET SDK does not support targeting .NET 9.0
Cause: .NET 9 SDK not installed in pipeline
Fix: Ensure UseDotNet@2 task installs .NET 9 SDK:
- task: UseDotNet@2
displayName: 'Install .NET Core SDK 9'
inputs:
version: '9.x'
includePreviewVersions: true
No artifacts published¶
Error: NuGet package not published to feed
Cause: Build not on master branch
Fix:
- Merge to
masterbranch, or - Remove branch condition from pack/push steps (not recommended for production)
Restore failed¶
Error: NU1101: Unable to find package
Causes:
- Azure feed connection not configured
- Service connection expired
- Feed GUID incorrect
Fixes:
- Check
restoreVstsFeedvariable is correct - Verify service connection in Azure DevOps
- Check NuGet authentication task is present
- Verify feed permissions
Coverage check failed¶
Error: Build quality check failed: Code coverage is below threshold
Cause: Code coverage below configured threshold
Fixes:
- Add more tests: Increase test coverage
- Lower threshold temporarily: Set
codeCoverageThresholdto current coverage - Exclude generated code: Use
[ExcludeFromCodeCoverage]attribute - Review coverage report: Identify untested code
Cannot push package¶
Error: NU1101: Unable to load the service index or 403 Forbidden
Causes:
- Service connection not configured
- Feed GUID incorrect
- Insufficient permissions
Fixes:
- Verify
publishVstsFeedvariable is correct - Check service connection has "Artifacts" permissions
- Verify feed exists and is accessible
- Check NuGet authentication task
Test Issues¶
Tests not running¶
Cause: Test projects not found or not configured correctly
Fix: Verify test project structure:
- Test project name contains "test" (case-insensitive)
- Test project references library project
- Test project has
IsTestProject=truein .csproj
Coverage not collected¶
Cause: coverlet.collector not configured
Fix: Verify test project includes:
<PackageReference Include="coverlet.collector">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
Package Issues¶
Package version incorrect¶
Cause: Build number format doesn't match NuGet version format
Fix: Ensure build number follows semantic versioning:
Package metadata missing¶
Cause: .csproj missing package metadata
Fix: Add to .csproj:
<PropertyGroup>
<PackageId>YourLibraryName</PackageId>
<Version>1.0.0</Version>
<Authors>Your Name</Authors>
<Description>Your library description</Description>
</PropertyGroup>
Performance Issues¶
Build too slow¶
Causes:
- Large solution
- Too many packages
- Inefficient restore
Fixes:
- Use .slnx format (faster than .sln)
- Enable package caching
- Use parallel restore
- Review package dependencies
Test execution slow¶
Causes:
- Too many tests
- Integration tests without mocking
- Database operations
Fixes:
- Use test parallelization
- Mock external dependencies
- Use in-memory databases for tests
- Separate unit and integration tests
๐งช Local Development Workflow¶
Prerequisites¶
- .NET SDK 8.0 and 9.0 installed
- Visual Studio 2022 or JetBrains Rider (optional)
- Python 3.x with pip (for MkDocs, optional)
Local Build¶
# Restore packages
dotnet restore
# Build solution
dotnet build -c Release
# Run tests
dotnet test --collect:"XPlat Code Coverage"
# Pack NuGet package
dotnet pack -c Release
Using .slnx Format¶
# Build using .slnx (faster)
dotnet build YourLibraryName.slnx -c Release
# Test using .slnx
dotnet test YourLibraryName.slnx --collect:"XPlat Code Coverage"
Code Coverage Locally¶
View Coverage Report:
-
Install ReportGenerator:
-
Generate HTML report:
-
Open
coverage/index.htmlin browser
Testing Strategies¶
Unit Tests¶
Location: tests/YourLibraryName.UnitTests/
Best Practices:
- Test one thing per test method
- Use descriptive test names
- Arrange-Act-Assert pattern
- Mock external dependencies
Example:
[TestClass]
public class MyServiceTests
{
[TestMethod]
public void DoWork_WithValidInput_ShouldReturnSuccess()
{
// Arrange
var service = new MyService();
// Act
var result = service.DoWork("input");
// Assert
Assert.IsTrue(result.IsSuccess);
}
}
Integration Tests¶
When to Use:
- Testing with real dependencies
- Database operations
- External service calls
Setup:
- Use SQL LocalDB or in-memory database
- Mock external services
- Clean up after tests
Test Coverage Goals¶
- Minimum: 70% line coverage
- Target: 80%+ line coverage
- Critical Code: 100% coverage
Debugging Pipeline Issues¶
Enable Verbose Logging¶
Add to pipeline YAML:
Test Pipeline Steps Locally¶
-
Restore:
-
Check Packages:
-
Build:
-
Test:
-
Pack:
Pre-Commit Checks¶
Run these before committing:
# Format code
dotnet format
# Build
dotnet build
# Test
dotnet test
# Check for issues
dotnet list package --deprecated
dotnet list package --vulnerable
๐ฆ Package Publishing¶
Automatic Publishing¶
Packages are automatically published on successful master branch builds.
Process:
- Code merged to
masterbranch - Pipeline automatically triggers
- Build, test, and pack steps execute
- Package published to Azure Artifacts feed
Manual Publishing¶
Option 1: Trigger Pipeline Manually
- Go to Azure DevOps โ Pipelines
- Select your pipeline
- Click "Run pipeline"
- Select
masterbranch - Click "Run"
Option 2: Local Publishing
# Pack locally
dotnet pack -c Release
# Push to feed
dotnet nuget push bin/Release/YourLibraryName.*.nupkg \
--source "YourFeedName" \
--api-key az
Package Versioning¶
Format: Semantic versioning (Major.Minor.Patch)
Example: 1.0.0, 1.0.1, 1.1.0
Build Number: Used for package version
Result: 1.0.0, 1.0.1, 1.0.2, etc.
Feed Configuration¶
Feed Location: Defined in publishVstsFeed variable
Format: {organizationId}/{projectId}/{feedId}
Example: e4c108b4-7989-4d22-93d6-391b77a39552/1889adca-ccb6-4ece-aa22-cad1ae4a35f3
Consuming Published Packages¶
Add Feed to nuget.config:
<packageSources>
<add key="ConnectSoft" value="https://pkgs.dev.azure.com/{org}/{project}/_packaging/{feed}/nuget/v3/index.json" />
</packageSources>
Install Package:
๐ Additional Resources¶
- Azure DevOps Pipelines Docs
- NuGet CLI Reference
- Code Coverage Best Practices
- Build Quality Checks Extension