In my last post I summed up my Azure DevOps setup at home. When the machine is running, the application is set up and the services are started the first tasks can be added. In this post I want to show how to create a simple solution that offers the following functionality.
- A simple library written in C# with basic functionality that can be extended easily.
- NUnit tests to implement test functionality. This helps improving code quality and prevents the library from getting published with errors.
- A file with Azure DevOps definitions called “azure-pipelines.yml”. This defines what the Azure DevOps server should do.
- A “NuGet.config” file to specify the feed the generated artifact (the .nupkg file) gets pushed to.
This can be seen as boilerplate. As soon as the library project is set up, the builds run through and the tests are executed everything else is copy, paste and good old programming. This means: The pipeline and deployment setup is implemented with a very simple projects which reduces the possible sources of errors and it can easily be tested. As soon as this is done the real development can start with little to no errors on the DevOps side. The following script just needs .NET core installed or at least the “dotnet” executable in the PATH. It creates the the solution including a library- and a test project. Additionally a example NuGet.config and a azure-pipeline.yml definition are created.
@echo off
set SolutionName=Example.Solution
set LibraryName=Example.Library
set TestsName=Example.Tests
set DevOpsUrl=https://devops.example.com
set DevOpsSpace=MySpace
set DevOpsProj=MyProj
set NuGetBaseUrl=%DevOpsUrl%/%DevOpsSpace%/%DevOpsProj%
set RepoAlias=MyRepo
dotnet new sln -o %SolutionName%
dotnet new classlib -o %SolutionName%\%LibraryName%
dotnet new nunit -o %SolutionName%\%TestsName%
dotnet sln %SolutionName%\%SolutionName%.sln^
add %SolutionName%\%LibraryName%\%LibraryName%.csproj
dotnet sln %SolutionName%\%SolutionName%.sln^
add %SolutionName%\%TestsName%\%TestsName%.csproj
dotnet add %SolutionName%\%TestsName%\%TestsName%.csproj^
reference %SolutionName%\%LibraryName%
set CLASSFILE1=%SolutionName%\%LibraryName%\Class1.cs
echo using System;>%CLASSFILE1%
echo namespace Example.Library>>%CLASSFILE1%
echo {>>%CLASSFILE1%
echo public class Class1>>%CLASSFILE1%
echo {>>%CLASSFILE1%
echo public static int GetValue() { return 4711; }>>%CLASSFILE1%
echo }>>%CLASSFILE1%
echo }>>%CLASSFILE1%
set UNITTEST1=%SolutionName%\%TestsName%\UnitTest1.cs
echo using NUnit.Framework;>%UNITTEST1%
echo using Example.Library;>>%UNITTEST1%
echo namespace Example.Tests>>%UNITTEST1%
echo {>>%UNITTEST1%
echo public class UnitTest1>>%UNITTEST1%
echo {>>%UNITTEST1%
echo [SetUp]>>%UNITTEST1%
echo public void Setup()>>%UNITTEST1%
echo {>>%UNITTEST1%
echo }>>%UNITTEST1%
echo [Test]>>%UNITTEST1%
echo public void Test1()>>%UNITTEST1%
echo {>>%UNITTEST1%
echo Assert.AreEqual(4711, Class1.GetValue());>>%UNITTEST1%
echo }>>%UNITTEST1%
echo }>>%UNITTEST1%
echo }>>%UNITTEST1%
set NUGETCONFIG=%SolutionName%\NuGet.config
echo ^<?xml version="1.0" encoding="utf-8"?^>>%NUGETCONFIG%
echo ^<configuration^>>>%NUGETCONFIG%
echo ^<packageSources^>>>%NUGETCONFIG%
echo ^<clear /^>>>%NUGETCONFIG%
echo ^<add key="%RepoAlias%" value="%NuGetBaseUrl%/_packaging/%RepoAlias%/nuget/v3/index.json" /^>>>%NUGETCONFIG%
echo ^</packageSources^>>>%NUGETCONFIG%
echo ^</configuration^>>>%NUGETCONFIG%
set AZUREPIPELINE=%SolutionName%\azure-pipelines.yml
echo trigger:>%AZUREPIPELINE%
echo - master>>%AZUREPIPELINE%
echo.>>%AZUREPIPELINE%
echo variables:>>%AZUREPIPELINE%
echo Major: '1'>>%AZUREPIPELINE%
echo Minor: '0'>>%AZUREPIPELINE%
echo Patch: '0'>>%AZUREPIPELINE%
echo BuildConfiguration: 'Release'>>%AZUREPIPELINE%
echo.>>%AZUREPIPELINE%
echo pool:>>%AZUREPIPELINE%
echo name: 'Default'>>%AZUREPIPELINE%
echo.>>%AZUREPIPELINE%
echo steps:>>%AZUREPIPELINE%
echo.>>%AZUREPIPELINE%
echo - script: dotnet build>>%AZUREPIPELINE%
echo displayName: 'Build library'>>%AZUREPIPELINE%
echo.>>%AZUREPIPELINE%
echo - script: dotnet test>>%AZUREPIPELINE%
echo displayName: 'Test library'>>%AZUREPIPELINE%
echo.>>%AZUREPIPELINE%
echo - script: dotnet publish -c $(BuildConfiguration)>>%AZUREPIPELINE%
echo displayName: 'Publish library'>>%AZUREPIPELINE%
echo.>>%AZUREPIPELINE%
echo - script: dotnet pack -c $(buildConfiguration) -p:PackageVersion=$(Major).$(Minor).$(Patch)>>%AZUREPIPELINE%
echo displayName: 'Pack library'>>%AZUREPIPELINE%
echo.>>%AZUREPIPELINE%
echo - script: dotnet nuget push --source "%RepoAlias%" --api-key az .\%LibraryName%\bin\$(buildConfiguration)\*.nupkg --skip-duplicate>>%AZUREPIPELINE%
echo displayName: 'Push library'>>%AZUREPIPELINE%
set TASKSJSON=%SolutionName%\%LibraryName%\.vscode\tasks.json
mkdir %SolutionName%\%LibraryName%\.vscode
echo {>>%TASKSJSON%
echo "version": "2.0.0",>>%TASKSJSON%
echo "tasks": [>>%TASKSJSON%
echo {>>%TASKSJSON%
echo "label": "build",>>%TASKSJSON%
echo "command": "dotnet",>>%TASKSJSON%
echo "type": "process",>>%TASKSJSON%
echo "args": [>>%TASKSJSON%
echo "build",>>%TASKSJSON%
echo "${workspaceFolder}/%LibraryName%/%LibraryName%.csproj",>>%TASKSJSON%
echo "/property:GenerateFullPaths=true",>>%TASKSJSON%
echo "/consoleloggerparameters:NoSummary">>%TASKSJSON%
echo ],>>%TASKSJSON%
echo "problemMatcher": "$msCompile">>%TASKSJSON%
echo }>>%TASKSJSON%
echo ]>>%TASKSJSON%
echo }>>%TASKSJSON%
In the folder containing the solution (*.sln file) the following commands can be executed and produce the corresponding output.
dotnet build
dotnet test
dotnet publish
dotnet pack
I recommend working with Visual Studio Code. Support for .NET is great and it is highly configurable. The library project contains a folder “.vscode” with a “tasks.json” file. This file contains a single build definition. The definition object in the array “tasks” can be copied and modified to support different task types like “test”, “publish” and “pack” directly from Visual Studio Code. They can be run by pressing [Crtl]+[Shift]+[P] and selecting the following:
A defined task can the be selected and is executed in the terminal:
I hope this script helps to create projects. Writing the correct pipeline definitions and configs took me some time to learn. Now the projects are easily created.