Step-by-Step Guide to setup CI/CD in .NET Core via Github Actions

Step-by-Step Guide to setup CI/CD in .NET Core via Github Actions

ยท

7 min read

In my recent work with open-source repositories on GitHub, I've noticed a common trend: the use of GitHub Actions for automated builds. This powerful feature of GitHub simplifies the process of continuous integration and continuous deployment (CI/CD).

In this blog post, we will explore how to set up a comprehensive CI/CD pipeline using GitHub Actions. We'll walk through the steps of creating a sample WebAPI and deploying it to Azure Web Apps. By the end, you'll have a solid understanding of how to streamline your development workflow with GitHub Actions and Azure.

Getting started with GitHub Actions

GitHub Actions is a CI/CD platform that allows you to automate your build, test, and deployment pipeline. You can create workflows that build and test every pull request to your repository or deploy merged pull requests to production.

GitHub Actions goes beyond just DevOps and lets you run workflows when other events happen in your repository. For example, you can run a workflow to automatically add the appropriate labels whenever someone creates a new issue in your repository.

GitHub provides Linux, Windows, and macOS virtual machines to run your workflows, or you can host your own self-hosted runners in your own data center or cloud infrastructure.

Components of GitHub Actions

There are several components to a GitHub Actions namely:

  • Workflows
  • Events
  • Jobs
  • Actions
  • Runners

Let's deep dive into Workflows.

What are Workflows?

A workflow is a configurable automated process that will run one or more jobs. Workflows are defined by a YAML file checked in to your repository and will run when triggered by an event in your repository, or they can be triggered manually, or at a defined schedule.

Workflows are defined in the .github/workflows directory in a repository, and a repository can have multiple workflows, each of which can perform a different set of tasks. For example, you can have one workflow to build and test pull requests, another workflow to deploy your application every time a release is created, and still another workflow that adds a label every time someone opens a new issue.

You can reference a workflow within another workflow.

Creating our first Workflow

I've used a boilerplate .NET Core WebAPI 8 template and pushed the code to this repository.

Pre-requisites

  • Azure WebApp 1
  • Setup Environment Variables 3
  • Enable SCM Basic Auth Publishing Credentials - This will enable us to be able to publish our .zip from Github Actions 3_1
  • Store Publish Profile in Github Repository Secrets - Since publish profile is considered as a sensitive information, it is ideal to store it in a secret and reference it within the pipeline later from here image

Once these settings are done, we can proceed with creating our first workflow!

Navigate to Action tab

Go to the Actions tab of the repository and click on New Workflow

image

Decide on using Built-in Workflows or Custom Workflow

There are a lot of built-in workflows available on the Github Actions. If they fit your need, you can use them. Since we're learning to build one from the scratch, I'll selected Setup a workflow yourself

image

Defining our workflow

Step#1 - Setup triggers and workflow name

Using on, we can specify when do we want this workflow to be triggered? For now, I've configured it to work in two ways:

  • push
    • This will trigger the workflow when the code is pushed to the main branch
  • workflow_dispatch
    • This will give us a manual way to trigger the pipeline from the GitHub Actions UI
name: Build ๐Ÿš€

on:
  workflow_dispatch:
  push:
    branches:
    - main

Step#2 - Setup any environment variables

If you're required to use any environment variables in the pipeline, that can be declared next. Here, I'm declaring an environment variable namely .NET version and I'm setting it to be 8.0.x

# Setup environment variables
env:
  DOTNET_VERSION: 8.0.x

Step#3 - Build Job

Within the next steps of the pipeline, we've build steps. We're requesting our job to run on the ubuntu-latest . Then, we need to define the steps required to be run as a part of this job. We've used the below steps:

  • actions/checkout@v3
    • This is to ensure our code is checked out from the main branch
  • actions/setup-dotnet@v3
    • This is to download the specified version of .NET (In our case 8.0.x) within the VM
  • dotnet restore ./Starter.sln
    • Restore the solution
  • Checking NuGet Vulnerabilities
    • This is a DevSecOps approach which most of the enterprises follow these days to ensure they catch any vulnerabilities earlier in their development stage
    • Within this step, we're usng the dotnet list package to list the Vulnerabilities in the project files we're using and pushing it to a file namely vulnerabilities.txt
    • If there are any vulnerabilities found, the pipeline would be blocked and no further steps would be executed
  • dotnet build
    • Building the projects.
    • --configuration Release switch will build the project with release configuration
    • --no-restore switch will instruct the compiler to build the project without restoring (Since we've explicitly restored the project earlier) and we can save time here
  • dotnet test
    • We would like to run the tests and along with that also publsh the code coverage report.
  • Create Code Coverage
    • Create code coverage report
  • Publish Coverage report
    • Publish the code coverage report as output of the Github summary
  • Publish Artifact
    • Using this step, the artifacts created in the build job will be exported so that we could import and use it in the next step
jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      # Checkout code
      - uses: actions/checkout@v3

      - name: Setup .NET
        uses: actions/setup-dotnet@v3
        with:
          dotnet-version: ${{env.DOTNET_VERSION}}

      - name: Dotnet Restore
        run: dotnet restore ./Starter.sln

      - name: Checking NuGet vulnerabilites
        run: |
            dotnet list package --vulnerable --include-transitive  > vulnerabilities.txt
            # Check if vulnerabilities are found in vulnerabilities.txt
            if grep -q "Vulnerabilities found" vulnerabilities.txt; then
              echo "Vulnerabilities found. Blocking pipeline."
              exit 1
            else
              echo "No vulnerabilities found."
            fi

      # Specifying no-restore flag since we're already restoring in earlier.
      - name: Dotnet Build
        run: dotnet build ./Starter.sln --configuration Release --no-restore

      # Specifying no-restore and no-build to speed up the process
      - name: Dotnet Test
        run: dotnet test ./Starter.sln --configuration Release --no-restore --no-build --collect:"XPlat Code Coverage" --logger trx --results-directory coverage

      - name: Dotnet Publish
        run: dotnet publish ./Starter/Starter.WebAPI/Starter.WebAPI.csproj --configuration Release --no-build --output '${{ env.AZURE_WEBAPP_PUBLISH_PATH }}/myapp'

      - name: Code Coverage Summary Report
        uses: irongut/CodeCoverageSummary@v1.3.0
        with:
           filename: 'coverage/*/coverage.cobertura.xml'
           badge: true
           format: 'markdown'
           output: 'both'

      - name: Publish Code Coverage
        run: cat code-coverage-results.md >> $GITHUB_STEP_SUMMARY

      - name: Publish artifact
        uses: actions/upload-artifact@v4
        with:
          name: myapp
          path: .

Step#4 - Deploy Job

This is where we would be deploying the artifact generated in the previous step to Azure App Service. Web App and the App Service plan are already provisioned. Within the deploy job, we're doing the following:

We've used the below steps:

  • actions/download-artifact@v4
    • This will download the artifact generated as the outcome of the build job
  • azure/webapps-deploy@v2

    • This is where we connect to Azure App Service and uploading the .zip file contents using the publish profile
    • Publish profile is generally considered as a secret so instead of hardcoding it here, it's better to reference it from Actions > Repository secrets ```yaml deploy: runs-on: ubuntu-latest needs: build

    steps:

    • name: Download artifact from build job uses: actions/download-artifact@v4 with: name: myapp path: .

    • name: Deploy to App Service uses: azure/webapps-deploy@v2 with: app-name: ${{ env.AZURE_WEBAPP_NAME }} publish-profile: ${{ secrets.AZURE_PUBLISH_PROFILE }} package: "${{ env.AZURE_WEBAPP_PUBLISH_PATH }}/myapp" ```

      Executing the Workflow

      As you can see both the build and the deploy jobs have been successfully completed image

Testing the application

After navigating to this URL, we're able to see our API deployed successfully 2 4

Show me the code :)

You can find the entire codebase and the workflow implemented in this Github repository

References

docs.github.com/en/billing/managing-billing..

docs.github.com/en/actions

docs.github.com/en/actions/using-workflows/..

I hope you followed along and enjoyed this hands-on tutorial. If you found it helpful, please leave a reaction. Your feedback motivates me to create more valuable content. Thank you for reading, and happy coding!

ย