Tutorial: CI of .NET Framework apps in Linux Containers

- By Ambarish Chitnis on December 15, 2017

This blog was motivated by a great question on our support forum: "Can I use Shippable with a C# project?". My knee-jerk reaction was to tell him to wait for our imminent support for Windows containers. However, in my previous startup, we had built a cross-platform Xamarin.Forms based mobile app in C# and sheer curiosity provoked me to look at the latest state of Mono.

Mono is an open source implementation of Microsoft's .NET Framework. I was thrilled to discover that Ubuntu was in the list of supported Linux distributions. I immediately posted a response on the support issue asking the customer to use a custom image based off Ubuntu with the Mono runtime in it. That's when a kind customer, Kyle Dodson, Co-Founder, CTO at @ZapicInc posted an even better response that he had this scenario working on Shippable and he laid out his approach succintly. You can find his excellent response here. That eventually led to a discussion between Kyle and me about partnering for a blog with the intention of helping other customers jumpstart their C# CI efforts and the rest is history. I would like to thank Kyle for contributing to the sample and his deep technical know-how.

This blog is first in a series of articles and is basically a tutorial on how to do CI of a C# application using Shippable powered by Linux containers. In the next post, we will cover CI/CD of an ASP.NET application running in a Linux container on a K8s cluster on AKS.  

 

Mono vs .NET Core 2.0

.NET applications can be built using Linux containers using either the Mono 5.x or .NET Core 2.0. Use Mono if your application needs to use the full .NET Framework (upto .NET 4.6). More details on compatability can be found here and supported platforms can be found here.

.NET Core 2.0 implements a subset of the .NET Framework API's. Given its reduced footprint, it is great for fully contained XCOPY style deployments. It comes with a nifty CLI called dotnet that can be used for everything from creating projects and adding references to running your application. Both Mono and .NET Core 2.0 support Linux, OSX and Windows with Arm32 support in preview for .NET Core 2.0.

The .NET Application Architecture site is a great resource to help you figure out the best patterns, practices and right framework for building your .NET application.

 

Before we start

  • You will need a GitHub or Bitbucket account to login to Shippable. 
  • CI of your C# application will be implemented in a shippable.yml file. We have an entire section in our docs that will help you understand the basics of using CI in Shippable that you can find here

 

Docker Image with Mono and .NET Core SDKs

Before we start building our shippable.yml file, we will build our custom Ubuntu 16.04 docker image that has .NET Core 2.0 and the latest Mono 5 SDKs. Usually, you will need one of these two SDKs, but we are installing both SDKs, since our sample builds both .NET Core 2.0 and Mono C# applications.

The GitHub repo for building the custom image can be found here, which was riffed from Kyle's repo that installs the latest Mono 5 SDK

Also included is a shippable.yml file that pushes the Dockerfile to our account in dockerhub. 

If you do not want to build your own Docker image, you can Shippable's image in your CI project by adding the following lines in your shippable.yml:

build:  
pre_ci_boot: image_name: devopsrecipes/u16dotnet image_tag: "master.4" options: "-e HOME=/root"

 

Sample project

The code and shippable.yml file can be found in our public GitHub repository devops-recipes/ci-csharp-consoleapp. In this sample we demonstrate the following:

  • Building a solution with NuGet dependencies that targets .NET Core 2.0 SDK.
  • Building a solution with NuGet dependencies that targets .NET Framework 4.6.2 that uses the new csproj file format.
  • Building a solution with NuGet dependencies that targets .NET Framework 4.6.2 that uses the classic csproj file format.

 

Shippable.yml file

language: none

build:
# Cache NuGet packages cache: true cache_dir_list: - /root/.config/NuGet - /root/.nuget
# Use our custom Ubuntu-mono-dotnet20 image to boot the CI build container pre_ci_boot: image_name: devopsrecipes/u16dotnet image_tag: "master.4" options: "-e HOME=/root" ci: - dotnet --version - mono --version - msbuild /version # # Build and run an application that uses .NET Core 2.0 using the new csproj file format # - pushd "${SHIPPABLE_BUILD_DIR}/ModernProjectNet20" - echo 'Restoring NuGet dependencies for ModernProjectNet20 that uses the new csproj file format...' - msbuild /target:restore /property:Configuration=Release /property:Platform="Any CPU" /maxcpucount /toolsversion:15.0 ci-csharp-consoleapp.sln - echo 'Building ModernProjectNet20 using the .NET Core 2 SDK...' - msbuild /property:Configuration=Release /property:Platform="Any CPU" /maxcpucount /toolsversion:15.0 ci-csharp-consoleapp.sln - echo 'Running ModernProjectNet20 using dotnet CLI...' - pushd "${SHIPPABLE_BUILD_DIR}/ModernProjectNet20/MyApplication/bin/Release/netcoreapp2.0" # Run application using the .NET Core 2 CLI - dotnet ci-csharp-consoleapp.dll # Run application using Mono runtime which needs MONO_PATH env variable to be set - echo 'Running ModernProjectNet20 using mono 5...' - export MONO_PATH=/root/.nuget/packages/newtonsoft.json/10.0.3/lib/netstandard1.3:/root/.nuget/packages/newtonsoft.json/10.0.3/lib/netstandard1.0 - mono ci-csharp-consoleapp.dll - popd - pushd # # Build and run an application that uses .NET 4.6.2 using the new csproj file format # - pushd "${SHIPPABLE_BUILD_DIR}/ModernProjectNet462" - echo 'Restoring NuGet dependencies for ModernProjectNet462 that uses the new csproj file format...' - msbuild /target:restore /property:Configuration=Release /property:Platform="Any CPU" /maxcpucount /toolsversion:15.0 MyModernProject.sln - echo 'Building ModernProjectNet462 using .NET 4.6.2...' - msbuild /property:Configuration=Release /property:Platform="Any CPU" /maxcpucount /toolsversion:15.0 MyModernProject.sln - pushd "${SHIPPABLE_BUILD_DIR}/ModernProjectNet462/MyApplication/bin/Release/net462" - echo 'Running ModernProjectNet462 using mono 5...' - mono MyApplication.exe '{"Hello":"World"}' - popd - popd # # Build and run application that uses .NET 4.6.2 using the classic csproj file format # - pushd "${SHIPPABLE_BUILD_DIR}/ClassicProject" - echo 'Downloading NuGet...' - curl -sSL https://dist.nuget.org/win-x86-commandline/latest/nuget.exe -o nuget.exe - echo 'Restoring NuGet dependencies for ClassicProject that uses the classic csproj file format...' - mono nuget.exe restore -MSBuildPath /usr/lib/mono/msbuild/15.0/bin - echo 'Building ClassicProject using .NET 4.6.2...' - msbuild /property:Configuration=Release /property:Platform="Any CPU" /maxcpucount /toolsversion:15.0 MyClassicProject.sln - pushd "${SHIPPABLE_BUILD_DIR}/ClassicProject/MyApplication/bin/Release" - echo 'Running ClassicProject using mono 5...' - mono MyApplication.exe '{"Hello":"World"}' - popd - popd

 

Console link for the sample project running in Shippable is here.

Screen Shot 2017-12-07 at 2.00.16 PM.png