.NET 💜 GitHub Actions: Intro to GitHub Actions for .NET

David Pine

Hi friends, I put together posts where I’m going to teach you the basics of the GitHub Actions platform. In this post, you’ll learn how GitHub Actions can improve your .NET development experience and team productivity. I’ll show you how to use them to automate common .NET app dev scenarios with workflow composition.

An introduction to GitHub Actions

Developers that use GitHub for managing their git repositories have a powerful continuous integration (CI) and continuous delivery (CD) feature with the help of GitHub Actions. A common developer scenario is when developers propose changes to the default branch (typically main) of a GitHub repository. These changes, while often scrutinized by reviewers, can have automated checks to ensure that the code compiles and tests pass.

GitHub Actions allow you to build, test, and deploy your code right from your source code repository on https://github.com. GitHub Actions are consumed by GitHub workflows. A GitHub workflow is a YAML (either *.yml or *.yaml) file within your GitHub repository. These workflow files reside in the .github/workflows/ directory from the root of the repository. A workflow references one or more GitHub Action(s) together as a series of instructions, where each instruction executes a specific task.

The GitHub Action terminology

To avoid mistakenly using some of these terms inaccurately, let’s define them:

  • GitHub Actions: GitHub Actions is a continuous integration and continuous delivery (CI/CD) platform that allows you to automate your build, test, and deployment pipeline.
  • workflow: A workflow is a configurable automated process that will run one or more jobs.
  • event: An event is a specific activity in a repository that triggers a workflow run.
  • job: A job is a set of steps in a workflow that execute on the same runner.
  • action: An action is a custom application for the GitHub Actions platform that performs a complex but frequently repeated task.
  • runner: A runner is a server that runs your workflows when they’re triggered.

For more information, see GitHub Docs: Understanding GitHub Actions

Inside the GitHub workflow file

A workflow file defines a sequence of jobs and their corresponding steps to follow. Each workflow has a name and a set of triggers, or events to act on. You have to specify at least one trigger for your workflow to run unless it’s a reusable workflow. A common .NET GitHub workflow would be to build and test your C# code when changes are either pushed or when there’s a pull request targeting the default branch. Consider the following workflow file:

name: build and test
on:
  push:
  pull_request:
    branches: [ main ]
    paths-ignore:
    - 'README.md'
env:
  DOTNET_VERSION: '6.0.x'
jobs:
  build-and-test:
    name: build-and-test-${{matrix.os}}
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macOS-latest]
    steps:
    - uses: actions/checkout@v2
    - name: Setup .NET
      uses: actions/setup-dotnet@v1
      with:
        dotnet-version: ${{ env.DOTNET_VERSION }}
    - name: Install dependencies
      run: dotnet restore
    - name: Build
      run: dotnet build --configuration Release --no-restore
    - name: Test
      run: dotnet test --no-restore --verbosity normal

I’m not going to assume that you have a deep understanding of this workflow, and while it’s less than thirty lines — there is still a lot to unpack. I put together a sequence diagram (powered by Mermaid), that shows how a developer might visualize this workflow.

GitHub build and test workflow sequence diagram.

Here’s the same workflow file, but this time it is expanded with inline comments to add context (if you’re already familiar with the workflow syntax, feel free to skip past this):

# The name of the workflow.
# This is the name that's displayed for status
# badges (commonly embedded in README.md files).
name: build and test

# Trigger this workflow on a push, or pull request to
# the main branch, when either C# or project files changed
on:
  push:
  pull_request:
    branches: [ main ]
    paths-ignore:
    - 'README.md'

# Create an environment variable named DOTNET_VERSION
# and set it as "6.0.x"
env:
  DOTNET_VERSION: '6.0.x' # The .NET SDK version to use

# Defines a single job named "build-and-test"
jobs:
  build-and-test:

    # When the workflow runs, this is the name that is logged
    # This job will run three times, once for each "os" defined
    name: build-and-test-${{matrix.os}}
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macOS-latest]

    # Each job run contains these five steps
    steps:

    # 1) Check out the source code so that the workflow can access it.
    - uses: actions/checkout@v2

    # 2) Set up the .NET CLI environment for the workflow to use.
    #    The .NET version is specified by the environment variable.
    - name: Setup .NET
      uses: actions/setup-dotnet@v1
      with:
        dotnet-version: ${{ env.DOTNET_VERSION }}

    # 3) Restore the dependencies and tools of a project or solution.
    - name: Install dependencies
      run: dotnet restore

    # 4) Build a project or solution and all of its dependencies.
    - name: Build
      run: dotnet build --configuration Release --no-restore

    # 5) Test a project or solution.
    - name: Test
      run: dotnet test --no-restore --verbosity normal

The preceding workflow file contains many comments to help detail each area of the workflow. You might have noticed that the steps define various usages of GitHub Actions or simple run commands. The relationship between a GitHub Action and a consuming GitHub workflow is that workflows consume actions. A GitHub Action is only as powerful as the consuming workflow. Workflows can define anything from simple tasks to elaborate compositions and everything in between. For more information on creating GitHub workflows for .NET apps, see the following .NET docs resources:

I hope that you’re asking yourself, “why is this important?” Sure, we can create GitHub Actions, and we can compose workflows that consume them — but why is that important?! That answer is GitHub status checks 🤓.

GitHub status checks

One of the primary benefits of using workflows is to define conditional status checks that can deterministically fail a build. A workflow can be configured as a status check for a pull request (PR), and if the workflow fails, for example the source code in the pull request doesn’t compile — the PR can be blocked from being merged. Consider the following screen capture, which shows that two checks have failed, thus blocking the PR from being merged.

Example GitHub pull request with failing status checks.

As the developer who is responsible for reviewing a PR, you’d immediately see that the pull request has failing status checks. You’d work with the developer who proposed the PR to get all of the status checks to pass. The following is a screen capture showing a “green build”, a build that has all of its status checks as passing.

Example GitHub pull request with passing status checks.

For more information, see GitHub Docs: GitHub status checks.

GitHub Actions that .NET developers should know

As a .NET developer, you’re likely familiar with the .NET CLI. The .NET CLI is included with the .NET SDK. If you don’t already have the .NET SDK, you can download the .NET 6 SDK.

Using the previous workflow file as a point of reference, there are five steps — each step includes either the run or uses syntax:

Action or command Description
uses: actions/checkout@v2 This action checks-out your repository under $GITHUB_WORKSPACE, so your workflow can access it. For more information, see actions/checkout
uses: actions/setup-dotnet@v1 This action sets up a .NET CLI environment for use in actions. For more information, see actions/setup-dotnet
run: dotnet restore Restores the dependencies and tools of a project or solution. For more information, see dotnet restore
run: dotnet build Builds the project or solution. For more information, see dotnet build
run: dotnet test Runs the tests for the project or solution. For more information, see dotnet test

Some steps rely on GitHub Actions and reference them with the uses syntax, while others run commands. For more information on the differences, see Workflow syntax for GitHub Actions: uses and run.

.NET applications rely on NuGet packages. You can optimize your workflows by caching various dependencies that change infrequently, such as NuGet packages. As an example, you can use the actions/cache to cache NuGet packages:

steps:
- uses: actions/checkout@v2
- name: Setup dotnet
  uses: actions/setup-dotnet@v1
  with:
    dotnet-version: '6.0.x'
- uses: actions/cache@v2
  with:
    path: ~/.nuget/packages
    # Look to see if there is a cache hit for the corresponding requirements file
    key: ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json') }}
    restore-keys: |
      ${{ runner.os }}-nuget
- name: Install dependencies
  run: dotnet add package Newtonsoft.Json --version 12.0.1

For more information, see GitHub Docs: Building and testing .NET - Caching dependencies.

In addition to using the standard GitHub Actions or invoking .NET CLI commands using the run syntax, you might be interested in learning about some additional GitHub Actions.

Additional GitHub Actions

Several .NET GitHub Actions are hosted on the dotnet GitHub organization:

.NET GitHub Action Description
dotnet/versionsweeper This action sweeps .NET repos for out-of-support target versions of .NET. The .NET docs team uses the .NET version sweeper GitHub Action to automate issue creation. The action runs as a cron job (or on a schedule). When it detects that .NET projects target out-of-support versions, it creates issues to report its findings. The output is configurable and helpful for tracking .NET version support concerns.
dotnet/code-analysis This action runs the code analysis rules that are included in the .NET SDK as part of continuous integration (CI). The action runs both code-quality (CAXXXX) rules and code-style (IDEXXXX) rules.

.NET developer community spotlight

The .NET developer community is building GitHub Actions that might be useful in your organizations. As an example, check out the zyborg/dotnet-tests-report which is a GitHub Action to run .NET tests and generate reports and badges. If you use this GitHub Action, be sure to give their repo a star ⭐.

There are many .NET GitHub Actions that can be consumed from workflows, see the GitHub Marketplace: .NET.

A word on .NET workloads

.NET runs anywhere, and you can use it to build anything. There are optional workloads that may need to be installed when building from a GitHub workflow. There are many workloads available, see the output of the dotnet workload search command as an example:

dotnet workload search

Workload ID         Description
-----------------------------------------------------------------------------------------
android             .NET SDK Workload for building Android applications.
android-aot         .NET SDK Workload for building Android applications with AOT support.
ios                 .NET SDK Workload for building iOS applications.
maccatalyst         .NET SDK Workload for building macOS applications with MacCatalyst.
macos               .NET SDK Workload for building macOS applications.
maui                .NET MAUI SDK for all platforms
maui-android        .NET MAUI SDK for Android
maui-desktop        .NET MAUI SDK for Desktop
maui-ios            .NET MAUI SDK for iOS
maui-maccatalyst    .NET MAUI SDK for Mac Catalyst
maui-mobile         .NET MAUI SDK for Mobile
maui-windows        .NET MAUI SDK for Windows
tvos                .NET SDK Workload for building tvOS applications.
wasm-tools          .NET WebAssembly build tools

If you're writing a workflow for Blazor WebAssembly app, or .NET MAUI as an example — you'll likely run the dotnet workload install command as one of your steps. For example, an individual run step to install the WebAssembly build tools would look like:

run: dotnet workload install wasm-tools

Summary

In this post, I explained the key differences between GitHub Actions and GitHub workflows. I explained and scrutinized each line in an example workflow file. I then showed you how a developer might visualize the execution of a GitHub workflow as a sequence diagram. I shared a few additional resources you may not have known about. For more information, see .NET Docs: GitHub Actions and .NET.

This is just the start of blogs around using GitHub Actions for .NET. In future posts, I'll show how to create GitHub Actions using .NET. I'll walk you through upgrading an existing .NET GitHub Action that is used to automatically maintain a _CODEMETRICS.md file within the root of the repository. The code metrics analyze the C# source code of the target repository to determine things such as cyclomatic complexity and the maintainability index. In addition to these metrics, we'll add the ability to generate Mermaid class diagrams, which is now natively supported by GitHub flavored markdown.

2 comments

Comments are closed. Login to edit/delete your existing comments

  • Luca Crisafulli

    .NET devs prefer to have a GUI for such things… Typing is for coding only. 🙂

  • Yann Duran

    Wow, I wish I’d seen this blog post when I started using Github Actions! Very comprehensive and understandable. I managed to learn most of this from bits & pieces all over the web. This would have made things gel so much quicker.