一键导入
dotnet-ado-patterns
Designing composable Azure DevOps YAML pipelines. Templates, variable groups, multi-stage, triggers.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Designing composable Azure DevOps YAML pipelines. Templates, variable groups, multi-stage, triggers.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
| name | dotnet-ado-patterns |
| description | Designing composable Azure DevOps YAML pipelines. Templates, variable groups, multi-stage, triggers. |
Composable Azure DevOps YAML pipeline patterns for .NET projects: template references with extends, stages, jobs, and steps keywords for hierarchical pipeline composition, variable groups and variable templates for centralized configuration, pipeline decorators for organization-wide policy injection, conditional insertion with ${{ if }} and ${{ each }} expressions, multi-stage pipelines (build, test, deploy), and pipeline triggers for CI, PR, and scheduled runs.
Version assumptions: Azure Pipelines YAML schema. DotNetCoreCLI@2 task for .NET 8/9/10 builds. Template expressions syntax v2.
Scope boundary: This skill owns composable pipeline design patterns for Azure DevOps YAML. Starter CI templates (basic build/test/pack) are owned by [skill:dotnet-add-ci] -- this skill extends those templates with advanced composition. CLI-specific release pipelines (build-package-release for CLI binaries) are owned by [skill:dotnet-cli-release-pipeline] -- this skill covers general pipeline patterns that CLI pipelines consume. ADO-unique features (environments with approvals, service connections, classic releases) are in [skill:dotnet-ado-unique].
Out of scope: Starter CI templates -- see [skill:dotnet-add-ci]. CLI release pipelines (tag-triggered build-package-release for CLI tools) -- see [skill:dotnet-cli-release-pipeline]. ADO-unique features (environments, service connections, classic releases) -- see [skill:dotnet-ado-unique]. Build/test specifics -- see [skill:dotnet-ado-build-test]. Publishing pipelines -- see [skill:dotnet-ado-publish]. GitHub Actions workflow patterns -- see [skill:dotnet-gha-patterns].
Cross-references: [skill:dotnet-add-ci] for starter templates that these patterns extend, [skill:dotnet-cli-release-pipeline] for CLI-specific release automation.
Stage templates define reusable pipeline stages that callers insert into their multi-stage pipeline:
# templates/stages/build-test.yml
parameters:
- name: dotnetVersion
type: string
default: '8.0.x'
- name: buildConfiguration
type: string
default: 'Release'
- name: projects
type: string
default: '**/*.sln'
stages:
- stage: Build
displayName: 'Build and Test'
jobs:
- job: BuildJob
pool:
vmImage: 'ubuntu-latest'
steps:
- task: UseDotNet@2
displayName: 'Install .NET SDK'
inputs:
packageType: 'sdk'
version: ${{ parameters.dotnetVersion }}
- task: DotNetCoreCLI@2
displayName: 'Restore'
inputs:
command: 'restore'
projects: ${{ parameters.projects }}
- task: DotNetCoreCLI@2
displayName: 'Build'
inputs:
command: 'build'
projects: ${{ parameters.projects }}
arguments: '-c ${{ parameters.buildConfiguration }} --no-restore'
# azure-pipelines.yml
trigger:
branches:
include:
- main
stages:
- template: templates/stages/build-test.yml
parameters:
dotnetVersion: '9.0.x'
buildConfiguration: 'Release'
projects: 'MyApp.sln'
- template: templates/stages/deploy.yml
parameters:
environment: 'staging'
Job templates encapsulate a complete job with its pool and steps:
# templates/jobs/dotnet-build.yml
parameters:
- name: dotnetVersion
type: string
default: '8.0.x'
- name: projects
type: string
jobs:
- job: Build
pool:
vmImage: 'ubuntu-latest'
steps:
- task: UseDotNet@2
inputs:
packageType: 'sdk'
version: ${{ parameters.dotnetVersion }}
- task: DotNetCoreCLI@2
displayName: 'Build'
inputs:
command: 'build'
projects: ${{ parameters.projects }}
arguments: '-c Release'
Step templates define reusable step sequences inserted into an existing job:
# templates/steps/dotnet-setup.yml
parameters:
- name: dotnetVersion
type: string
default: '8.0.x'
- name: nugetFeed
type: string
default: ''
steps:
- task: UseDotNet@2
displayName: 'Install .NET SDK ${{ parameters.dotnetVersion }}'
inputs:
packageType: 'sdk'
version: ${{ parameters.dotnetVersion }}
- ${{ if ne(parameters.nugetFeed, '') }}:
- task: NuGetAuthenticate@1
displayName: 'Authenticate NuGet feed'
- task: DotNetCoreCLI@2
displayName: 'Restore packages'
inputs:
command: 'restore'
projects: '**/*.sln'
${{ if ne(parameters.nugetFeed, '') }}:
feedsToUse: 'select'
vstsFeed: ${{ parameters.nugetFeed }}
jobs:
- job: Build
pool:
vmImage: 'ubuntu-latest'
steps:
- checkout: self
- template: templates/steps/dotnet-setup.yml
parameters:
dotnetVersion: '9.0.x'
nugetFeed: 'MyOrg/MyFeed'
- task: DotNetCoreCLI@2
displayName: 'Build'
inputs:
command: 'build'
arguments: '-c Release --no-restore'
The extends keyword enforces a required pipeline structure defined by an organization template. Callers cannot bypass the structure:
# templates/pipeline-policy.yml
parameters:
- name: stages
type: stageList
default: []
stages:
- stage: SecurityScan
displayName: 'Security Scan (Required)'
jobs:
- job: Scan
pool:
vmImage: 'ubuntu-latest'
steps:
- script: echo "Running mandatory security scan"
- ${{ each stage in parameters.stages }}:
- ${{ stage }}
- stage: Compliance
displayName: 'Compliance Check (Required)'
dependsOn:
- ${{ each stage in parameters.stages }}:
- ${{ stage.stage }}
jobs:
- job: Check
pool:
vmImage: 'ubuntu-latest'
steps:
- script: echo "Running compliance checks"
# azure-pipelines.yml (caller)
extends:
template: templates/pipeline-policy.yml
parameters:
stages:
- stage: Build
jobs:
- job: BuildApp
pool:
vmImage: 'ubuntu-latest'
steps:
- script: dotnet build -c Release
The extends template wraps caller-defined stages with mandatory security and compliance stages that cannot be removed.
Variable groups centralize configuration shared across multiple pipelines. Link them from Azure Pipelines Library:
variables:
- group: 'dotnet-build-settings'
- group: 'nuget-feed-credentials'
- name: buildConfiguration
value: 'Release'
Variable templates define reusable variable sets in YAML files:
# templates/variables/dotnet-defaults.yml
variables:
dotnetVersion: '8.0.x'
buildConfiguration: 'Release'
testResultsDirectory: '$(Build.ArtifactStagingDirectory)/test-results'
coverageDirectory: '$(Build.ArtifactStagingDirectory)/coverage'
# azure-pipelines.yml
variables:
- template: templates/variables/dotnet-defaults.yml
- name: projectPath
value: 'MyApp.sln'
Link variable groups to Azure Key Vault for secret management. Secrets are fetched at pipeline runtime:
# Reference in pipeline
variables:
- group: 'kv-production-secrets' # linked to Azure Key Vault
- name: nonSecretVar
value: 'some-value'
steps:
- script: |
echo "Using secret from Key Vault"
# $(sql-connection-string) resolves at runtime from Key Vault
env:
CONNECTION_STRING: $(sql-connection-string)
Key Vault-linked variable groups require a service connection with Key Vault access. Secret names in Key Vault map to variable names (hyphens become valid variable characters).
Pipeline decorators inject steps into every pipeline in an organization or project, enforcing policies without modifying individual pipeline files. Decorators are an ADO-exclusive feature with no GitHub Actions equivalent -- see [skill:dotnet-ado-unique] for implementation details including extension manifests, deployment guidance, and use case examples.
${{ if }} Expressionsparameters:
- name: runIntegrationTests
type: boolean
default: false
- name: targetEnvironment
type: string
default: 'development'
values:
- development
- staging
- production
stages:
- stage: Build
jobs:
- job: BuildJob
steps:
- script: dotnet build -c Release
- ${{ if eq(parameters.runIntegrationTests, true) }}:
- stage: IntegrationTests
dependsOn: Build
jobs:
- job: IntegrationTestJob
steps:
- script: dotnet test --filter Category=Integration
- ${{ if eq(parameters.targetEnvironment, 'production') }}:
- stage: ApprovalGate
dependsOn: Build
jobs:
- job: WaitForApproval
pool: server
steps:
- task: ManualValidation@0
inputs:
notifyUsers: 'release-managers@example.com'
instructions: 'Approve production deployment'
${{ each }} Iterationparameters:
- name: environments
type: object
default:
- name: development
pool: 'ubuntu-latest'
approvals: false
- name: staging
pool: 'ubuntu-latest'
approvals: true
- name: production
pool: 'ubuntu-latest'
approvals: true
stages:
- stage: Build
jobs:
- job: BuildJob
steps:
- script: dotnet build -c Release
- ${{ each env in parameters.environments }}:
- stage: Deploy_${{ env.name }}
displayName: 'Deploy to ${{ env.name }}'
dependsOn: Build
jobs:
- ${{ if eq(env.approvals, true) }}:
- job: Approve
pool: server
steps:
- task: ManualValidation@0
inputs:
instructions: 'Approve deployment to ${{ env.name }}'
- deployment: DeployApp
pool:
vmImage: ${{ env.pool }}
environment: ${{ env.name }}
strategy:
runOnce:
deploy:
steps:
- script: echo "Deploying to ${{ env.name }}"
# templates/steps/dotnet-test.yml
parameters:
- name: collectCoverage
type: boolean
default: false
steps:
- task: DotNetCoreCLI@2
displayName: 'Run tests'
inputs:
command: 'test'
projects: '**/*Tests.csproj'
${{ if eq(parameters.collectCoverage, true) }}:
arguments: '-c Release --collect:"XPlat Code Coverage"'
${{ else }}:
arguments: '-c Release'
- ${{ if eq(parameters.collectCoverage, true) }}:
- task: PublishCodeCoverageResults@2
displayName: 'Publish coverage'
inputs:
summaryFileLocation: '$(Agent.TempDirectory)/**/coverage.cobertura.xml'
trigger:
branches:
include:
- main
- release/*
stages:
- stage: Build
displayName: 'Build'
jobs:
- job: BuildJob
pool:
vmImage: 'ubuntu-latest'
steps:
- task: UseDotNet@2
inputs:
packageType: 'sdk'
version: '8.0.x'
- task: DotNetCoreCLI@2
displayName: 'Build'
inputs:
command: 'build'
projects: 'MyApp.sln'
arguments: '-c Release'
- task: DotNetCoreCLI@2
displayName: 'Publish'
inputs:
command: 'publish'
projects: 'src/MyApp/MyApp.csproj'
arguments: '-c Release -o $(Build.ArtifactStagingDirectory)/app'
- task: PublishPipelineArtifact@1
displayName: 'Upload artifact'
inputs:
targetPath: '$(Build.ArtifactStagingDirectory)/app'
artifactName: 'app'
- stage: Test
displayName: 'Test'
dependsOn: Build
jobs:
- job: UnitTests
pool:
vmImage: 'ubuntu-latest'
steps:
- task: UseDotNet@2
inputs:
packageType: 'sdk'
version: '8.0.x'
- task: DotNetCoreCLI@2
displayName: 'Run tests'
inputs:
command: 'test'
projects: '**/*Tests.csproj'
arguments: '-c Release --logger "trx;LogFileName=results.trx"'
- task: PublishTestResults@2
displayName: 'Publish test results'
condition: always()
inputs:
testResultsFormat: 'VSTest'
testResultsFiles: '**/results.trx'
- stage: DeployStaging
displayName: 'Deploy to Staging'
dependsOn: Test
jobs:
- deployment: DeployStaging
pool:
vmImage: 'ubuntu-latest'
environment: 'staging'
strategy:
runOnce:
deploy:
steps:
- download: current
artifact: app
- script: echo "Deploying to staging"
- stage: DeployProduction
displayName: 'Deploy to Production'
dependsOn: DeployStaging
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
jobs:
- deployment: DeployProduction
pool:
vmImage: 'ubuntu-latest'
environment: 'production'
strategy:
runOnce:
deploy:
steps:
- download: current
artifact: app
- script: echo "Deploying to production"
stages:
- stage: Build
jobs:
- job: BuildJob
steps:
- script: dotnet build -c Release
- stage: UnitTests
dependsOn: Build
jobs:
- job: UnitTestJob
steps:
- script: dotnet test --filter Category!=Integration
- stage: IntegrationTests
dependsOn: Build
jobs:
- job: IntegrationTestJob
steps:
- script: dotnet test --filter Category=Integration
# Deploy only if BOTH test stages succeed
- stage: Deploy
dependsOn:
- UnitTests
- IntegrationTests
condition: and(succeeded('UnitTests'), succeeded('IntegrationTests'))
jobs:
- deployment: DeployApp
environment: 'production'
strategy:
runOnce:
deploy:
steps:
- script: echo "Deploying"
trigger:
branches:
include:
- main
- release/*
exclude:
- feature/experimental/*
paths:
include:
- src/**
- tests/**
- '*.sln'
- Directory.Build.props
- Directory.Packages.props
exclude:
- docs/**
- '*.md'
tags:
include:
- 'v*'
pr:
branches:
include:
- main
- release/*
paths:
include:
- src/**
- tests/**
exclude:
- docs/**
drafts: false # do not trigger on draft PRs
schedules:
- cron: '0 6 * * 1-5'
displayName: 'Weekday nightly build'
branches:
include:
- main
always: false # only run if there are changes since last run
- cron: '0 0 * * 0'
displayName: 'Weekly full validation'
branches:
include:
- main
always: true # run even without changes
Trigger a pipeline when another pipeline completes:
resources:
pipelines:
- pipeline: buildPipeline
source: 'MyApp-Build'
trigger:
branches:
include:
- main
stages:
- stage: DeployAfterBuild
jobs:
- deployment: Deploy
environment: 'staging'
strategy:
runOnce:
deploy:
steps:
- download: buildPipeline
artifact: app
- script: echo "Deploying build from upstream pipeline"
type: boolean is expected causes a validation error before the pipeline runs; always match types exactly.extends templates cannot be overridden -- callers cannot inject steps before or after the mandatory stages; this is by design for policy enforcement.${{ variables.mySecret }} resolves at compile time when secrets are not yet available; use $(mySecret) runtime syntax instead.${{ each }} iterates at compile time -- the loop generates YAML before the pipeline runs; runtime variables cannot be used as the iteration source.trigger: none and pr: none -- omitting both trigger and pr sections enables default CI triggering on all branches; explicitly set trigger: none to disable./ or ./; use src/** not ./src/**.branches.include filter applies after the schedule fires; the schedule itself is only evaluated from the default branch YAML.azure-pipelines.yml file path.Guidelines for writing TUnit tests in .NET, including setup, assertions, async testing, and best practices. Use when writing unit tests with TUnit framework, setting up TUnit in a .NET project, or migrating from other test frameworks to TUnit.
Hosted services, background jobs, outbox patterns, and graceful shutdown handling for ASP.NET Core applications. Includes patterns for reliable job processing, distributed systems, and lifecycle management. Use when implementing background processing in ASP.NET Core applications, handling outbox patterns for reliable message delivery, or managing graceful service shutdown.
Design stable, compatible public APIs using extend-only design principles. Manage API compatibility, wire compatibility, versioning, naming conventions, parameter ordering, and return types for NuGet packages and distributed systems. Use when designing public APIs for NuGet packages or libraries, making changes to existing public APIs, planning wire format changes for distributed systems, or reviewing pull requests for breaking changes.
Write modern, high-performance C# code using records, pattern matching, value objects, async/await, Span<T>/Memory<T>, and best-practice API design patterns. Emphasizes functional-style programming with C# 12+ features. Use when writing new C# code or refactoring existing code, designing public APIs for libraries or services, optimizing performance-critical code paths, or building async/await-heavy applications.
Choosing the right concurrency abstraction in .NET - from async/await for I/O to Channels for producer/consumer to Akka.NET for stateful entity management. Covers both high-level abstractions and low-level synchronization primitives. Use when deciding how to handle concurrent operations in .NET, evaluating whether to use async/await, Channels, or Akka.NET, or managing state across multiple concurrent entities.
Design .NET types for performance. Covers struct vs class decision matrix, sealed by default, readonly structs, ref struct and Span/Memory selection, FrozenDictionary, ValueTask, and collection return types. Use when designing new types and APIs, reviewing code for performance issues, choosing between class, struct, and record, or working with collections and enumerables.