Skip to content

๐Ÿ› ๏ธ 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

dotnet test --collect:"XPlat Code Coverage"

๐Ÿš€ 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

- task: NuGetAuthenticate@1
  displayName: '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):

variables:
  codeCoverageThreshold: 70
  useLocalSqlDb: true

Via Variable Groups:

variables:
- group: 'LibraryTemplateVariables'

๐Ÿงช Test Coverage Validation

Coverage Threshold

Fail threshold is defined by:

coverageThreshold: '$(codeCoverageThreshold)'

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

variables:
  codeCoverageThreshold: 70

Option 3: Variable Group

variables:
- group: 'LibraryTemplateVariables'
  # codeCoverageThreshold defined in variable group

Coverage Collection

Coverage is collected using coverlet.collector:

arguments: '--collect:"XPlat Code Coverage" --settings ConnectSoft.LibraryTemplate.runsettings'

Format: Cobertura XML
Location: $(Agent.TempDirectory)/**/coverage.cobertura.xml

Viewing Coverage

  1. Go to Azure DevOps โ†’ Pipelines โ†’ Your Pipeline
  2. Select a build run
  3. Click "Code Coverage" tab
  4. View coverage by file, class, or method

Coverage Best Practices

  1. Set Realistic Thresholds: Start with 70%, increase gradually
  2. Focus on Critical Code: Prioritize business logic coverage
  3. Exclude Generated Code: Use [ExcludeFromCodeCoverage] attribute
  4. Regular Reviews: Review coverage reports regularly
  5. 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 master branch, 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:

  1. Check restoreVstsFeed variable is correct
  2. Verify service connection in Azure DevOps
  3. Check NuGet authentication task is present
  4. Verify feed permissions

Coverage check failed

Error: Build quality check failed: Code coverage is below threshold

Cause: Code coverage below configured threshold

Fixes:

  1. Add more tests: Increase test coverage
  2. Lower threshold temporarily: Set codeCoverageThreshold to current coverage
  3. Exclude generated code: Use [ExcludeFromCodeCoverage] attribute
  4. 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:

  1. Verify publishVstsFeed variable is correct
  2. Check service connection has "Artifacts" permissions
  3. Verify feed exists and is accessible
  4. 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=true in .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:

1.0.0
1.0.1
1.1.0

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:

  1. Use .slnx format (faster than .sln)
  2. Enable package caching
  3. Use parallel restore
  4. Review package dependencies

Test execution slow

Causes:

  • Too many tests
  • Integration tests without mocking
  • Database operations

Fixes:

  1. Use test parallelization
  2. Mock external dependencies
  3. Use in-memory databases for tests
  4. 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:

  1. Install ReportGenerator:

    dotnet tool install -g dotnet-reportgenerator-globaltool
    

  2. Generate HTML report:

    dotnet test --collect:"XPlat Code Coverage"
    reportgenerator -reports:"**/coverage.cobertura.xml" -targetdir:"coverage" -reporttypes:Html
    

  3. Open coverage/index.html in 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:

variables:
  system.debug: true

Test Pipeline Steps Locally

  1. Restore:

    dotnet restore
    

  2. Check Packages:

    dotnet list package --deprecated
    dotnet list package --vulnerable --include-transitive
    

  3. Build:

    dotnet build -c Release
    

  4. Test:

    dotnet test --collect:"XPlat Code Coverage" --settings YourLibraryName.runsettings
    

  5. Pack:

    dotnet pack -c Release --no-build
    

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:

  1. Code merged to master branch
  2. Pipeline automatically triggers
  3. Build, test, and pack steps execute
  4. Package published to Azure Artifacts feed

Manual Publishing

Option 1: Trigger Pipeline Manually

  1. Go to Azure DevOps โ†’ Pipelines
  2. Select your pipeline
  3. Click "Run pipeline"
  4. Select master branch
  5. 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

variables:
  majorMinorVersion: 1.0
  semanticVersion: $[counter(variables['majorMinorVersion'], 0)]

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:

dotnet add package YourLibraryName

๐Ÿ“™ Additional Resources

๐Ÿ“™ Additional Resources