A Potpourri of .Net Core CI/CD (Lite)
Tags: dotnet, devops, productivity, csharp
Published: 2018-09-21 06:50:21

---
title: A Potpourri of .Net Core CI/CD (Lite)
published: true
tags: dotnet,devops,productivity,csharp
canonical_url: 
---

I have been using Visual Studio professionally for about a decade now. [Jenkins](https://jenkins.io/) for CI/CD for the last 5 years. While the latest iterations of both are excellent tools ([Jenkins pipeline](https://jenkins.io/solutions/pipeline/), in particular), my development environment feels stagnant.

I use OSX at home, and with our [migration to Github](./github-pages-migration-part-2-51ho) now seemed like the perfect time to try something different. What follows is a brain-dump of stuff I’ve done with a recent github project, [nng.NETCore](https://github.com/subor/nng.NETCore)- .Net bindings for [nng](https://github.com/nanomsg/nng).

## dotnet CLI

I’d previously used [MonoDevelop](https://www.monodevelop.com/) on OSX. Post Microsoft acquisition it was rebranded [Visual Studio for Mac](https://docs.microsoft.com/en-us/visualstudio/mac/). While more comfortable than Apple’s Xcode, it always feels… clunky. [Enter the dotnet CLI](https://docs.microsoft.com/en-us/dotnet/core/tools/?tabs=netcore2x):

```bash
mkdir l0core
cd l0core
# Create l0core.sln
dotnet new sln
# Create l0core/l0core.csproj
dotnet new console --name l0core
# Add l0core.csproj to l0core.sln
dotnet sln add l0core
# Add a bunch of existing projects

dotnet add l0core package <package_name>
```

One thing that surprised me was although you can add an existing project to the sln with:

```bash
dotnet sln add ../layer0/Layer0Shared/
```

You can’t easily add a project reference, for example:

```bash
# NB: This doesn't work
# In l0core.csproj add reference to Layer0Shared.csproj
dotnet add l0core reference Layer0Shared
```

Instead, you have to refer to the actual project:

```bash
dotnet add l0core reference ../layer0/Layer0Shared/
```

In Visual Studio I’m used to adding a project to the solution, then adding references to that project.

## Visual Studio Code

[Visual Studio Code](https://code.visualstudio.com/) has been my go-to text editor for over a year now. Especially for [editing markdown](https://rendered-obsolete.github.io/2018/02/16/markdown.html).

After creating the project, [VS Code C# guide](https://docs.microsoft.com/en-us/dotnet/core/tutorials/with-visual-studio-code) mentions debugging and stuff that didn’t work. Down at the bottom, the [FAQ](https://docs.microsoft.com/en-us/dotnet/core/tutorials/with-visual-studio-code#faq) mentions `.Net: Generate Assets for Build and Debug`. That failed with:

```
Could not locate .NET Core project. Assets were not generated.
```

Seems to be [this bug](https://github.com/OmniSharp/omnisharp-vscode/issues/1425). In any case, closing and restarting VS Code seemed to fix the issue.

**Start Debugging** and immediately failing was great:  
 ![](https://rendered-obsolete.github.io/assets/vscode_debug.png)

Starting with 1.26 (now on 1.27.1), after a few weeks using it as my main C# IDE, my gripes are:

- Intellisense stops working frequently (need to **Cmd+Shift+p > `restart omnisharp`** )
- No [“tasks” window](https://docs.microsoft.com/en-us/visualstudio/debugger/using-the-tasks-window?view=vs-2017)
- Starting debugger or running tests get stuck. End up `killall dotnet` and restarting VS Code
- No [XML doc](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/xmldoc/xml-documentation-comments) assistance ([this extension](https://marketplace.visualstudio.com/items?itemName=k--kato.docomment) is all there is). In regular Visual Studio:    
  - `///` starts comment block, and hitting return indents and inserts `///`
  - [XmlDoc tags like `<see>`](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/xmldoc/recommended-tags-for-documentation-comments) auto-complete and use intellisense
- Non-trivial yet common types (e.g. `System.Assembly`) take debugger a while to evaluate
- Lacks some source code analysis/linting; unneeded `using` statements, namespace scoping, etc.

## Appveyor

Setup [Appveyor](https://www.appveyor.com/) for both Windows and Linux builds of [nng.NETCore](https://github.com/subor/nng.NETCore).

After adding the project via the web interface, either commiting an empty `appveyor.yml` or clicking **NEW BUILD** will trigger a build and tests.

Failed with:

```
msbuild "C:\projects\nng-netcore\nng.NETCore.sln" /verbosity:minimal /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
Microsoft (R) Build Engine version 14.0.25420.1
Copyright (C) Microsoft Corporation. All rights reserved.
C:\projects\nng-netcore\nng.NETCore\nng.NETCore.csproj(1,1): error MSB4041: The default XML namespace of the project must be the MSBuild XML namespace. If the project is authored in the MSBuild 2003 format, please add xmlns="http://schemas.microsoft.com/developer/msbuild/2003" to the <Project> element. If the project has been authored in the old 1.0 or 1.2 format, please convert it to MSBuild 2003 format.
```

Appveyor defaults to using msbuild 14.0 which doesn’t support the latest project format. Change `appveyor.yml` to:

```yml
image: Visual Studio 2017
```

`git push` (or **NEW BUILD** ):

```
C:\Program Files\dotnet\sdk\2.1.401\Sdks\Microsoft.NET.Sdk\targets\Microsoft.PackageDependencyResolution.targets(198,5): error NETSDK1004: Assets file 'C:\projects\nng-netcore\nng.NETCore\obj\project.assets.json' not found. Run a NuGet package restore to generate this file. [C:\projects\nng-netcore\nng.NETCore\nng.NETCore.csproj]
Done Building Project "C:\projects\nng-netcore\nng.NETCore\nng.NETCore.csproj" (default targets) -- FAILED.
```

Need to restore packages:

```yml
before_build:
  - dotnet restore
```

To reduce the amout of build log-spam:

```yml
build:
  verbosity: minimal
```

## Testing with xUnit

Our current project uses [NUnit](http://nunit.org/) so I’m pretty comfortable with that. [xUnit seems to be the only recommended testing framework that isn’t MSTest](https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-test?tabs=netcore21#filter-option-details).

I like using parameterized tests to run variants and cover multiple code paths. A [handful](https://andrewlock.net/creating-parameterised-tests-in-xunit-with-inlinedata-classdata-and-memberdata/) [of blog](http://hamidmosalla.com/2017/02/25/xunit-theory-working-with-inlinedata-memberdata-classdata/) [posts](http://ikeptwalking.com/writing-data-driven-tests-using-xunit/) cover how to do that:

```csharp
class TransportsClassData : IEnumerable<object[]>
{
    public IEnumerator<object[]> GetEnumerator()
    {
        yield return new object[] { "ws://localhost:23123" };
        yield return new object[] { "tcp://localhost:23124" };
    }
    
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

[Theory]
[ClassData(typeof(TransportsClassData))]
public async Task Basic(string url)
{
    // ...
}
```

Needed the equivalent of [Nunit’s SetupFixtureAttribute](https://github.com/nunit/docs/wiki/SetUpFixture-Attribute) to perform one-time setup like [loading the nng native/unmanaged library](/2018/09/09/native-assembly.html):

```csharp
public class NngCollectionFixture
{
    public NngCollectionFixture()
    {
        // ...
    }
}

[CollectionDefinition("nng")]
public class NngCollection : ICollectionFixture<NngCollectionFixture>
{
}

[Collection("nng")]
public class BusTests
{
    public BusTests(NngCollectionFixture collectionFixture)
    {
        // ...
    }
}
```

Appveyor automatically picked up all the tests without any additional configuration. See [Running tests](https://www.appveyor.com/docs/running-tests/) for more information.

## Code Coverage

Using [codecov](https://codecov.io/) for code coverage. The following references cover everything:

- https://www.appveyor.com/blog/2017/03/17/codecov/
- https://github.com/codecov/example-csharp
- https://github.com/OpenCover/opencover/wiki/Usage

In all projects you want to enable code coverage add:

```xml
<PropertyGroup>
  <DebugType>full</DebugType>
</PropertyGroup>
```

In `appveyor.yml`:

```yml
before_build:
  - dotnet restore
  - choco install opencover.portable
  - choco install codecov

test_script:
  - OpenCover.Console.exe -register:user -target:dotnet.exe -targetargs:test -output:".\coverage.xml" -oldstyle

after_test:
  - codecov -f "coverage.xml" -t 2a07cd3d-8620-4495-8c14-3252b10b90bd
```

1. Use [Chocolatey](https://chocolatey.org/) to install [OpenCover](https://github.com/OpenCover/opencover)
2. Run `dotnet test` with OpenCover
3. Upload results to codecov

## Linux

Round out the CI with a [Linux build](https://www.appveyor.com/docs/getting-started-with-appveyor-for-linux/#running-windows-and-linux-builds-side-by-side) and both debug/release configurations:

```yml
image: 
- Visual Studio 2017
- Ubuntu
configuration:
  - Debug
  - Release
```

Commands can be prefixed with `cmd:` or `sh:` for Windows-only and Linux-only commands, respectively. With neither runs on both. The Chocolately stuff slows down builds quite a bit (adds a few minutes), so only run code coverage on Windows:

```yml
before_build:
  - dotnet restore
  - cmd: choco install opencover.portable
  - cmd: choco install codecov

test_script:
  - cmd: OpenCover.Console.exe -register:user -target:dotnet.exe -targetargs:"test --filter platform!=posix" -output:".\coverage.xml" -oldstyle
  - sh: dotnet test --filter "platform!=windows"
```

Avoid platform-specific tests with `--filter platform!=XYZ`. In the test code:

```csharp
[Fact]
[Trait("platform", "windows")]
public void WindowsOnlyTest()
{
    // ...
}
```

See [Filter Option Details](https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-test?tabs=netcore21#filter-option-details)and [Running Selective Unit Tests](https://docs.microsoft.com/en-us/dotnet/core/testing/selective-unit-tests) for additional options.

## Deployment

[Branches](https://www.appveyor.com/docs/branches/) shows how to do branch dependent build configuration without creating multiple `appveyor.yml` files. [Deployment](https://www.appveyor.com/docs/deployment/) covers general deployment topics.

Wanted to limit deployment to specifc branches when there was a git tag. [Pushing artifacts to Github releases](https://www.appveyor.com/docs/deployment/github/#configuring-in-appveyoryml) shows the appveyor configuration which I then [adapted to Nuget](https://www.appveyor.com/docs/deployment/nuget/):

```yml
deploy:
  provider: NuGet
  api_key:
    secure: OSKjxq8SQmVX8UaVkgaq1aUeGnuXHiTzNZoIi2VR0OMCp/WypCkBY7JbkmoKz497
  artifact: /.*\.nupkg/
  on:
    branch: master
    appveyor_repo_tag: true
```

The `api_key` value comes from [encrypting](https://ci.appveyor.com/tools/encrypt) my [nuget upload token](./signing-a-nupkg-and-uploading-to-nugetorg-2a98).

The obvious problem being I can’t `nuget sign` without my private key. Might have to settle for deploying to Github releases for now.

## Flair

No github project is complete without flair.

[![NuGet](https://img.shields.io/nuget/v/Subor.nng.NETCore.svg?colorB=brightgreen)](https://www.nuget.org/packages/Subor.nng.NETCore) via:

```
[![NuGet](https://img.shields.io/nuget/v/PACKAGE.svg?colorB=COLOR)](https://www.nuget.org/packages/PACKAGE)
```

[![Build status](https://img.shields.io/appveyor/tests/jake-ruyi/nng-netcore/master.svg)](https://ci.appveyor.com/project/jake-ruyi/nng-netcore/branch/master) from the [shields.io examples](https://shields.io/#/examples/build):

```
[![Build status](https://img.shields.io/appveyor/tests/USERNAME/PROJECT/BRANCH.svg)](https://ci.appveyor.com/project/USERNAME/PROJECT/branch/BRANCH)
```

[![codecov](https://codecov.io/gh/subor/nng.NETCore/branch/master/graph/badge.svg)](https://codecov.io/gh/subor/nng.NETCore) from Codecov.io repo **Settings > Badge** :

```
[![codecov](https://codecov.io/gh/GITHUB_OWNER/PROJECT/branch/BRANCH/graph/badge.svg)](https://codecov.io/gh/GITHUB_OWNER/PROJECT)
```

## Docker

While working on OSX it would be convenient to be able to build/test on Windows and Linux without having to push and wait for Appveyor. [Enter Docker](https://docs.microsoft.com/en-us/dotnet/core/docker/docker-basics-dotnet-core).

Create `Dockerfile`:

```dockerfile
FROM microsoft/dotnet:2.1-sdk
WORKDIR /app

COPY . ./
RUN dotnet restore
RUN dotnet test tests --filter platform!=windows
```

Run:

```bash
docker build -t dotnetapp-dev .
```

Have experimented with Docker in the past. While it was certainly cool, never really went “all in” and made it part of our development pipeline. Now that Windows is supported as a first-class citizen I’ll be giving it another, more serious look.