关于 dotnet-ado-patterns
为 .NET 项目提供 Azure DevOps YAML 管道的可组合设计模式,面向需要复用模板、集中配置和构建多阶段流程的开发团队。内容涵盖从模板层级组合到触发器及条件插入。
主要功能
- 01组合阶段、作业和步骤模板
- 02使用 extends 定义强制管道结构
- 03集中管理变量组和变量模板
- 04用条件表达式生成管道内容
- 05配置多阶段流程与触发器
适用场景
- 01跨多个 .NET 管道复用构建步骤
- 02为组织管道注入安全与合规阶段
- 03按环境条件生成部署流程
- 04设置 CI、PR 和定时运行触发器
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ wshaddix/ dotnet-skills --skill dotnet-ado-patterns安装后在 Claude Code 中输入/dotnet-ado-patterns确认已加载。
详细介绍
dotnet-ado-patterns
适用于 .NET 项目的可组合 Azure DevOps YAML 管道模式:使用 extends、stages、jobs 和 steps 关键字引用模板,以分层组合管道;使用变量组和变量模板实现集中式配置;使用管道装饰器注入组织级策略;使用 ${{ if }} 和 ${{ each }} 表达式进行条件插入;使用多阶段管道(生成、测试、部署);以及配置用于 CI、PR 和计划运行的管道触发器。
版本假设: Azure Pipelines YAML 架构。使用 DotNetCoreCLI@2 任务生成 .NET 8/9/10。模板表达式语法 v2。
范围界定: 此技能负责 Azure DevOps YAML 的可组合管道设计模式。入门 CI 模板(基本生成/测试/打包)由 [skill:dotnet-add-ci] 负责——此技能通过高级组合扩展这些模板。CLI 专属发布管道(用于 CLI 二进制文件的 build-package-release)由 [skill:dotnet-cli-release-pipeline] 负责——此技能介绍 CLI 管道所使用的通用管道模式。ADO 独有功能(包含审批的环境、服务连接、经典发布)见 [skill:dotnet-ado-unique]。
不在范围内: 入门 CI 模板——请参阅 [skill:dotnet-add-ci]。CLI 发布管道(由标签触发、面向 CLI 工具的 build-package-release)——请参阅 [skill:dotnet-cli-release-pipeline]。ADO 独有功能(环境、服务连接、经典发布)——请参阅 [skill:dotnet-ado-unique]。生成/测试细节——请参阅 [skill:dotnet-ado-build-test]。发布管道——请参阅 [skill:dotnet-ado-publish]。GitHub Actions 工作流模式——请参阅 [skill:dotnet-gha-patterns]。
交叉引用:[skill:dotnet-add-ci] 提供这些模式所扩展的入门模板;[skill:dotnet-cli-release-pipeline] 提供 CLI 专属发布自动化。
模板引用
阶段模板
阶段模板定义可复用的管道阶段,调用方可将其插入多阶段管道:
# 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'
作业模板
作业模板封装包含池和步骤的完整作业:
# 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'
步骤模板
步骤模板定义可复用的步骤序列,可插入现有作业:
# 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'
扩展模板(强制管道结构)
extends 关键字会强制使用由组织模板定义的必需管道结构。调用方无法绕过该结构:
# 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
extends 模板会将调用方定义的阶段包装在强制性的安全与合规阶段中,这些阶段无法移除。
变量组和变量模板
变量组
变量组集中管理多个管道共享的配置。请从 Azure Pipelines Library 链接变量组:
variables:
- group: 'dotnet-build-settings'
- group: 'nuget-feed-credentials'
- name: buildConfiguration
value: 'Release'
变量模板
变量模板在 YAML 文件中定义可复用的变量集:
# 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'
集成 Key Vault 的变量组
将变量组链接到 Azure Key Vault 以管理机密。机密会在管道运行时获取:
# 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 的变量组需要具有 Key Vault 访问权限的服务连接。Key Vault 中的机密名称会映射为变量名(连字符会转换为有效的变量字符)。
管道装饰器
管道装饰器会向组织或项目中的每个管道注入步骤,无需修改各个管道文件即可强制执行策略。装饰器是 ADO 独有功能,GitHub Actions 中没有对应功能——有关实现细节(包括扩展清单、部署指南和用例示例),请参阅 [skill:dotnet-ado-unique]。
条件插入
${{ if }} 表达式
parameters:
- 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: '[email protected]'
instructions: 'Approve production deployment'
${{ each }} 迭代
parameters:
- 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"
管道触发器
CI 触发器
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 触发器
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
流水线资源触发器
在另一条流水线完成时触发流水线:
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"
Agent 注意事项
- 模板参数类型在编译时强制检查 -- 在要求
type: boolean的位置传入字符串,会在流水线运行前导致验证错误;务必确保类型完全匹配。 - 无法覆盖
extends模板 -- 调用方无法在必需阶段之前或之后插入步骤;这是为执行策略而做的设计。 - 模板表达式中无法使用变量组机密 -- 当机密尚不可用时,
${{ variables.mySecret }}会在编译时求值;请改用$(mySecret)运行时语法。 ${{ each }}在编译时迭代 -- 循环会在流水线运行前生成 YAML;不能使用运行时变量作为迭代源。- CI 和 PR 触发器与
trigger: none和pr: none互斥 -- 省略trigger和pr部分会启用所有分支的默认 CI 触发;请显式设置trigger: none以禁用触发。 - 触发器中的路径筛选器使用相对于仓库根目录的路径 -- 路径前不要加
/或./;使用src/**,而不是./src/**。 - 定时触发器始终先在默认分支上运行 --
branches.include筛选器会在计划触发后应用;计划本身只会根据默认分支中的 YAML 进行评估。 - 流水线资源触发器需要源流水线名称,而不是 YAML 文件路径 -- 请使用 ADO 中显示的流水线名称,而不是
azure-pipelines.yml文件路径。