The new (not so new) Azure DevOps's YAML pipelines are fantastic but can become quite complicated. It's especially true if you have many little projects or microservices in the same git repository that you want to build and deploy independently.

Template

Fortunately, it's possible to build YAML templates that can be reused through many different pipelines. Doing so, simplify the maintenance, and give you better segregation. It's possible to create templates for any level: stages, jobs, or tasks. I like to create templates for jobs and tasks and let each build pipeline define its stages. It's not all pipelines that have the same packaging sequence or the same number of available environments.

My Recipe

I'll share my standard "base" template that I use as a starting point in all my projects. The idea is relatively simple:

  1. Restore NuGet packages
  2. Build
  3. Run unit tests
  4. Run integration tests
  5. Package
  6. Publish package

be.job.template.build.yml

parameters:
- name: buildPlatform
  type: string
  default: 'x64'

- name: buildConfiguration
  type: string
  default: 'Release'

- name: solutionPath
  type: string

- name: artifactName
  type: string
  default: 'backend-package'

jobs:
- job: 'BuildBE'
  displayName: 'Build Backend'
  pool: 
    vmImage: 'windows-latest'
  steps:
  - checkout: self
    fetchDepth: 100
    lfs: false
  - task: DotNetCoreCLI@2
    displayName: Restore
    inputs:
        command: restore
        projects: '${{ parameters.solutionPath }}*.sln'
        noCache: true
  - task: DotNetCoreCLI@2
    displayName: 'Build'
    inputs:
        command: 'build'
        projects: '${{ parameters.solutionPath }}*.sln'
        arguments: '--configuration ${{ parameters.buildConfiguration }}'
  - task: DotNetCoreCLI@2
    displayName: 'Unit tests'
    inputs:
        command: 'test'
        projects: '${{ parameters.solutionPath }}**/*.Tests.Unit.csproj'
        arguments: '--configuration ${{ parameters.buildConfiguration }} --collect "Code Coverage"'
        verbosityRestore: Minimal
  - task: DotNetCoreCLI@2
    displayName: 'Integration tests'
    inputs:
        command: 'test'
        projects: '${{ parameters.solutionPath }}**/*.Tests.Integration.csproj'
        arguments: '--configuration ${{ parameters.buildConfiguration }} --collect "Code Coverage"'
        verbosityRestore: Minimal

  - task: DotNetCoreCLI@2
    displayName: 'Package'
    inputs:
        command: publish
        publishWebProjects: false
        projects: ${{ parameters.solutionPath }}*.sln
        arguments: '--configuration ${{ parameters.buildConfiguration }} --output $(Build.ArtifactStagingDirectory) --no-restore'
        zipAfterPublish: True
  - task: PublishBuildArtifacts@1
    displayName: 'Publish Artifacts'
    inputs:
        ArtifactName: '${{ parameters.artifactName }}'

If you are not using the latest version of .NET, you may require an extra task at the beginning to install the .NET version you need.

 - task: UseDotNet@2
    displayName: 'Use DotNet'
    inputs:
        version: '2.x'
        packageType: runtime

Then, creating a new build pipeline can be done in a matter of minutes and it's no more than 10 lines of code!

trigger: 
  batch: true
  paths:
    include:
    - Sources/PathToYourSln/*

jobs:
- template: 'templates/be.job.template.build.yml'
  parameters:
    solutionPath: Sources/PathToYourSln/