This article explores the use of CircleCI’s Windows execution environment to build and run tests for a simple calculator application built using ASP.NET Core on a Windows virtual machine. It includes the full configuration file for the application and breaks it down explaining the relevant sections.

There are many frameworks in web and application development and each one has different features and benefits. ASP.NET is an open source, server-side web application framework, created by Microsoft, which runs on Windows and allows developers to create web applications, web services, and dynamic content-driven websites. ASP.NET Core is an improved, cross-platform version of ASP.NET that runs on every major computing platform, including Windows, macOS, and Linux.

The benefits of CircleCI’s Windows support to build and test your application are:

  • Support for Docker Engine - Enterprise for Docker-based Windows workflows
  • The Windows environment jobs are VM-based and provide complete build isolation
  • PowerShell is the default shell, but Bash and cmd are available via declaration
  • Windows jobs have access to the same set of powerful CircleCI features, like caches, workspaces, approval jobs, and restricted contexts, with the same level of support

Prerequisites

To follow along you will need:

Application details

This tutorial uses a simple calculator application that you can find and fork here. The following is the application’s directory structure:

.
├── Dockerfile
├── LICENSE
├── README.md
├── SimpleCalc
│   ├── Controllers
│   ├── Models
│   ├── Program.cs
│   ├── Services
│   ├── SimpleCalc.csproj
│   ├── Startup.cs
│   ├── Views
│   ├── appsettings.Development.json
│   ├── appsettings.json
│   └── wwwroot
├── SimpleCalc.sln
└── test
    └── SimpleCalc.Tests

8 directories, 9 files

Once you have forked the project, connect it to CircleCI by following the instructions for setting up your build on CircleCI.

Configuring CircleCI

To configure CircleCI, I’ve added a config.yml file in a .circleci folder located in the project’s root. Here is the full config file for this project:

version: 2.1

orbs:
  windows: circleci/windows@2.2.0

jobs:
  test:
    description: Setup and run application tests
    executor:
      name: windows/default
    steps:
      - checkout
      - restore_cache:
          keys:
            - dotnet-packages-v1-{{ checksum "SimpleCalc/SimpleCalc.csproj" }}
      - run:
          name: "Install project dependencies"
          command: dotnet.exe restore
      - save_cache:
          paths:
            - C:\Users\circleci\.nuget\packages
          key: dotnet-packages-v1-{{ checksum "SimpleCalc/SimpleCalc.csproj" }}
      - run:
          name: "Run Application Tests"
          command: dotnet.exe test -v n --results-directory:test_coverage --collect:"Code Coverage"
      - run:
          name: "Print Working Directory"
          command: pwd
      - store_artifacts:
          path: C:\Users\circleci\project\test_coverage
  build:
    description: Build application with Release configuration
    executor:
      name: windows/default
    steps:
      - checkout
      - run:
          name: "Build Application according to some given configuration"
          command: dotnet.exe build --configuration Release
workflows:
  test_and_build:
    jobs:
      - test
      - build:
          requires:
            - test

Now, let’s break down the configuration.

Defining OS/environment and runtime

We are using CircleCI version 2.1 which supports the use of orbs. CircleCI orbs are shareable packages of configuration elements, including jobs, commands, and executors. The orb that we are using is the Windows orb. It gives users the tools to build Windows projects such as a Universal Windows Platform (UWP) application, a .NET executable, or Windows-specific projects (like the .NET framework).

version: 2.1

orbs:
  windows: circleci/windows@2.2.0

This project has test and build jobs. The build job will require all the tests to pass in order for it to be run. To configure this ordering, we will employ CircleCI’s workflows which are a set of rules for defining a collection of jobs and their run order. The workflow declaration is at the end of the example config file.

workflows:
  test_and_build:
    jobs:
      - test
      - build:
          requires:
            - test

Setting up and running tests

The Windows executor is preloaded with Visual Studio 2019 plus a number of other development tools. We will be using the PowerShell shell which is the default, so it needs no declaration.

  test:
    description: Setup and run application tests
    executor:
      name: windows/default

Steps are a collection of executable commands which are run during a job. In the following steps, we are checking out our code and restoring our project’s dependencies.

    steps:
      - checkout
      - restore_cache:
          keys:
            - dotnet-packages-v1-{{ checksum "SimpleCalc/SimpleCalc.csproj" }}
      - run:
          name: "Install project dependencies"
          command: dotnet.exe restore
      - save_cache:
          paths:
            - C:\Users\circleci\.nuget\packages
          key: dotnet-packages-v1-{{ checksum "SimpleCalc/SimpleCalc.csproj" }}

CircleCI’s caching documentation goes into great detail describing the caching options that are available and how to use them. The dotnet.exe restore command uses NuGet to restore dependencies as well as the project-specific tools that are specified in the project file. By default, the restoration of dependencies and tools are executed in parallel.

Unit testing is essential for a real-world application. You can also collect the code coverage from the unit tests. Unit testing is a software testing method by which individual units of source code are tested to determine whether they are fit for use. A unit is the smallest possible testable software component. The “dotnet.exe test” is the .NET test driver used to execute unit tests. Our project employs the use of xUnit test framework. If all tests are successful, the test runner returns 0 as an exit code; otherwise, if any test fails, it returns 1. The test runner and the unit test library are packaged as NuGet packages and are restored as ordinary dependencies for the project.

      - run:
         name: "Run Application Tests"
         command: dotnet.exe test -v n --results-directory:test_coverage --collect:"Code Coverage"

We are storing the coverage collected from running the tests as an artifact in the test_coverage folder. Code coverage reports for ASP.NET Core projects are not provided out-of-the-box.

      - store_artifacts:
         path: C:\Users\circleci\project\test_coverage

Building the project

After the successful running of the tests, we want to build our project. To do this we define the build configuration to use. The options available for this project are:

  • Release
  • Debug The config below shows our choice of release.
  build:
    description: Build application with Release configuration
    executor:
      name: windows/default
    steps:
      - checkout
      - run:
          name: "Build Application according to some given configuration"
          command: dotnet.exe build --configuration Release

Starting with .NET Core 2.0 SDK, you don’t have to run “dotnet restore” because it’s run implicitly by all commands that require a restore to occur, such as “dotnet new”, “dotnet build” and “dotnet run”.

Conclusion

The use of the Windows executor on CircleCI makes it a breeze to build and test ASP.NET Core applications. The CircleCI certified Windows orb is a trusted solution because it was tested by CircleCI and validated by the community. To learn more about the orbs that are available for use in your config, visit the orb registry.

Orbs save time that would otherwise be spent on:

  • Researching, testing, and setting up a Windows virtual machine
  • Installing development tools, e.g., vs2019, Nuget
  • Learning to use Windows tools like PowerShell

The use of orbs also reduces the number of lines of code one would have to write in their config files. This project’s configuration file has 42 lines of code, making it easier to understand and maintain.


Dominic Motuka is a DevOps Engineer at Andela with 4+ years of hands-on experience supporting, automating, and optimizing production-ready deployments in AWS and GCP, leveraging configuration management, CI/CD, and DevOps processes.