Aspire Publish¶
Generate Azure DevOps pipeline YAML and infrastructure configuration from your AppHost using aspire publish.
📋 Overview¶
Instead of maintaining pipeline YAML files by hand, you can define your application's services, security, and infrastructure in C# inside the Aspire AppHost. Running aspire publish --output-path ./ from your application root directory generates all the pipeline files, variables, and auth configuration your project needs.
Why Aspire Publish?¶
| Benefit | Description |
|---|---|
| Type safety | Compile-time validation prevents misconfigured pipeline variables |
| Single source | AppHost is the single source of truth for project structure |
| Refactoring | Rename a permission or service and all generated files update |
| Discoverability | IntelliSense shows available services, options, and permissions |
| Consistency | All Forge projects follow the same generation patterns |
🚀 Quick Start¶
1. Configure Your AppHost¶
using Aspire.Hosting;
using SAIF.Platform.Aspire.Hosting;
using SAIF.Platform.Aspire.Hosting.Generation.Services;
using SAIF.Platform.Aspire.Hosting.Generation.Infrastructure.Security;
var builder = DistributedApplication.CreateBuilder(args);
// Environment setup
builder.AddSaifEnvironment()
.WithApplicationName("MyApp")
.WithBusinessDomain("it")
.WithOwner("MyTeam");
// API service
var api = builder.AddApiService<Projects.MyApp_Api>("myapp");
// Frontend (optional)
var frontend = builder.AddFrontendService("frontend", "../MyApp.Frontend")
.WithEnvironment("BACKEND_URL", api.GetEndpoint("http"));
api.WithReference(frontend);
builder.Build().Run();
2. Publish¶
From your application root directory:
3. Review Generated Files¶
.azdo/
├── azure-pipelines-api.yml
├── azure-pipelines-api-pr.yml
└── vars/
├── base.yml
└── api.yml
🏗️ Environment Setup¶
Every AppHost starts with AddSaifEnvironment():
builder.AddSaifEnvironment()
.WithApplicationName("Emmjoh")
.WithBusinessDomain("it")
.WithOwner("Architecture");
Optional Configuration¶
builder.AddSaifEnvironment()
.WithApplicationName("Emmjoh")
.WithBusinessDomain("it")
.WithOwner("Architecture")
.WithTenant("corporate")
.WithPipelineDefaults(o =>
{
o.DotNetVersion = "10.x";
o.NodeVersion = "22.x";
o.TemplateRef = "refs/heads/releases/v3";
o.SecretsVariableGroupName = "my-secrets";
});
See API Reference — Environment for all available methods and defaults.
📦 Service Registration¶
API Service¶
Registers a .NET API project for both local development (aspire run) and pipeline generation (aspire publish). API services are standalone projects with their own repository and pipeline.
Generated project ID: {domain}-api-{type}-{name} (e.g., it-api-exp-myapp)
API Type Options¶
var api = builder.AddApiService<Projects.MyApp_Api>("myapp",
new ApiPipelineOptions { ApiType = ApiType.Process });
ApiType |
Short Code | Example Project ID | Use Case |
|---|---|---|---|
Experience |
exp |
it-api-exp-myapp |
BFF / frontend-facing APIs |
Process |
proc |
it-api-proc-myapp |
Business process APIs |
System |
sys |
it-api-sys-myapp |
System/infrastructure APIs |
None |
none |
it-api-none-myapp |
No API type classification |
OpenAPI Specification¶
var api = builder.AddApiService<Projects.MyApp_Api>("myapp",
new ApiPipelineOptions { OpenApiFileName = "openapi.v1.yaml" });
Frontend Service¶
Registers a React/Vite frontend for local development and pipeline generation. The frontend can be used in two ways:
- Alongside an API — the frontend lives in the same repository as the API, sharing an AppHost (
.WithReference(api)) - Standalone web project — its own repository and AppHost with a dedicated web pipeline
Generated project ID: {domain}-web-{name} (e.g., it-web-myapp)
The second parameter is the path to the frontend project directory, relative to the AppHost.
Event Service¶
Registers a virtual event service for pipeline generation only (no runnable project). Event services are standalone projects with their own repository, TypeSpec definitions, and pipeline.
Generated project ID: events-{name} (e.g., events-myapp)
Event Service Options¶
builder.AddEventService("events",
new EventServicePipelineOptions { OpenApiFileName = "openapi.yaml" });
Subscription Service¶
Registers an Azure Functions subscription project. Subscription services are a feature paired with an API — they live in the same repository and share an AppHost with their API project. Wraps the standard Aspire AddAzureFunctionsProject<T>() and adds pipeline annotations for discovery.
var subscription = builder.AddSubscriptionService<Projects.MyApp_Subscriptions>("subscriptions")
.WithReference(serviceBus)
.WithReference(database);
Generated project ID: {domain}-func-{appName} (e.g., it-func-myapp)
🔒 Security Configuration¶
App Roles and Business Roles¶
builder.AddSecurity()
.WithCorporateAssignment(new AppRoleAssignment(
new BusinessRole("ClaimsAdjuster"),
[new AppRole("Claims.Read"), new AppRole("Claims.Write")]))
.WithExternalAssignment(new AppRoleAssignment(
new BusinessRole("InjuredWorker"),
[new AppRole("Claims.Submit")]));
See Aspire Security Configuration for the full security setup guide.
Upstream API Dependencies¶
builder.AddUpstreamApi("it-api-proc-orders")
.WithScope(new Scope("Orders.Read"))
.WithAppRole(new AppRole("Orders.App.Read"));
See API Reference — Security for all available methods.
🗃️ Infrastructure Features¶
Oracle Database¶
Declare an Oracle Database dependency to include the project ID variable group in generated API pipelines:
📂 Generated Files¶
The files generated depend on which services and features are registered in your AppHost.
Pipeline Files¶
| Generated File | Condition |
|---|---|
.azdo/azure-pipelines-api.yml |
API service registered |
.azdo/azure-pipelines-api-pr.yml |
API service registered |
.azdo/azure-pipelines-web.yml |
Frontend registered |
.azdo/azure-pipelines-web-pr.yml |
Frontend registered |
.azdo/azure-pipelines-sub.yml |
Subscription detected |
.azdo/azure-pipelines-sub-pr.yml |
Subscription detected |
.azdo/azure-pipelines-auth.yml |
Security registered |
.azdo/azure-pipelines-auth-pr.yml |
Security registered |
.azdo/azure-pipelines-event-service.yml |
Event service registered |
.azdo/azure-pipelines-event-service-pr.yml |
Event service registered |
Variable Files¶
| Generated File | Condition |
|---|---|
.azdo/vars/base.yml |
Always |
.azdo/vars/api.yml |
API service registered |
.azdo/vars/web.yml |
Frontend registered |
.azdo/vars/sub.yml |
Subscription detected |
.azdo/vars/auth.yml |
Security registered |
.azdo/vars/event-service.yml |
Event service registered |
Security Configuration Files¶
| Generated File | Condition |
|---|---|
infra/api/config.yml |
Security registered |
infra/auth/corp/config.yml |
Corporate assignments configured |
infra/auth/ext/user/business-role-app-role.yml |
External assignments configured |
infra/auth/ext/app/authorized-apps.yml |
Upstream API with external scopes |
🎯 Complete Example¶
This example shows all features working together:
using Aspire.Hosting;
using SAIF.Platform.Aspire.Hosting;
using SAIF.Platform.Aspire.Hosting.Generation.Features;
using SAIF.Platform.Aspire.Hosting.Generation.Services;
using SAIF.Platform.Aspire.Hosting.Generation.Infrastructure.Security;
var builder = DistributedApplication.CreateBuilder(args);
// ── Environment ──────────────────────────────────────────────────────
builder.AddSaifEnvironment()
.WithApplicationName("MyApp")
.WithBusinessDomain("it")
.WithOwner("Architecture");
// ── API ──────────────────────────────────────────────────────────────
var api = builder.AddApiService<Projects.MyApp_Api>("myapp");
// ── Security ─────────────────────────────────────────────────────────
builder.AddSecurity()
.WithCorporateAssignment(new AppRoleAssignment(
new BusinessRole("ClaimsAdjuster"),
[new AppRole("Claims.Read"), new AppRole("Claims.Write")]))
.WithExternalAssignment(new AppRoleAssignment(
new BusinessRole("InjuredWorker"),
[new AppRole("Claims.Submit")]));
builder.AddUpstreamApi("it-api-proc-orders")
.WithScope(new Scope("Orders.Read"))
.WithAppRole(new AppRole("Orders.App.Read"));
// ── Frontend ─────────────────────────────────────────────────────────
var frontend = builder.AddFrontendService("frontend", "../MyApp.Frontend")
.WithEnvironment("BACKEND_URL", api.GetEndpoint("http"));
api.WithReference(frontend);
// ── Data ─────────────────────────────────────────────────────────────
#pragma warning disable ASPIRECOSMOSDB001
var cosmosdb = builder.AddAzureCosmosDB("cosmosdb")
.RunAsEmulator(emulator => emulator.WithLifetime(ContainerLifetime.Persistent));
#pragma warning restore ASPIRECOSMOSDB001
var database = cosmosdb.AddCosmosDatabase("CosmosDbConnection", "it-api-exp-myapp");
database.AddContainer("Claims", "/PartitionKey");
api.WithReference(database);
// ── Service Bus + Subscription ───────────────────────────────────────
var serviceBus = builder.AddAzureServiceBus("sbnamespace")
.RunAsEmulator(emulator => emulator.WithLifetime(ContainerLifetime.Persistent));
var subscription = builder.AddSubscriptionService<Projects.MyApp_Subscriptions>("subscriptions")
.WithReference(serviceBus)
.WithReference(database);
// ── Event Service ────────────────────────────────────────────────────
builder.AddEventService("events");
builder.Build().Run();
🔍 Aspire Dashboard¶
When running locally with aspire run, the Aspire dashboard shows all registered resources with their types and states:
| Icon | Resource Type | State | Description |
|---|---|---|---|
| Pipeline | Pipelines | Configured | Pipeline generation registered |
| Lock Closed | Security | Configured | Auth configuration registered |
| Event Service | Configured | Event service pipeline ready | |
| Link | Upstream API | Configured | External API dependency declared |
Runnable resources (API, Frontend, Functions) appear with their standard Aspire states (Running, Starting, etc.).
✅ Verification¶
After publishing:
- Check generated files — Verify pipeline YAML and variable files exist in
.azdo/ - Compare with existing — Review diffs if replacing hand-maintained pipelines
- Validate YAML — Ensure variable references and template paths are correct
- Test PR pipeline — Create a PR to validate the generated PR pipelines work
💡 Tips¶
Migrate Existing Projects¶
To migrate a project from hand-maintained pipelines to Aspire publish:
- Add
SAIF.Platform.Aspire.Hostingto your AppHost project - Configure services and security in
AppHost.cs(see examples above) - Run
aspire publish --output-path ./from the application root directory to generate files - Review the diff — generated files write directly to
.azdo/matching existing filenames and directory structure, overwriting in place - Commit and validate in a PR pipeline run
When to Re-Publish¶
Re-run aspire publish --output-path ./ whenever you:
- Add or remove a service (API, frontend, subscription, event service)
- Change security configuration (roles, permissions, upstream APIs)
- Update pipeline defaults (SDK versions, template ref)
- Change the application name or business domain
Working Example¶
The Forge foundry includes a complete working example at foundry/dotnet/aspire-publish/. Use it as a reference for all supported features.
📖 API Reference¶
All extension methods are in the SAIF.Platform.Aspire.Hosting package. Methods extend either IDistributedApplicationBuilder (top-level registration) or IResourceBuilder<T> (fluent configuration).
Environment¶
| Method | Parameters | Description |
|---|---|---|
AddSaifEnvironment() |
string name = "saif" |
Registers the SAIF environment and enables pipeline generation. Call this first. |
.WithApplicationName() |
string applicationName |
Sets the application name used in project ID computation and pipeline variables. Required. |
.WithBusinessDomain() |
string domain |
Sets the business domain prefix (it, claims, hr, etc.). Required. |
.WithOwner() |
string owner |
Sets the owning team name. Required. |
.WithTenant() |
string tenant |
Overrides the deployment tenant. Default: "corporate". |
.WithPipelineDefaults() |
Action<PipelineDefaults> configure |
Overrides SDK versions, template ref, or secrets variable group. See Pipeline Defaults. |
Services¶
| Method | Parameters | Returns | Description |
|---|---|---|---|
AddApiService<TProject>() |
string name, ApiPipelineOptions? options = null |
IResourceBuilder<ProjectResource> |
Registers a .NET API project. Runs during aspire run and generates pipelines on publish. TProject must implement IProjectMetadata. |
AddFrontendService() |
string name, string appDirectory, string runScriptName = "dev" |
IResourceBuilder<JavaScriptAppResource> |
Registers a React/Vite frontend. appDirectory is relative to the AppHost. |
AddEventService() |
string name, EventServicePipelineOptions? options = null |
IResourceBuilder<EventServiceResource> |
Registers a virtual event service (pipeline generation only — no runnable project). |
AddSubscriptionService<TProject>() |
string name |
IResourceBuilder<AzureFunctionsProjectResource> |
Registers an Azure Functions subscription project. Wraps AddAzureFunctionsProject<T>() and adds pipeline annotations. TProject must implement IProjectMetadata. Requires an API service in the same AppHost. |
Security¶
| Method | Parameters | Returns | Description |
|---|---|---|---|
AddSecurity() |
string name = "security" |
IResourceBuilder<SecurityResource> |
Registers security resource for auth config generation. |
.WithCorporateAssignment() |
AppRoleAssignment assignment |
IResourceBuilder<SecurityResource> |
Adds a corporate (Entra ID) business role → app role mapping. |
.WithExternalAssignment() |
AppRoleAssignment assignment |
IResourceBuilder<SecurityResource> |
Adds an external (Okta) business role → app role mapping. |
AddUpstreamApi() |
string projectId |
IResourceBuilder<UpstreamApiResource> |
Declares a dependency on an external API by its project ID. |
.WithScope() |
Scope scope |
IResourceBuilder<UpstreamApiResource> |
Adds a delegated permission (user context flows through). |
.WithAppRole() |
AppRole appRole |
IResourceBuilder<UpstreamApiResource> |
Adds an application permission (service-to-service). |
.WithAppRoleAssignment() |
AppRoleAssignment assignment |
IResourceBuilder<UpstreamApiResource> |
Adds all app roles from a business role assignment. |
Infrastructure Features¶
| Method | Parameters | Returns | Description |
|---|---|---|---|
.WithOracle<T>() |
(none) | IResourceBuilder<T> |
Declares Oracle Database dependency. Includes the project ID variable group in generated API pipelines. T must implement IResource. |
Options Classes¶
ApiPipelineOptions¶
| Property | Type | Default | Description |
|---|---|---|---|
ApiType |
ApiType |
Experience |
API classification: Experience, Process, System, or None. |
OpenApiFileName |
string |
"openapi.v1.yaml" |
OpenAPI specification filename. |
EventServicePipelineOptions¶
| Property | Type | Default | Description |
|---|---|---|---|
OpenApiFileName |
string |
"openapi.yaml" |
OpenAPI specification filename. |
PipelineDefaults¶
| Property | Type | Default | Description |
|---|---|---|---|
DotNetVersion |
string |
"10.x" |
.NET SDK version for pipeline agents. |
NodeVersion |
string |
"22.x" |
Node.js version for pipeline agents. |
TemplateRef |
string |
"refs/heads/releases/v3" |
Pipeline templates repository branch. |
SecretsVariableGroupName |
string |
"" |
Azure DevOps variable group for secrets. |
Security Model Types¶
All types are in SAIF.Platform.Aspire.Hosting.Generation.Infrastructure.Security.
| Type | Constructor | Description |
|---|---|---|
AppRole |
(string Value, string? DisplayName = null, string? Description = null) |
An application permission identifier. |
Scope |
(string Value, string? DisplayName = null, string? Description = null) |
An OAuth2 delegated permission. |
BusinessRole |
(string Name) |
A named business role (Entra/Okta group). |
AppRoleAssignment |
(BusinessRole Role, AppRole[] AppRoles) |
Maps a business role to one or more app roles. |
Enums¶
ApiType¶
| Value | Short Code | Project ID Pattern |
|---|---|---|
Experience |
exp |
{domain}-api-exp-{name} |
Process |
proc |
{domain}-api-proc-{name} |
System |
sys |
{domain}-api-sys-{name} |
None |
none |
{domain}-api-none-{name} |
📚 Related Documentation¶
- Aspire Security Configuration — Define auth configuration in C#
- Event Service — Create event services with TypeSpec
- Event Subscription — Add Service Bus subscriptions
- Pipeline Troubleshooting — Resolve pipeline errors
- Aspire Troubleshooting — Debug Aspire startup issues