We know how many benefits come with implementing CI/CD and continuous integration in projects, and how much it improves delivery quality.
In this article you will see how to implement some CI/CD steps in Github Actions using .NET 5. You will find that the basic implementation is quite straightforward — but don't stop here, there are many other flows you can build beyond these.
1. Structuring the Project
We will create a simple WebApi that displays some color data. We will also add a test project using XUnit, which we will use as one of the CI/CD flows.
1.1. Creating the Solution
dotnet new sln -n Colors1.2. Adding the WebApi
dotnet new webapi -n Colors.WebApi1.3. Adding the Test Project
dotnet new xunit -n Colors.Tests1.4. Adding projects to the solution
dotnet sln add .\Colors.WebApi\Colors.WebApi.csproj
dotnet sln add .\Colors.Tests\Colors.Tests.csproj1.5. Restoring the project
dotnet restore
dotnet build Colors.sln2. WebApi Implementation
I implemented a simple API with a color repository that handles GET requests and returns colors, along with some basic tests. The project structure looks like this:
2.1. ColorController
using System.Linq;
using Colors.WebApi.Repositories;
using Microsoft.AspNetCore.Mvc;
namespace Colors.WebApi.Controllers
{
[ApiController]
[Route("api/colors")]
public class ColorController : ControllerBase
{
[HttpGet]
public IActionResult GetAllColors()
{
var colors = ColorsRepository.Get();
return Ok(colors);
}
[HttpGet("{name}")]
public IActionResult GetByName(string name)
{
var colors = ColorsRepository.Get();
var response = colors.Where(x =>
x.Name.ToLower() == name.ToLower())
.FirstOrDefault();
return Ok(response);
}
}
}2.2. Color Model
namespace Colors.WebApi.Models
{
public class Color
{
public int Id { get; set; }
public string Name { get; set; }
}
}2.3. Repository — ColorRepository
using System.Collections.Generic;
using Colors.WebApi.Models;
namespace Colors.WebApi.Repositories
{
public static class ColorsRepository
{
public static List<Color> Get()
{
var colors = new List<Color>();
colors.Add(new Color { Id = 1, Name = "Red" });
colors.Add(new Color { Id = 2, Name = "Black" });
colors.Add(new Color { Id = 3, Name = "Pink" });
colors.Add(new Color { Id = 4, Name = "Green" });
colors.Add(new Color { Id = 5, Name = "Gray" });
return colors;
}
}
}2.4. Test — ColorControllerTest
using System.Collections.Generic;
using Colors.WebApi.Controllers;
using Colors.WebApi.Models;
using Microsoft.AspNetCore.Mvc;
using Xunit;
namespace Colors.Tests
{
public class ColorControllerTest
{
private ColorController _controller;
public ColorControllerTest()
{
_controller = new ColorController();
}
[Fact]
public void GetAllColorsTest()
{
var response = _controller.GetAllColors()
as OkObjectResult;
var colors = response.Value as List<Color>;
Assert.NotNull(colors);
}
[Theory]
[InlineData("red")]
[InlineData("black")]
public void GetColorTest(string color)
{
var response = _controller.GetByName(color)
as OkObjectResult;
var returnColor = response.Value as Color;
Assert.NotNull(returnColor);
Assert.Equal(color.ToUpper(),returnColor.Name.ToUpper());
}
}
}3. Docker Configuration
Now let's configure Docker. Create a file named Dockerfile at the root of the project. We will use the ASP.NET Core Runtime and the .NET SDK for .NET 5, both available on DockerHub. We will include both projects — Colors.Tests and Colors.WebApi — so they are all compiled. The file should look like this:
FROM mcr.microsoft.com/dotnet/aspnet:5.0 AS base
WORKDIR /app
FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build
WORKDIR /src
COPY Colors.sln ./
COPY Colors.WebApi/*.csproj ./Colors.WebApi/
COPY Colors.Tests/*.csproj ./Colors.Tests/
RUN dotnet restore
COPY . .
WORKDIR /src/Colors.WebApi
RUN dotnet build -c Release -o /app
WORKDIR /src/Colors.Tests
RUN dotnet build -c Release -o /app
FROM build AS publish
RUN dotnet publish -c Release -o /app
FROM base AS final
WORKDIR /app
COPY --from=publish /app .
CMD ASPNETCORE_URLS=http://*:$PORT dotnet Colors.WebApi.dllThe Dockerfile essentially describes the publication routine we would normally perform manually. It copies the entire project into the image, restores dependencies, runs a release build, and finally publishes the project — generating the correct output files and running it.
4. Configuring Github Actions
This step could also have been done first — you could have cloned the project and started development from there.
Assuming you followed the article flow, you will need to create a project on Github. Once that is done, go to your repository and open the Actions tab.
We will look for the .NET Workflow, which generates a .yml file. This is where your configuration lives — you define which steps should run during the continuous integration process. As mentioned, we will configure the test and build steps using the Dockerfile image we set up earlier. Modify the file so it looks like this:
name: .NET
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup .NET
uses: actions/setup-dotnet@v1
with:
dotnet-version: 5.0.100
- name: Restore dependencies
run: dotnet restore
- name: Build
run: dotnet build --no-restore
- name: Test
run: dotnet test --no-build --verbosity normal
- name: Docker Build
run: docker build . --file Dockerfile --tag colors-apiNow that you have configured your remote Github project, if you have not connected it to your local project yet, run the following from the root of your project:
git init
git remote add origin {your repository address}
git pullNow download the inserted configurations, add your files, and commit.
git add .
git commit -m "first commit"
git push -u origin master5. Conclusion
After committing, you can go to your project in Github Actions and watch all the steps running. Using continuous integration in your projects greatly improves delivery quality — and implementing it in personal projects is excellent practice.