CI/CD for C# Projects: GitHub Actions at an Advanced Level

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Introduction: Why is Basic CI/CD Not Enough?



The initial level of CI/CD for C# projects is usually limited to simply running dotnet build and dotnet test. However, when a project grows into a microservice architecture, a monorepository, or requires support for multiple .NET versions, the standard approach becomes insufficient. Build times increase, tests are duplicated, and the NuGet package deployment process becomes routine.



In this article, we will cover advanced techniques for configuring GitHub Actions for C# (.NET) projects. You will learn how to speed up pipelines using caching, organize parallel tests, implement matrix builds, and set up automatic publishing to NuGet. All code examples are ready to use in your projects.



1. Matrix Build and Testing Multiple .NET Versions



A matrix is a powerful GitHub Actions tool that allows you to run the same workflow with different parameters. For C#, this is indispensable when you need to check code compatibility with .NET 6, .NET 8, and .NET 9.



Example Matrix Configuration



Create the file .github/workflows/ci-matrix.yml:



name: Matrix Build and Test

on: push: branches: [ main, develop ] pull_request: branches: [ main ]

jobs: build: runs-on: ubuntu-latest strategy: matrix: dotnet-version: ['6.0.x', '8.0.x', '9.0.x'] configuration: ['Release', 'Debug'] fail-fast: false # Continue the build even if one version fails

steps: - uses: actions/checkout@v4 - name: Setup .NET ${{ matrix.dotnet-version }} uses: actions/setup-dotnet@v4 with: dotnet-version: ${{ matrix.dotnet-version }} - name: Restore dependencies run: dotnet restore - name: Build run: dotnet build --configuration ${{ matrix.configuration }} --no-restore - name: Run tests run: dotnet test --configuration ${{ matrix.configuration }} --no-build --verbosity normal


Key Points:

  • fail-fast: false — allows you to see results for all versions, even if one of them fails.
  • The matrix can include not only .NET versions but also OS (e.g., os: [ubuntu-latest, windows-latest]).
  • For a monorepository, you can add a project-path parameter and test different modules.


2. Dependency Caching: Speed Up the Build by 3x



Each GitHub Actions run starts with a clean environment. Without caching, dotnet restore will download all NuGet packages from scratch every time. For large projects, this can take 2-5 minutes. The solution is caching the ~/.nuget/packages folder.



Advanced Cache Configuration



Use the actions/cache action with a proper key that takes the lock file into account:



steps:  - uses: actions/checkout@v4

- name: Cache NuGet packages uses: actions/cache@v4 with: path: ~/.nuget/packages # Key: hash of the lock file + OS name key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }} # Fallback to the previous key if the lock file hasn't changed restore-keys: | ${{ runner.os }}-nuget-

- name: Setup .NET uses: actions/setup-dotnet@v4 with: dotnet-version: '8.0.x'

- name: Restore dependencies run: dotnet restore


Tip: If the project has multiple .csproj files, use hashFiles('**/*.csproj', '**/packages.lock.json') for a more precise hash.



3. Parallel Test Execution and Report Generation



When a project has hundreds of tests, sequential execution can take tens of minutes. GitHub Actions supports parallelization at the job level. But for

Blogs

Book Recommendations