diff --git a/.openpublishing.redirection.json b/.openpublishing.redirection.json index 937c2eef76..31ea8958ed 100644 --- a/.openpublishing.redirection.json +++ b/.openpublishing.redirection.json @@ -335,6 +335,10 @@ { "source_path_from_root": "/docs/fundamentals/build-container-images.md", "redirect_url": "/dotnet/aspire/fundamentals/custom-deployments" + }, + { + "source_path_from_root": "/docs/deployment/aspire-deploy/local-deployment-state.md", + "redirect_url": "/dotnet/aspire/deployment/deployment-state-caching" } ] } diff --git a/docs/app-host/certificate-trust.md b/docs/app-host/certificate-trust.md new file mode 100644 index 0000000000..266fe46a6a --- /dev/null +++ b/docs/app-host/certificate-trust.md @@ -0,0 +1,281 @@ +--- +title: Certificate trust customization in Aspire +description: Learn how to customize trusted certificates for Executable and Container resources in Aspire to enable secure communication. +ms.date: 11/10/2025 +ai-usage: ai-assisted +--- + +# Certificate trust customization in Aspire + +In Aspire, you can customize which certificates resources consider trusted for TLS/HTTPS traffic. This is particularly useful for resources that don't use the system's root trusted certificates by default, such as containerized applications, Python apps, and Node.js apps. By configuring certificate trust, you enable these resources to communicate securely with services that use certificates they wouldn't otherwise trust, including the Aspire dashboard's OTLP endpoint. + +> [!IMPORTANT] +> Certificate trust customization only applies at run time. Custom certificates aren't included in publish or deployment artifacts. + +## When to use certificate trust customization + +Certificate trust customization is valuable when: + +- Resources need to trust the ASP.NET Core Development Certificate for local HTTPS communication. +- Containerized services must communicate with the dashboard over HTTPS. +- Python or Node.js applications need to trust custom certificate authorities. +- You're working with services that have specific certificate trust requirements. +- Resources need to establish secure telemetry connections to the Aspire dashboard. + +## Development certificate trust + +By default, Aspire attempts to add trust for the ASP.NET Core Development Certificate to resources that wouldn't otherwise trust it. This enables resources to communicate with the dashboard OTEL collector endpoint over HTTPS and any other HTTPS endpoints secured by the development certificate. + +You can control this behavior at the per-resource level using the `WithDeveloperCertificateTrust` API or through AppHost configuration settings. + +### Configure development certificate trust per resource + +To explicitly enable or disable development certificate trust for a specific resource: + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +// Explicitly enable development certificate trust +var nodeApp = builder.AddNpmApp("frontend", "../frontend") + .WithDeveloperCertificateTrust(trust: true); + +// Disable development certificate trust +var pythonApp = builder.AddPythonApp("api", "../api", "main.py") + .WithDeveloperCertificateTrust(trust: false); + +builder.Build().Run(); +``` + +## Certificate authority collections + +Certificate authority collections allow you to bundle custom certificates and make them available to resources. You create a collection using the `AddCertificateAuthorityCollection` method and then reference it from resources that need to trust those certificates. + +### Create and use a certificate authority collection + +```csharp +using System.Security.Cryptography.X509Certificates; + +var builder = DistributedApplication.CreateBuilder(args); + +// Load your custom certificates +var certificates = new X509Certificate2Collection(); +certificates.ImportFromPemFile("path/to/certificate.pem"); + +// Create a certificate authority collection +var certBundle = builder.AddCertificateAuthorityCollection("my-bundle") + .WithCertificates(certificates); + +// Apply the certificate bundle to resources +builder.AddNpmApp("my-project", "../myapp") + .WithCertificateAuthorityCollection(certBundle); + +builder.Build().Run(); +``` + +In the preceding example, the certificate bundle is created with custom certificates and then applied to a Node.js application, enabling it to trust those certificates. + +## Certificate trust scopes + +Certificate trust scopes control how custom certificates interact with a resource's default trusted certificates. Different scopes provide flexibility in managing certificate trust based on your application's requirements. + +The `WithCertificateTrustScope` API accepts a `CertificateTrustScope` value to specify the trust behavior. + +### Default trust scopes + +Different resource types have different default trust scopes: + +- **Append**: The default for most resources, appending custom certificates to the default trusted certificates. +- **System**: The default for Python projects, which combines custom certificates with system root certificates because Python doesn't properly support Append mode. +- **None**: The default for .NET projects on Windows, as there's no way to automatically change the default system store source. + +### Append mode + +Attempts to append the configured certificates to the default trusted certificates for a given resource. This mode is useful when you want to add trust for additional certificates while maintaining trust for the system's default certificates. + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +builder.AddNodeApp("api", "../api") + .WithCertificateTrustScope(CertificateTrustScope.Append); + +builder.Build().Run(); +``` + +> [!NOTE] +> Not all languages and runtimes support Append mode. For example, Python doesn't natively support appending certificates to the default trust store. + +### Override mode + +Attempts to override a resource to only trust the configured certificates, replacing the default trusted certificates entirely. This mode is useful when you need strict control over which certificates are trusted. + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +var certBundle = builder.AddCertificateAuthorityCollection("custom-certs") + .WithCertificates(myCertificates); + +builder.AddPythonModule("api", "./api", "uvicorn") + .WithCertificateAuthorityCollection(certBundle) + .WithCertificateTrustScope(CertificateTrustScope.Override); + +builder.Build().Run(); +``` + +### System mode + +Attempts to combine the configured certificates with the default system root certificates and use them to override the default trusted certificates for a resource. This mode is intended to support Python or other languages that don't work well with Append mode. + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +builder.AddPythonApp("worker", "../worker", "main.py") + .WithCertificateTrustScope(CertificateTrustScope.System); + +builder.Build().Run(); +``` + +### None mode + +Disables all custom certificate trust for the resource, causing it to rely solely on its default certificate trust behavior. + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +builder.AddContainer("service", "myimage") + .WithCertificateTrustScope(CertificateTrustScope.None); + +builder.Build().Run(); +``` + +## Custom certificate trust configuration + +For advanced scenarios, you can specify custom certificate trust behavior using a callback API. This callback allows you to customize the command line arguments and environment variables required to configure certificate trust for different resource types. + +### Configure certificate trust with a callback + +Use `WithCertificateTrustConfiguration` to customize how certificate trust is configured for a resource: + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +builder.AddContainer("api", "myimage") + .WithCertificateTrustConfiguration(async (ctx) => + { + // Add a command line argument + ctx.Arguments.Add("--use-system-ca"); + + // Set environment variables with certificate paths + // CertificateBundlePath resolves to the path of the custom certificate bundle file + ctx.EnvironmentVariables["MY_CUSTOM_CERT_VAR"] = ctx.CertificateBundlePath; + + // CertificateDirectoriesPath resolves to paths containing individual certificates + ctx.EnvironmentVariables["CERTS_DIR"] = ctx.CertificateDirectoriesPath; + + await Task.CompletedTask; + }); + +builder.Build().Run(); +``` + +The callback receives a `CertificateTrustConfigurationCallbackAnnotationContext` that provides: + +- `Scope`: The `CertificateTrustScope` for the resource. +- `Arguments`: Command line arguments for the resource. Values can be strings or path providers like `CertificateBundlePath` or `CertificateDirectoriesPath`. +- `EnvironmentVariables`: Environment variables for configuring certificate trust. The dictionary key is the environment variable name; values can be strings or path providers. By default, includes `SSL_CERT_DIR` and may include `SSL_CERT_FILE` if Override or System scope is configured. +- `CertificateBundlePath`: A value provider that resolves to the path of a custom certificate bundle file. +- `CertificateDirectoriesPath`: A value provider that resolves to paths containing individual certificates. + +Default implementations are provided for Node.js, Python, and container resources. Container resources rely on standard OpenSSL configuration options, with default values that support the majority of common Linux distributions. + +### Configure container certificate paths + +For container resources, you can customize where certificates are stored and accessed using `WithContainerCertificatePaths`: + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +builder.AddContainer("api", "myimage") + .WithContainerCertificatePaths( + customCertificatesDestination: "/custom/certs/path", + defaultCertificateBundlePaths: ["/etc/ssl/certs/ca-certificates.crt"], + defaultCertificateDirectoryPaths: ["/etc/ssl/certs"]); + +builder.Build().Run(); +``` + +The `WithContainerCertificatePaths` API accepts three optional parameters: + +- `customCertificatesDestination`: Overrides the base path in the container where custom certificate files are placed. If not set or set to `null`, the default path of `/usr/lib/ssl/aspire` is used. +- `defaultCertificateBundlePaths`: Overrides the path(s) in the container where a default certificate authority bundle file is located. When the `CertificateTrustScope` is Override or System, the custom certificate bundle is additionally written to these paths. If not set or set to `null`, a set of default certificate paths for common Linux distributions is used. +- `defaultCertificateDirectoryPaths`: Overrides the path(s) in the container where individual trusted certificate files are found. When the `CertificateTrustScope` is Append, these paths are concatenated with the path to the uploaded certificate artifacts. If not set or set to `null`, a set of default certificate paths for common Linux distributions is used. + +> [!NOTE] +> All desired paths must be configured in a single call to `WithContainerCertificatePaths` as only the most recent call to the API is honored. + +## Common scenarios + +### Enable HTTPS telemetry to the dashboard + +By default, Aspire enables development certificate trust for resources, allowing them to send telemetry to the dashboard over HTTPS: + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +// Development certificate trust is enabled by default +var nodeApp = builder.AddNpmApp("frontend", "../frontend"); +var pythonApp = builder.AddPythonApp("api", "../api", "main.py"); + +builder.Build().Run(); +``` + +### Trust custom certificates in containers + +When working with containerized services that need to trust custom certificates: + +```csharp +using System.Security.Cryptography.X509Certificates; + +var builder = DistributedApplication.CreateBuilder(args); + +// Load custom CA certificates +var customCerts = new X509Certificate2Collection(); +customCerts.Import("corporate-ca.pem"); + +var certBundle = builder.AddCertificateAuthorityCollection("corporate-certs") + .WithCertificates(customCerts); + +// Apply to container +builder.AddContainer("service", "myservice:latest") + .WithCertificateAuthorityCollection(certBundle); + +builder.Build().Run(); +``` + +### Disable certificate trust for Python apps + +Python projects use System mode by default. To disable certificate trust customization for a Python app: + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +// Disable certificate trust for Python apps +builder.AddPythonModule("api", "./api", "uvicorn") + .WithCertificateTrustScope(CertificateTrustScope.None); + +builder.Build().Run(); +``` + +## Limitations + +Certificate trust customization has the following limitations: + +- Currently supported only in run mode, not in publish mode. +- Not all languages and runtimes support all trust scope modes. +- Python applications don't natively support Append mode. +- Custom certificate trust requires appropriate runtime support within the resource. + +## See also + +- [Host external executables in Aspire](executable-resources.md) +- [Add Dockerfiles to your .NET app model](withdockerfile.md) +- [AppHost configuration](configuration.md) diff --git a/docs/azureai/ai-integrations-compatibility-matrix.md b/docs/azureai/ai-integrations-compatibility-matrix.md index d3e69b6062..b1d28aeb4c 100644 --- a/docs/azureai/ai-integrations-compatibility-matrix.md +++ b/docs/azureai/ai-integrations-compatibility-matrix.md @@ -118,7 +118,8 @@ The [Aspire.Hosting.GitHub.Models](https://www.nuget.org/packages/Aspire.Hosting ```csharp var builder = DistributedApplication.CreateBuilder(args); -var chat = builder.AddGitHubModel("chat", "openai/gpt-4o-mini"); +var model = GitHubModel.OpenAI.OpenAIGpt4oMini; +var chat = builder.AddGitHubModel("chat", model); builder.AddProject() .WithReference(chat); diff --git a/docs/azureai/azureai-foundry-integration.md b/docs/azureai/azureai-foundry-integration.md index 44354cdbd3..0a28cf47df 100644 --- a/docs/azureai/azureai-foundry-integration.md +++ b/docs/azureai/azureai-foundry-integration.md @@ -63,7 +63,8 @@ var builder = DistributedApplication.CreateBuilder(args); var foundry = builder.AddAzureAIFoundry("foundry"); -var chat = foundry.AddDeployment("chat", "Phi-4", "1", "Microsoft"); +var model = AIFoundryModel.Microsoft.Phi4; +var chat = foundry.AddDeployment("chat", model); builder.AddProject() .WithReference(chat) @@ -75,17 +76,18 @@ builder.AddProject() The preceding code: - Adds an Azure AI Foundry resource named `foundry`. -- Adds an Azure AI Foundry deployment resource named `chat` with a model name of `Phi-4`. The model name must correspond to an [available model](/azure/ai-foundry/foundry-models/concepts/models) in the Azure AI Foundry service. +- Adds an Azure AI Foundry deployment resource named `chat` using the constant for Phi-4. The model must correspond to an [available model](/azure/ai-foundry/foundry-models/concepts/models) in the Azure AI Foundry service. -> [!NOTE] -> The `format` parameter of the `AddDeployment(...)` method can be found in the Azure AI Foundry portal in the details page of the model, right after the `Quick facts` text. +> [!TIP] +> Use the strongly-typed constants to avoid typos and ensure you're using valid model identifiers. These constants are grouped by publisher (for example, `AIFoundryModel.Microsoft.Phi4`, `AIFoundryModel.OpenAI.Gpt4o`). ### Configure deployment properties You can customize deployment properties using the method: ```csharp -var chat = foundry.AddDeployment("chat", "Phi-4", "1", "Microsoft") +var model = AIFoundryModel.Microsoft.Phi4; +var chat = foundry.AddDeployment("chat", model) .WithProperties(deployment => { deployment.SkuName = "Standard"; @@ -131,7 +133,8 @@ var builder = DistributedApplication.CreateBuilder(args); var foundry = builder.AddAzureAIFoundry("foundry") .RunAsFoundryLocal(); -var chat = foundry.AddDeployment("chat", "phi-3.5-mini", "1", "Microsoft"); +var model = AIFoundryModel.Local.Phi35Mini; +var chat = foundry.AddDeployment("chat", model); builder.AddProject() .WithReference(chat) @@ -144,6 +147,9 @@ When the AppHost starts up, the local foundry service is also started. This requ The method configures the resource to run as an emulator. It downloads and loads the specified models locally. The method provides health checks for the local service and automatically manages the Foundry Local lifecycle. +> [!TIP] +> Use the strongly-typed `AIFoundryModel.Local` constants for local development models. These constants are specifically optimized for Foundry Local (for example, `AIFoundryModel.Local.Phi4Mini`, `AIFoundryModel.Local.DeepseekR17b`). + ### Assign roles to resources You can assign specific roles to resources that need to access the Azure AI Foundry service. Use the method: diff --git a/docs/cli-reference/aspire-deploy.md b/docs/cli-reference/aspire-deploy.md index f35f402d73..216f9cc086 100644 --- a/docs/cli-reference/aspire-deploy.md +++ b/docs/cli-reference/aspire-deploy.md @@ -55,6 +55,14 @@ The following options are available: - [!INCLUDE [option-project](includes/option-project.md)] +- **`-e, --environment`** + + The deployment environment name. Defaults to `production`. Each environment maintains its own isolated deployment state file. + +- **`--clear-cache`** + + Clears the cached deployment state for the specified environment before deploying. When used, the deployment prompts for all values but doesn't save them to cache. + - **`-o, --output-path`** The output path for deployment artifacts. Defaults to a folder named _deploy_ in the current directory. @@ -84,3 +92,25 @@ The following options are available: ```Command aspire deploy --project './projects/apphost/orchestration.AppHost.csproj' -- -fast ``` + +- Deploy to a specific environment: + + ```Command + aspire deploy --environment staging + ``` + +- Clear cached deployment state and deploy: + + ```Command + aspire deploy --clear-cache + ``` + +- Clear cached deployment state for a specific environment: + + ```Command + aspire deploy --environment staging --clear-cache + ``` + +## See also + +- [Deployment state caching](../deployment/deployment-state-caching.md) diff --git a/docs/compatibility/13.0/defaultazurecredential-managedidentity-default.md b/docs/compatibility/13.0/defaultazurecredential-managedidentity-default.md new file mode 100644 index 0000000000..6be35b7d7f --- /dev/null +++ b/docs/compatibility/13.0/defaultazurecredential-managedidentity-default.md @@ -0,0 +1,101 @@ +--- +title: "Breaking change - DefaultAzureCredential defaults to ManagedIdentityCredential on ACA and App Service" +description: "Learn about the breaking change in Aspire 13.0 where DefaultAzureCredential behavior is changed to only use ManagedIdentityCredential when deploying to Azure Container Apps and Azure App Service." +ms.date: 11/10/2025 +ai-usage: ai-assisted +ms.custom: https://github.com/dotnet/docs-aspire/issues/5154 +--- + +# DefaultAzureCredential defaults to ManagedIdentityCredential on ACA and App Service + +In Aspire 13.0, when deploying to Azure Container Apps and Azure App Service, the default behavior of `DefaultAzureCredential` has been changed to only use `ManagedIdentityCredential`. This is accomplished by setting the `AZURE_TOKEN_CREDENTIALS` environment variable to ensure deterministic credential resolution. + +## Version introduced + +Aspire 13.0 + +## Previous behavior + +Previously, `DefaultAzureCredential` used the full chain of identity providers by default. This meant that `EnvironmentCredential` and `WorkloadIdentityCredential` would be attempted before `ManagedIdentityCredential` when authenticating to Azure resources. + +```csharp +// No explicit environment variable was set +// DefaultAzureCredential would try credentials in this order: +// 1. EnvironmentCredential +// 2. WorkloadIdentityCredential +// 3. ManagedIdentityCredential +// ... and others +``` + +## New behavior + +Now `DefaultAzureCredential` only uses `ManagedIdentityCredential` when deploying to Azure Container Apps and Azure App Service. This is achieved by setting the `AZURE_TOKEN_CREDENTIALS` environment variable automatically. + +```csharp +// The AZURE_TOKEN_CREDENTIALS environment variable is automatically set +// DefaultAzureCredential now only uses ManagedIdentityCredential +``` + +This change forces `DefaultAzureCredential` to behave in a deterministic manner and optimizes the underlying `ManagedIdentityCredential` for resilience. + +## Type of breaking change + +This is a [behavioral change](../categories.md#behavioral-change). + +## Reason for change + +This change enforces Azure SDK best practices for production environments. Using deterministic credentials improves both security and reliability by: + +- Ensuring predictable authentication behavior +- Reducing potential authentication failures from trying multiple credential types +- Optimizing credential acquisition performance +- Following the principle of least privilege by using managed identities + +For more information, see: + +- [Use deterministic credentials in production environments](/dotnet/azure/sdk/authentication/best-practices?tabs=aspdotnet#use-deterministic-credentials-in-production-environments) +- [GitHub PR dotnet/aspire#11832](https://github.com/dotnet/aspire/pull/11832) +- [Azure SDK resilience improvements](https://github.com/Azure/azure-sdk-for-net/pull/52545) + +## Recommended action + +If you were relying on `EnvironmentCredential` or `WorkloadIdentityCredential` in your application, you can choose one of the following options to revert to the old behavior or adapt your code: + +### Option 1: Use specific credential types explicitly + +Don't use `DefaultAzureCredential` in your application. Instead, explicitly use `EnvironmentCredential` or `WorkloadIdentityCredential` in production code. + +### Option 2: Remove the environment variable in your deployment + +Implement a `PublishAsAzureContainerApp` callback and remove the environment variable from the bicep: + +```csharp +builder.AddProject("frontend") + .PublishAsAzureContainerApp((infra, app) => + { + // Remove the AZURE_TOKEN_CREDENTIALS env var to restore previous behavior + var containerAppContainer = app.Template.Containers[0].Value!; + var azureTokenCredentialEnv = containerAppContainer.Env + .Single(v => v.Value!.Name.Value == "AZURE_TOKEN_CREDENTIALS"); + containerAppContainer.Env.Remove(azureTokenCredentialEnv); + }); +``` + +For Azure App Service, use a similar approach with `PublishAsAzureAppService`: + +```csharp +builder.AddProject("frontend") + .PublishAsAzureAppService((infra, app) => + { + // Remove the AZURE_TOKEN_CREDENTIALS env var to restore previous behavior + var settings = app.Properties.SiteConfig.Value!.AppSettings!; + var azureTokenCredentialSetting = settings + .Single(s => s.Value!.Name.Value == "AZURE_TOKEN_CREDENTIALS"); + settings.Remove(azureTokenCredentialSetting); + }); +``` + +## Affected APIs + +- `Aspire.Hosting.Azure.AzureContainerAppExtensions.AddAzureContainerAppEnvironment` +- `Aspire.Hosting.Azure.AzureAppServiceEnvironmentExtensions.AddAzureAppServiceEnvironment` diff --git a/docs/compatibility/13.0/index.md b/docs/compatibility/13.0/index.md new file mode 100644 index 0000000000..8decc31494 --- /dev/null +++ b/docs/compatibility/13.0/index.md @@ -0,0 +1,22 @@ +--- +title: Breaking changes in Aspire 13.0 +titleSuffix: "" +description: Navigate to the breaking changes in Aspire 13.0. +ms.date: 10/20/2025 +--- + +# Breaking changes in Aspire 13.0 + +If you're migrating an app to Aspire 13.0, the breaking changes listed here might affect you. + +[!INCLUDE [binary-source-behavioral](../includes/binary-source-behavioral.md)] + +> [!NOTE] +> This article is a work in progress. It's not a complete list of breaking changes in Aspire 13.0. + +## Breaking changes + +| Title | Type of change | Introduced version | +|--|--|--| +| [Activity reporter and pipeline context renamed](pipeline-activity-reporter-renamed.md) | Binary incompatible, source incompatible | 13.0 | +| [DefaultAzureCredential defaults to ManagedIdentityCredential on ACA and App Service](defaultazurecredential-managedidentity-default.md) | Behavioral change | 13.0 | diff --git a/docs/compatibility/13.0/pipeline-activity-reporter-renamed.md b/docs/compatibility/13.0/pipeline-activity-reporter-renamed.md new file mode 100644 index 0000000000..106783147f --- /dev/null +++ b/docs/compatibility/13.0/pipeline-activity-reporter-renamed.md @@ -0,0 +1,142 @@ +--- +title: "Breaking change - Activity reporter and pipeline context renamed" +description: "Learn about the breaking change in Aspire 13.0 where publishing activity reporter APIs and context types were renamed to reflect pipeline architecture." +ms.date: 10/20/2025 +ai-usage: ai-assisted +ms.custom: https://github.com/dotnet/aspire/pull/12137 +--- + +# Activity reporter and pipeline context renamed + +In Aspire 13.0, the publishing activity reporter APIs and context types have been renamed to better reflect the pipeline architecture. The publishing step creation process has been simplified, with steps now automatically created during pipeline execution rather than requiring explicit creation within step actions. + +## Version introduced + +Aspire 13.0 Preview 1 + +## Previous behavior + +The previous API used publishing-specific names and required explicit step creation within pipeline actions: + +```csharp +builder.Pipeline.AddStep("assign-storage-role", async (context) => +{ + var roleAssignmentStep = await context.ActivityReporter + .CreateStepAsync($"assign-storage-role", context.CancellationToken); + + await using (roleAssignmentStep.ConfigureAwait(false)) + { + var assignRoleTask = await roleAssignmentStep + .CreateTaskAsync($"Granting file share access...", context.CancellationToken); + + await using (assignRoleTask.ConfigureAwait(false)) + { + // ... task work + } + } +}); +``` + +The types used were: + +- `DeployingContext` - Provided context for pipeline execution +- `IPublishingActivityReporter` - Interface for reporting activities +- `PublishingActivityReporter` - Implementation of the activity reporter +- `NullPublishingActivityReporter` - Null object pattern implementation +- `IPublishingStep` - Interface for publishing steps +- `IPublishingTask` - Interface for publishing tasks + +## New behavior + +The API now uses pipeline-specific names and automatically creates steps during pipeline execution: + +```csharp +builder.Pipeline.AddStep("assign-storage-role", async (stepContext) => +{ + var assignRoleTask = await stepContext.ReportingStep + .CreateTaskAsync($"Granting file share access...", stepContext.CancellationToken); + + await using (assignRoleTask.ConfigureAwait(false)) + { + // ... task work + } +}); +``` + +The types have been renamed as follows: + +- `DeployingContext` → `PipelineContext` - Shared context across all pipeline steps +- `IPublishingActivityReporter` → `IPipelineActivityReporter` - Interface for reporting pipeline activities +- `PublishingActivityReporter` → `PipelineActivityReporter` - Implementation of the pipeline activity reporter +- `NullPublishingActivityReporter` → `NullPipelineActivityReporter` - Null object pattern implementation +- `IPublishingStep` → `IReportingStep` - Interface for reporting steps +- `IPublishingTask` → `IReportingTask` - Interface for reporting tasks + +Additionally, a new `PipelineStepContext` type has been introduced that combines the shared `PipelineContext` with a step-specific `IReportingStep`, allowing each step to track its own tasks and completion state independently. + +## Type of breaking change + +This change can affect [binary compatibility](../categories.md#binary-compatibility) and [source compatibility](../categories.md#source-compatibility). + +## Reason for change + +The previous three-level hierarchy (`ActivityReporter → Step → Task`) was unnecessarily complex. The new architecture simplifies this by automatically creating steps during pipeline execution and integrating step management directly into the pipeline. This provides a cleaner separation between the shared pipeline context and step-specific execution context, making the API more intuitive and reducing boilerplate code. + +## Recommended action + +Update your code to use the new type names and simplified step creation pattern: + +1. Replace `DeployingContext` with `PipelineContext` for shared pipeline context or `PipelineStepContext` for step-specific context. +1. Replace `IPublishingActivityReporter` with `IPipelineActivityReporter`. +1. Replace `PublishingActivityReporter` with `PipelineActivityReporter`. +1. Replace `NullPublishingActivityReporter` with `NullPipelineActivityReporter`. +1. Replace `IPublishingStep` with `IReportingStep`. +1. Replace `IPublishingTask` with `IReportingTask`. +1. Update pipeline step actions to accept `PipelineStepContext` instead of `DeployingContext`. +1. Remove explicit step creation calls within pipeline actions and use the automatically created `context.ReportingStep` instead. + +Migration example: + +```csharp +// Before +builder.Pipeline.AddStep("my-step", async (context) => +{ + var step = await context.ActivityReporter + .CreateStepAsync("my-step", context.CancellationToken); + + await using (step.ConfigureAwait(false)) + { + var task = await step.CreateTaskAsync("Doing work...", context.CancellationToken); + await using (task.ConfigureAwait(false)) + { + // Do work + await task.CompleteAsync("Done", CompletionState.Completed, context.CancellationToken); + } + } +}); + +// After +builder.Pipeline.AddStep("my-step", async (stepContext) => +{ + var task = await stepContext.ReportingStep + .CreateTaskAsync("Doing work...", stepContext.CancellationToken); + + await using (task.ConfigureAwait(false)) + { + // Do work + await task.CompleteAsync("Done", CompletionState.Completed, stepContext.CancellationToken); + } +}); +``` + +## Affected APIs + +- +- +- `Aspire.Hosting.Publishing.PublishingActivityReporter` +- +- +- +- `Aspire.Hosting.Publishing.PublishingStep` +- `Aspire.Hosting.Publishing.PublishingTask` +- diff --git a/docs/compatibility/toc.yml b/docs/compatibility/toc.yml index b0a1e43590..9a3735621f 100644 --- a/docs/compatibility/toc.yml +++ b/docs/compatibility/toc.yml @@ -5,9 +5,21 @@ items: href: ../get-started/aspire-overview.md - name: Breaking changes href: breaking-changes.md -- name: Aspire 9.5 +- name: Aspire 13.0 expanded: true items: + - name: Overview + href: 13.0/index.md + - name: Breaking changes in 13.0 + expanded: true + items: + - name: Activity reporter and pipeline context renamed + href: 13.0/pipeline-activity-reporter-renamed.md + - name: DefaultAzureCredential defaults to ManagedIdentityCredential on ACA and App Service + href: 13.0/defaultazurecredential-managedidentity-default.md +- name: Aspire 9.5 + expanded: false + items: - name: Overview href: 9.5/index.md - name: Breaking changes in 9.5 diff --git a/docs/deployment/aspire-deploy/aca-deployment-aspire-cli.md b/docs/deployment/aspire-deploy/aca-deployment-aspire-cli.md index f0462ff5ae..34a6eb3e1f 100644 --- a/docs/deployment/aspire-deploy/aca-deployment-aspire-cli.md +++ b/docs/deployment/aspire-deploy/aca-deployment-aspire-cli.md @@ -139,7 +139,7 @@ Save to user secrets: [y/n] (n): n ``` > [!NOTE] -> The CLI will continuously prompt until all unresolved parameters are provided with values. While Azure deployment is in preview, the CLI will prompt to save values in user secrets but not use them, as [deployment state is not supported](https://github.com/dotnet/aspire/issues/11444). +> The CLI continuously prompts until all unresolved parameters are provided with values. The `aspire deploy` command caches deployment state locally to streamline subsequent deployments. For more information, see [Deployment state caching](../deployment-state-caching.md). Once parameters are collected, Azure infrastructure is provisioned using Bicep templates. This step creates the necessary Azure resources including the Container Apps environment, Container Registry, and any backing services like Redis caches: diff --git a/docs/deployment/azd/aca-deployment-azd-in-depth.md b/docs/deployment/azd/aca-deployment-azd-in-depth.md index 777a532241..d34dc201e8 100644 --- a/docs/deployment/azd/aca-deployment-azd-in-depth.md +++ b/docs/deployment/azd/aca-deployment-azd-in-depth.md @@ -370,9 +370,13 @@ properties: value: "true" - name: OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES value: "true" - - name: services__apiservice__0 + - name: APISERVICE_HTTP value: http://apiservice.internal.{{ .Env.AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN }} - - name: services__apiservice__1 + - name: APISERVICE_HTTPS + value: https://apiservice.internal.{{ .Env.AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN }} + - name: services__apiservice__http__0 + value: http://apiservice.internal.{{ .Env.AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN }} + - name: services__apiservice__https__0 value: https://apiservice.internal.{{ .Env.AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN }} tags: azd-service-name: webfrontend diff --git a/docs/deployment/deployment-state-caching.md b/docs/deployment/deployment-state-caching.md new file mode 100644 index 0000000000..0f9988d607 --- /dev/null +++ b/docs/deployment/deployment-state-caching.md @@ -0,0 +1,171 @@ +--- +title: Deployment state caching +description: Learn how the aspire deploy command manages deployment state through cached configuration files. +ms.date: 10/17/2025 +ai-usage: ai-assisted +--- + +# Deployment state caching + +The `aspire deploy` command manages deployment state through cached configuration files stored locally on your machine. This caching mechanism streamlines repeated deployments by preserving provisioning settings and parameters, making subsequent deployments faster and more efficient. + +## Default behavior + +The `aspire deploy` command automatically manages deployment state based on whether cached configuration exists for your application and target environment. + +### First deployment + +When you run `aspire deploy` for the first time, or for the first time in a target `--environment`, the command: + +1. Prompts for provisioning information (subscription ID, resource group name, location). +1. Prompts for deployment parameters (for example, API keys, connection strings). +1. Initiates the deployment process. +1. Saves all prompted values and deployment state to `~/.aspire/deployments/{AppHostSha}/production.json`. + +### Subsequent deployments + +On subsequent `aspire deploy` executions, the command: + +1. Detects the existing deployment state file at _~/.aspire/deployments/{AppHostSha}/production.json_. +1. Notifies you that settings will be read from the cached file. +1. Prompts for confirmation to load the cached settings. +1. Loads the configuration from the cached file into the configuration provider. +1. Proceeds with deployment using the cached values (no re-prompting). + +## Environment-specific deployments + +Different deployment environments (such as development, staging, and production) typically require different configurations, resource names, and connection strings. The `aspire deploy` command supports environment-specific deployments, ensuring that each environment maintains isolated deployment state. + +### Specify an environment + +Use the `--environment` flag to deploy to different environments: + +```Aspire +aspire deploy --environment staging +``` + +**First deployment to a specific environment:** + +- Prompts for all provisioning and parameter information. +- Saves deployment state to _~/.aspire/deployments/{AppHostSha}/{environment}.json_ (for example, _staging.json_). + +**Subsequent deployments:** + +- Reads the environment-specific cached file. +- Loads configuration from the cached state. +- Uses cached values without prompting. + +### Environment variable support + +The deployment environment can also be specified using the `DOTNET_ENVIRONMENT` environment variable: + +```bash +export DOTNET_ENVIRONMENT=staging && aspire deploy +``` + +This behaves identically to using the `--environment` flag, loading the appropriate cached configuration file. + +## Cache management + +The `aspire deploy` command provides mechanisms to manage cached deployment state, giving you control over when to use cached values and when to start fresh. + +### Clear the cache + +Use the `--clear-cache` flag to reset deployment state: + +```Aspire +aspire deploy --clear-cache +``` + +**Behavior:** + +1. Prompts for confirmation before deleting the cache for the specified environment. +1. Deletes the environment-specific deployment state file (for example, _~/.aspire/deployments/{AppHostSha}/production.json_). +1. Prompts for all provisioning and parameter information as if deploying for the first time. +1. Proceeds with deployment. +1. **Does not save the prompted values** to cache. + +### Environment-specific cache clearing + +The `--clear-cache` flag respects the environment context: + +```Aspire +aspire deploy --environment staging --clear-cache +``` + +This clears only the _staging.json_ cache file while leaving other environment caches (like _production.json_) intact. + +## File storage location + +- **Path pattern:** _~/.aspire/deployments/{AppHostSha}/{environment}.json_. +- **Default environment:** `production`. +- **AppHostSha:** A hash value representing the application host configuration, ensuring deployment states are specific to each application configuration. + +## Use deployment state in CI/CD pipelines + +When using the `aspire deploy` command in continuous integration and deployment (CI/CD) pipelines, you might want to persist deployment state across pipeline runs. This approach can be useful for maintaining consistent deployment configurations without manual intervention. + +### GitHub Actions example + +The following example demonstrates how to cache deployment state in a GitHub Actions workflow using the `actions/cache` action: + +```yaml +name: Deploy to Azure + +on: + push: + branches: [ main ] + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Cache Aspire deployment state + uses: actions/cache@v4 + with: + path: ~/.aspire/deployments + key: aspire-deploy-${{ hashFiles('**/AppHost.csproj') }}-${{ github.ref }} + restore-keys: | + aspire-deploy-${{ hashFiles('**/AppHost.csproj') }}- + aspire-deploy- + + - name: Deploy with Aspire CLI + run: aspire deploy --environment production + env: + AZURE_CREDENTIALS: ${{ secrets.AZURE_CREDENTIALS }} +``` + +This workflow caches the _~/.aspire/deployments_ directory, using the AppHost project file hash and branch reference as cache keys. The `actions/cache` action automatically restores the cache before the deployment step and saves any updates to the cache after the job completes. Subsequent workflow runs restore the cached deployment state, allowing automated deployments without re-prompting for configuration values. + +> [!CAUTION] +> When caching deployment state in CI/CD pipelines, ensure that your pipeline has appropriate access controls and secret management practices in place, as the cached state might contain sensitive configuration values. + +## Security considerations + +The deployment state files are stored locally on your machine in the _~/.aspire/deployments_ directory. These files contain provisioning settings and parameter values, including secrets that might be associated with parameter resources. The `aspire deploy` command follows the same security pattern as .NET's user secrets manager: + +- Files are stored outside of source code to mitigate against accidental secret leaks in version control. +- Secrets are stored in plain text in the local file system. +- Any process running under your user account can access these files. + +Consider these security best practices: + +- Ensure your local machine has appropriate security measures in place. +- Be cautious when sharing or backing up files from the _~/.aspire/deployments_ directory. +- Use the `--clear-cache` flag when you need to change sensitive parameter values. + +## Key points + +- Each environment maintains its own isolated deployment state. +- Cached values persist across deployments unless explicitly cleared. +- The `--clear-cache` flag performs a one-time deployment without persisting new values. +- Environment selection can be specified via flag or environment variable. +- You're prompted for confirmation when loading cached settings. +- Cache files are stored per application (via AppHostSha) and per environment. + +## See also + +- [aspire deploy command reference](../cli-reference/aspire-deploy.md) +- [Deploy to Azure Container Apps using Aspire CLI](aspire-deploy/aca-deployment-aspire-cli.md) diff --git a/docs/deployment/manifest-format.md b/docs/deployment/manifest-format.md index 6fc40d16fc..9d6406a53d 100644 --- a/docs/deployment/manifest-format.md +++ b/docs/deployment/manifest-format.md @@ -110,8 +110,10 @@ Publishing the manifest from the default starter template for Aspire produces th "OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES": "true", "OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES": "true", "ConnectionStrings__cache": "{cache.connectionString}", - "services__apiservice__0": "{apiservice.bindings.http.url}", - "services__apiservice__1": "{apiservice.bindings.https.url}" + "APISERVICE_HTTP": "{apiservice.bindings.http.url}", + "APISERVICE_HTTPS": "{apiservice.bindings.https.url}", + "services__apiservice__http__0": "{apiservice.bindings.http.url}", + "services__apiservice__https__0": "{apiservice.bindings.https.url}" }, "bindings": { "http": { @@ -142,8 +144,10 @@ This dependency is known because the environment variables for the _webfrontend_ "env": { // ... other environment variables omitted for clarity "ConnectionStrings__cache": "{cache.connectionString}", - "services__apiservice__0": "{apiservice.bindings.http.url}", - "services__apiservice__1": "{apiservice.bindings.https.url}" + "APISERVICE_HTTP": "{apiservice.bindings.http.url}", + "APISERVICE_HTTPS": "{apiservice.bindings.https.url}", + "services__apiservice__http__0": "{apiservice.bindings.http.url}", + "services__apiservice__https__0": "{apiservice.bindings.https.url}" }, ``` diff --git a/docs/diagnostics/aspirepipelines001.md b/docs/diagnostics/aspirepipelines001.md new file mode 100644 index 0000000000..c4f92ce0cb --- /dev/null +++ b/docs/diagnostics/aspirepipelines001.md @@ -0,0 +1,55 @@ +--- +title: Compiler Error ASPIREPIPELINES001 +description: Learn more about compiler Error ASPIREPIPELINES001. Pipeline infrastructure APIs are for evaluation purposes only and are subject to change or removal in future updates. +ms.date: 10/27/2025 +f1_keywords: + - "ASPIREPIPELINES001" +helpviewer_keywords: + - "ASPIREPIPELINES001" +ai-usage: ai-generated +--- + +# Compiler Error ASPIREPIPELINES001 + +**Version introduced:** 13.0 + +> Pipeline infrastructure APIs are for evaluation purposes only and are subject to change or removal in future updates. Suppress this diagnostic to proceed. + +Aspire introduced pipeline infrastructure APIs starting in version 9.2. These APIs provide core functionality for building deployment pipelines, including interfaces and types for pipeline activity reporting, step management, and publishing contexts. Pipeline infrastructure enables you to create custom deployment workflows and track their execution. + +Pipeline infrastructure APIs are considered experimental and are expected to change in future updates. + +## APIs affected + +This diagnostic applies to the following pipeline infrastructure APIs: + +- `IPipelineActivityReporter` - Interface for reporting pipeline activities +- `IReportingStep` - Interface for managing pipeline steps +- `IReportingTask` - Interface for managing tasks within a step +- `CompletionState` - Enumeration for tracking completion status +- `PublishingContext` - Context for publishing operations +- `PublishingCallbackAnnotation` - Annotation for publishing callbacks +- Related extension methods and implementations + +## To correct this error + +Suppress the error with one of the following methods: + +- Set the severity of the rule in the _.editorconfig_ file. + + ```ini + [*.{cs,vb}] + dotnet_diagnostic.ASPIREPIPELINES001.severity = none + ``` + + For more information about editor config files, see [Configuration files for code analysis rules](/dotnet/fundamentals/code-analysis/configuration-files). + +- Add the following `PropertyGroup` to your project file: + + ```xml + + $(NoWarn);ASPIREPIPELINES001 + + ``` + +- Suppress in code with the `#pragma warning disable ASPIREPIPELINES001` directive. diff --git a/docs/diagnostics/aspirepipelines002.md b/docs/diagnostics/aspirepipelines002.md new file mode 100644 index 0000000000..3a037f7c0d --- /dev/null +++ b/docs/diagnostics/aspirepipelines002.md @@ -0,0 +1,56 @@ +--- +title: Compiler Error ASPIREPIPELINES002 +description: Learn more about compiler Error ASPIREPIPELINES002. Deployment state manager APIs are for evaluation purposes only and are subject to change or removal in future updates. +ms.date: 10/28/2025 +f1_keywords: + - "ASPIREPIPELINES002" +helpviewer_keywords: + - "ASPIREPIPELINES002" +ai-usage: ai-generated +--- + +# Compiler Error ASPIREPIPELINES002 + +**Version introduced:** 13.0 + +> Deployment state manager APIs are for evaluation purposes only and are subject to change or removal in future updates. Suppress this diagnostic to proceed. + +Aspire introduced deployment state manager APIs as part of the pipeline infrastructure starting in version 13.0. These APIs enable you to manage and persist deployment state information across pipeline executions. The deployment state manager provides functionality for storing, retrieving, and clearing deployment artifacts and metadata, which is essential for incremental deployments and tracking deployment history. + +Deployment state manager APIs are considered experimental and are expected to change in future updates. + +## APIs affected + +This diagnostic applies to the following deployment state manager APIs: + +- `IDeploymentStateManager` - Interface for managing deployment state +- Deployment state manager implementations +- `Deploy` property in `PublishingOptions` +- `ClearCache` property in `PublishingOptions` +- `Step` property in `PublishingOptions` +- `DeployingCallbackAnnotation` - Annotation for deploying callbacks +- Azure provisioning context providers +- Related extension methods and implementations + +## To correct this error + +Suppress the error with one of the following methods: + +- Set the severity of the rule in the _.editorconfig_ file. + + ```ini + [*.{cs,vb}] + dotnet_diagnostic.ASPIREPIPELINES002.severity = none + ``` + + For more information about editor config files, see [Configuration files for code analysis rules](/dotnet/fundamentals/code-analysis/configuration-files). + +- Add the following `PropertyGroup` to your project file: + + ```xml + + $(NoWarn);ASPIREPIPELINES002 + + ``` + +- Suppress in code with the `#pragma warning disable ASPIREPIPELINES002` directive. diff --git a/docs/diagnostics/aspirepipelines003.md b/docs/diagnostics/aspirepipelines003.md new file mode 100644 index 0000000000..e6722b0660 --- /dev/null +++ b/docs/diagnostics/aspirepipelines003.md @@ -0,0 +1,55 @@ +--- +title: Compiler Error ASPIREPIPELINES003 +description: Learn more about compiler Error ASPIREPIPELINES003. Container image build APIs are for evaluation purposes only and are subject to change or removal in future updates. +ms.date: 10/28/2025 +f1_keywords: + - "ASPIREPIPELINES003" +helpviewer_keywords: + - "ASPIREPIPELINES003" +ai-usage: ai-generated +--- + +# Compiler Error ASPIREPIPELINES003 + +**Version introduced:** 13.0 + +> Container image build APIs are for evaluation purposes only and are subject to change or removal in future updates. Suppress this diagnostic to proceed. + +Aspire introduced container image build APIs as part of the pipeline infrastructure starting in version 13.0. These APIs provide functionality for building container images as part of the deployment pipeline. The container image build APIs enable you to configure build options, specify target platforms, select image formats, and integrate with container runtimes like Docker and Podman. + +Container image build APIs are considered experimental and are expected to change in future updates. + +## APIs affected + +This diagnostic applies to the following container image build APIs: + +- `IResourceContainerImageBuilder` - Interface for building container images +- `ContainerBuildOptions` - Options for configuring container builds +- `ContainerImageFormat` - Enumeration for specifying image format +- `ContainerTargetPlatform` - Type for specifying target platform +- `ContainerTargetPlatformExtensions` - Extension methods for platform configuration +- Docker and Podman container runtime implementations +- Related extension methods and implementations + +## To correct this error + +Suppress the error with one of the following methods: + +- Set the severity of the rule in the _.editorconfig_ file. + + ```ini + [*.{cs,vb}] + dotnet_diagnostic.ASPIREPIPELINES003.severity = none + ``` + + For more information about editor config files, see [Configuration files for code analysis rules](/dotnet/fundamentals/code-analysis/configuration-files). + +- Add the following `PropertyGroup` to your project file: + + ```xml + + $(NoWarn);ASPIREPIPELINES003 + + ``` + +- Suppress in code with the `#pragma warning disable ASPIREPIPELINES003` directive. diff --git a/docs/diagnostics/overview.md b/docs/diagnostics/overview.md index d0ae8596e3..1bf32ad861 100644 --- a/docs/diagnostics/overview.md +++ b/docs/diagnostics/overview.md @@ -24,6 +24,9 @@ The following table lists the possible MSBuild and .NET Analyzer warnings and er | [`ASPIRECOMPUTE001`](aspirecompute001.md) | (Experimental) Error | Compute related types and members are for evaluation purposes only and is subject to change or removal in future updates. | | [`ASPIRECOSMOSDB001`](aspirecosmosdb001.md) | (Experimental) Error | `RunAsPreviewEmulator` is for evaluation purposes only and is subject to change or removal in future updates. | | [`ASPIREHOSTINGPYTHON001`](aspirehostingpython001.md) | (Experimental) Error | `AddPythonApp` is for evaluation purposes only and is subject to change or removal in future updates. | +| [`ASPIREPIPELINES001`](aspirepipelines001.md) | (Experimental) Error | Pipeline infrastructure APIs are for evaluation purposes only and are subject to change or removal in future updates. | +| [`ASPIREPIPELINES002`](aspirepipelines002.md) | (Experimental) Error | Deployment state manager APIs are for evaluation purposes only and are subject to change or removal in future updates. | +| [`ASPIREPIPELINES003`](aspirepipelines003.md) | (Experimental) Error | Container image build APIs are for evaluation purposes only and are subject to change or removal in future updates. | | [`ASPIREPROXYENDPOINTS001`](aspireproxyendpoints001.md) | (Experimental) Error | ProxyEndpoint members are for evaluation purposes only and are subject to change or removal in future updates. | | [`ASPIREPUBLISHERS001`](aspirepublishers001.md) | Error | Publishers are for evaluation purposes only and are subject to change or removal in future updates. | diff --git a/docs/extensibility/dev-tunnels-integration.md b/docs/extensibility/dev-tunnels-integration.md index 76ba0fbd2d..f98dfbb407 100644 --- a/docs/extensibility/dev-tunnels-integration.md +++ b/docs/extensibility/dev-tunnels-integration.md @@ -101,6 +101,7 @@ The preceding code: When another resource references a dev tunnel, environment variables are injected using the [Aspire service discovery](../service-discovery/overview.md) configuration format. Use the `WithReference` overloads that accept the `IResourceBuilder` parameter to reference a dev tunnel. This injects environment variables like: ```env +WEB_HTTPS=https://myweb-1234.westeurope.devtunnels.ms/ services__web__https__0=https://myweb-1234.westeurope.devtunnels.ms/ ``` diff --git a/docs/extensibility/interaction-service.md b/docs/extensibility/interaction-service.md index 20d39e344f..e3e07f1e61 100644 --- a/docs/extensibility/interaction-service.md +++ b/docs/extensibility/interaction-service.md @@ -87,7 +87,7 @@ These approaches help you create interactive, user-friendly experiences for loca > }); > ``` > -> For CLI specific contexts, the interaction service is retrieved from either the `PublishingContext` or `DeploymentContext` depending on the operation being performed. +> For CLI specific contexts, the interaction service is retrieved from either the `PublishingContext` or `PipelineStepContext` depending on the operation being performed. ## Display messages diff --git a/docs/fundamentals/app-host-overview.md b/docs/fundamentals/app-host-overview.md index d3be75c638..483055705e 100644 --- a/docs/fundamentals/app-host-overview.md +++ b/docs/fundamentals/app-host-overview.md @@ -46,28 +46,45 @@ When you call - - + Exe - net9.0 + net10.0 - - - - ``` +> [!NOTE] +> The alternative approach of explicitly listing the SDK and package reference still works and isn't a requirement to change existing projects: +> +> ```xml +> +> +> +> +> +> Exe +> net10.0 +> +> +> +> +> +> +> +> +> +> +> ``` + The following code describes an AppHost `Program` with two project references and a Redis cache: ```csharp @@ -113,11 +130,16 @@ Aspire projects are made up of a set of resources. The primary base resource typ | Method | Resource type | Description | |--|--|--| | | | A .NET project, for example, an ASP.NET Core web app. | +| `AddCSharpApp` | `CSharpAppResource` | A C# project or file-based app, for example, a _*.cs_ file, _*.csproj_ file, or project directory. | | | | A container image, such as a Docker image. | | | | An executable file, such as a [Node.js app](../get-started/build-aspire-apps-with-nodejs.md). | | | | A parameter resource that can be used to [express external parameters](external-parameters.md). | -Project resources represent .NET projects that are part of the app model. When you add a project reference to the AppHost project, the Aspire SDK generates a type in the `Projects` namespace for each referenced project. For more information, see [Aspire SDK: Project references](dotnet-aspire-sdk.md#project-references). + + +Project resources represent .NET projects that are part of the app model. When you add a project reference to the AppHost project, the Aspire SDK generates a type in the `Projects` namespace for each referenced project. For more information, see [Aspire SDK: Project references](dotnet-aspire-sdk.md#project-references). Alternatively, you can add C# projects or file-based apps without a project reference using the `AddCSharpApp` method. To add a project to the app model, use the method: @@ -140,6 +162,25 @@ var apiservice = builder.AddProject("apiservice") The preceding code adds three replicas of the "apiservice" project resource to the app model. For more information, see [Aspire dashboard: Resource replicas](dashboard/explore.md#resource-replicas). +C# app resources represent C# projects or file-based apps that are part of the app model. Unlike , which requires a project reference, the `AddCSharpApp` method can add C# projects or file-based apps using a path to a _*.cs_ file, _*.csproj_ file, or project directory. This is useful for adding file-based apps introduced in .NET 10 or for including projects without adding a project reference to the AppHost. + +To add a C# app to the app model, use the `AddCSharpApp` method: + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +// Adds a file-based C# app "inventoryservice" from a .cs file. +var inventoryService = builder.AddCSharpApp("inventoryservice", @"..\InventoryService.cs"); + +// Adds a C# project "catalogservice" from a project directory. +var catalogService = builder.AddCSharpApp("catalogservice", @"..\CatalogService"); +``` + +The `AddCSharpApp` method supports the same configuration options as , including replicas, environment variables, and resource dependencies. + +> [!NOTE] +> The `AddCSharpApp` method is marked as experimental and requires .NET 10 SDK for file-based C# app support. For more information on file-based apps, see the [What's new in Aspire 9.5](../whats-new/dotnet-aspire-9.5.md#file-based-apphost-support-preview) documentation. + ## Reference resources A reference represents a dependency between resources. For example, you can probably imagine a scenario where a web frontend depends on a Redis cache. Consider the following example AppHost `Program` C# code: @@ -176,9 +217,16 @@ Project-to-project references are handled differently than resources that have w | Method | Environment variable | |--|--| | `WithReference(cache)` | `ConnectionStrings__cache="localhost:62354"` | -| `WithReference(apiservice)` | `services__apiservice__http__0="http://localhost:5455"`
`services__apiservice__https__0="https://localhost:7356"` | +| `WithReference(apiservice)` | `APISERVICE_HTTP="http://localhost:5455"`
`APISERVICE_HTTPS="https://localhost:7356"`
`services__apiservice__http__0="http://localhost:5455"`
`services__apiservice__https__0="https://localhost:7356"` | + +Adding a reference to the "apiservice" project results in service discovery environment variables being added to the frontend. This is because typically, project-to-project communication occurs over HTTP/gRPC. + +Aspire injects two types of environment variables for service references: -Adding a reference to the "apiservice" project results in service discovery environment variables being added to the frontend. This is because typically, project-to-project communication occurs over HTTP/gRPC. For more information, see [Aspire service discovery](../service-discovery/overview.md). +- **Simplified format** (e.g., `APISERVICE_HTTP`): Uses the pattern `{RESOURCENAME}_{ENDPOINTNAME}` in uppercase. This format is simpler and more suitable for non-.NET languages and polyglot scenarios. +- **.NET service discovery format** (e.g., `services__apiservice__http__0`): Uses the pattern `services__{servicename}__{endpointname}__{index}` in lowercase. This format is used by .NET's configuration-based service discovery. + +For more information, see [Aspire service discovery](../service-discovery/overview.md). To get specific endpoints from a or an , use one of the following endpoint APIs: @@ -202,31 +250,68 @@ var apiservice = builder.AddProject("apiservice") | Method | Environment variable | |---------------------------|-------------------------------------------------------| -| `WithReference(endpoint)` | `services__myapp__endpoint__0=https://localhost:9043` | +| `WithReference(endpoint)` | `MYAPP_ENDPOINT="https://localhost:9043"`
`services__myapp__endpoint__0="https://localhost:9043"` | The `port` parameter is the port that the container is listening on. For more information on container ports, see [Container ports](networking-overview.md#container-ports). For more information on service discovery, see [Aspire service discovery](../service-discovery/overview.md). ### Service endpoint environment variable format -In the preceding section, the method is used to express dependencies between resources. When service endpoints result in environment variables being injected into the dependent resource, the format might not be obvious. This section provides details on this format. +In the preceding section, the method is used to express dependencies between resources. When service endpoints result in environment variables being injected into the dependent resource, the format might not be obvious. This section provides details on the available formats. + +When one resource depends on another resource, the AppHost injects environment variables into the dependent resource. These environment variables configure the dependent resource to connect to the resource it depends on. Aspire provides two environment variable formats to support different scenarios: -When one resource depends on another resource, the AppHost injects environment variables into the dependent resource. These environment variables configure the dependent resource to connect to the resource it depends on. The format of the environment variables is specific to Aspire and expresses service endpoints in a way that is compatible with [Service Discovery](../service-discovery/overview.md). +#### Simplified format (polyglot-friendly) -Service endpoint environment variable names are prefixed with `services__` (double underscore), then the service name, the endpoint name, and finally the index. The index supports multiple endpoints for a single service, starting with `0` for the first endpoint and incrementing for each endpoint. +The simplified format uses the pattern `{RESOURCENAME}_{ENDPOINTNAME}` in uppercase. This format is easier to use from non-.NET languages and is recommended for polyglot scenarios. + +Consider the following environment variable examples: + +```Environment +APISERVICE_HTTP +APISERVICE_HTTPS +``` + +The preceding environment variables express HTTP and HTTPS endpoints for the `apiservice` service. A named endpoint might be expressed as follows: + +```Environment +APISERVICE_MYENDPOINT +``` + +In the preceding example, the `apiservice` service has a named endpoint called `myendpoint`. + +> [!NOTE] +> The environment variable name is based on the resource name, not the optional connection name parameter. Even when using `WithReference(resource, "customname")` to specify a custom connection name, the generated environment variables still use the resource's name (e.g., `APISERVICE_HTTP`), not the custom name. + +#### .NET service discovery format + +The .NET service discovery format is used by .NET's configuration-based service discovery. Service endpoint environment variable names are prefixed with `services__` (double underscore), then the service name, the endpoint name, and finally the index. The index supports multiple endpoints for a single service, starting with `0` for the first endpoint and incrementing for each endpoint. Consider the following environment variable examples: ```Environment services__apiservice__http__0 +services__apiservice__https__0 ``` -The preceding environment variable expresses the first HTTP endpoint for the `apiservice` service. The value of the environment variable is the URL of the service endpoint. A named endpoint might be expressed as follows: +The preceding environment variables express the first HTTP and HTTPS endpoints for the `apiservice` service. A named endpoint might be expressed as follows: ```Environment -services__apiservice__myendpoint__0 +APISERVICE_MYENDPOINT +``` + +In the preceding example, the `apiservice` service has a named endpoint called `myendpoint`. + +#### Using a specific endpoint with WithEnvironment + +To specify a custom environment variable name for a specific endpoint, use the method combined with : + +```csharp +var projectA = builder.AddProject("projecta"); +var projectB = builder.AddProject("projectb") + .WithEnvironment("PROJECTA_URL", projectA.GetEndpoint("https")); ``` -In the preceding example, the `apiservice` service has a named endpoint called `myendpoint`. The value of the environment variable is the URL of the service endpoint. +This generates a single environment variable `PROJECTA_URL` with the HTTPS endpoint URL of the `projecta` service. ## Reference existing resources diff --git a/docs/fundamentals/custom-deployments.md b/docs/fundamentals/custom-deployments.md index 68f784cc9b..bbc4c5e362 100644 --- a/docs/fundamentals/custom-deployments.md +++ b/docs/fundamentals/custom-deployments.md @@ -14,7 +14,7 @@ Aspire provides powerful APIs for building container images from your resources During publishing and deployment, the container image builder is available to create images for resources that need them. Aspire uses this builder when a resource requires a container image, such as when publishing with Docker Compose. The process involves two main components: - : The service that turns resource definitions into runnable container images. -- : The API that provides structured progress reporting during long-running operations. +- `IPipelineActivityReporter`: The API that provides structured progress reporting during long-running operations. These APIs give you fine-grained control over the image building process and provide real-time feedback to users during lengthy build operations. @@ -53,9 +53,9 @@ The class provides strong The builder performs container runtime health checks (Docker/Podman) only when at least one resource requires a Dockerfile build. This change eliminates false-positive errors in projects that publish directly from .NET assemblies. If the container runtime is required but unhealthy, the builder throws an explicit `InvalidOperationException` to surface the problem early. -## Publishing activity reporter API +## Pipeline activity reporter API -The `PublishingActivityProgressReporter` API enables structured progress reporting during [`aspire publish`](../cli-reference/aspire-publish.md) and [`aspire deploy`](../cli-reference/aspire-deploy.md) commands. This reduces uncertainty during long-running operations and surfaces failures early. +The `PipelineActivityReporter` API enables structured progress reporting during [`aspire publish`](../cli-reference/aspire-publish.md) and [`aspire deploy`](../cli-reference/aspire-deploy.md) commands. This reduces uncertainty during long-running operations and surfaces failures early. ### API overview and behavior @@ -63,7 +63,7 @@ The progress reporter uses a hierarchical model with guaranteed ordering and thr | Concept | Description | CLI Rendering | Behavior | |---------|-------------|---------------|----------| -| **Step** | Top-level phase, such as "Build images" or "Deploy workloads". | Step message with status glyph and elapsed time. | Forms a strict tree structure; nested steps are unsupported. | +| **Step** | Top-level phase, such as "Build images" or "Deploy workloads". | Step message with status glyph and elapsed time. | Forms a strict tree structure; nested steps are unsupported. Steps are created automatically during pipeline execution. | | **Task** | Discrete unit of work nested under a step. | Task message with indentation. | Belongs to a single step; supports parallel creation with deterministic completion ordering. | | **Completion state** | Final status: `Completed`, `Warning`, or `Error`. | ✅ (Completed), ⚠️ (Warning), ❌ (Error) | Each step/task transitions exactly once to a final state. | @@ -71,9 +71,9 @@ The progress reporter uses a hierarchical model with guaranteed ordering and thr The reporter API provides structured access to progress reporting with the following characteristics: -- **Acquisition**: Retrieved from `PublishingContext.ActivityReporter` or `DeployingContext.ActivityReporter`. -- **Step creation**: `CreateStepAsync(title, ct)` returns an `IPublishingActivityStep`. -- **Task creation**: `IPublishingActivityStep.CreateTaskAsync(title, ct)` returns an `IPublishingActivityTask`. +- **Acquisition**: Retrieved from `PublishingContext.ActivityReporter` or through the `PipelineStepContext.ReportingStep` property in pipeline steps. +- **Step creation**: Steps are now created automatically during pipeline execution. The `CreateStepAsync(title, ct)` method returns an `IReportingStep`. +- **Task creation**: `IReportingStep.CreateTaskAsync(title, ct)` returns an `IReportingTask`. - **State transitions**: `SucceedAsync`, `WarnAsync`, `FailAsync` methods accept a summary message. - **Completion**: `CompletePublishAsync(message, state, isDeploy, ct)` marks the entire operation. - **Ordering**: Creation and completion events preserve call order; updates are serialized. @@ -130,7 +130,7 @@ The preceding code: - Implements a publishing pipeline that builds container images and generates deployment manifests. - Uses the `IResourceContainerImageBuilder` API to build container images. -- Reports progress and completion status using the `PublishingActivityProgressReporter` API. +- Reports progress and completion status using the `PipelineActivityReporter` API. Your publishing callback might use `IResourceContainerImageBuilder` to build container images, while your deployment callback might use the built images and push them to a registry or deployment target. @@ -143,7 +143,7 @@ Like the publishing callback, the deploying callback is registered using the `De The preceding code: - Simulates deploying workloads to a Kubernetes cluster. -- Uses the `PublishingActivityProgressReporter` API to create and manage deployment steps and tasks. +- Uses the `PipelineActivityReporter` API to create and manage deployment steps and tasks. - Reports progress and marks each deployment phase as completed. - Completes the deployment operation with a final status update. - Handles cancellation through the provided `CancellationToken`. diff --git a/docs/fundamentals/dashboard/configuration.md b/docs/fundamentals/dashboard/configuration.md index e0b95466c8..fc72c2641a 100644 --- a/docs/fundamentals/dashboard/configuration.md +++ b/docs/fundamentals/dashboard/configuration.md @@ -1,7 +1,7 @@ --- title: Aspire dashboard configuration description: Aspire dashboard configuration options -ms.date: 04/15/2025 +ms.date: 10/10/2025 ms.topic: reference --- @@ -72,12 +72,13 @@ Alternatively, these same values could be configured using a JSON configuration | `ASPNETCORE_URLS` | `http://localhost:18888` | One or more HTTP endpoints through which the dashboard frontend is served. The frontend endpoint is used to view the dashboard in a browser. When the dashboard is launched by the Aspire AppHost this address is secured with HTTPS. Securing the dashboard with HTTPS is recommended. | | `ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL` | `http://localhost:18889` | The [OTLP/gRPC](https://opentelemetry.io/docs/specs/otlp/#otlpgrpc) endpoint. This endpoint hosts an OTLP service and receives telemetry using gRPC. When the dashboard is launched by the Aspire AppHost this address is secured with HTTPS. Securing the dashboard with HTTPS is recommended. | | `ASPIRE_DASHBOARD_OTLP_HTTP_ENDPOINT_URL` | `http://localhost:18890` | The [OTLP/HTTP](https://opentelemetry.io/docs/specs/otlp/#otlphttp) endpoint. This endpoint hosts an OTLP service and receives telemetry using Protobuf over HTTP. When the dashboard is launched by the Aspire AppHost the OTLP/HTTP endpoint isn't configured by default. To configure an OTLP/HTTP endpoint with the AppHost, set an `ASPIRE_DASHBOARD_OTLP_HTTP_ENDPOINT_URL` env var value in _launchSettings.json_. Securing the dashboard with HTTPS is recommended. | -| `ASPIRE_DASHBOARD_UNSECURED_ALLOW_ANONYMOUS` | `false` | Configures the dashboard to not use authentication and accepts anonymous access. This setting is a shortcut to configuring `Dashboard:Frontend:AuthMode` and `Dashboard:Otlp:AuthMode` to `Unsecured`. | +| `ASPIRE_DASHBOARD_MCP_ENDPOINT_URL` | `http://localhost:18891` | The [Aspire MCP](mcp-server.md) endpoint. When this value isn't specified then the MCP server is hosted with an `ASPNETCORE_URLS` endpoint. The MCP server can be disabled by configuring `Dashboard:Mcp:Disabled` to `true`. When the dashboard is launched by the Aspire AppHost this address is secured with HTTPS. Securing the dashboard with HTTPS is recommended. | +| `ASPIRE_DASHBOARD_UNSECURED_ALLOW_ANONYMOUS` | `false` | Configures the dashboard to not use authentication and accepts anonymous access. This setting is a shortcut to configuring `Dashboard:Frontend:AuthMode`, `Dashboard:Otlp:AuthMode` and `Dashboard:Mcp:AuthMode` to `Unsecured`. | | `ASPIRE_DASHBOARD_CONFIG_FILE_PATH` | `null` | The path for a JSON configuration file. If the dashboard is being run in a Docker container, then this is the path to the configuration file in a mounted volume. This value is optional. | | `ASPIRE_DASHBOARD_FILE_CONFIG_DIRECTORY` | `null` | The directory where the dashboard looks for key-per-file configuration. This value is optional. | | `ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL` | `null` | The gRPC endpoint to which the dashboard connects for its data. If this value is unspecified, the dashboard shows telemetry data but no resource list or console logs. This setting is a shortcut to `Dashboard:ResourceServiceClient:Url`. | -## Frontend authentication +## Frontend The dashboard frontend endpoint authentication is configured with `Dashboard:Frontend:AuthMode`. The frontend can be secured with OpenID Connect (OIDC) or browser token authentication. @@ -87,10 +88,12 @@ Browser token authentication works by the frontend asking for a token. The token |--|--|--| | `Dashboard:Frontend:AuthMode` | `BrowserToken` | Can be set to `BrowserToken`, `OpenIdConnect` or `Unsecured`. `Unsecured` should only be used during local development. It's not recommended when hosting the dashboard publicly or in other settings. | | `Dashboard:Frontend:BrowserToken` | `null` | Specifies the browser token. If the browser token isn't specified, then the dashboard generates one. Tooling that wants to automate logging in with browser token authentication can specify a token and open a browser with the token in the query string. A new token should be generated each time the dashboard is launched. | +| `Dashboard:Frontend:PublicUrl` | `null` | Specifies the public URL used to access the dashboard frontend. The public URL is used when constructing links to the dashboard frontend. If a public URL isn't specified, the frontend endpoint is used instead. This setting is important when the dashboard is accessed through a proxy and the dashboard endpoint isn't directly reachable. | | `Dashboard:Frontend:OpenIdConnect:NameClaimType` | `name` | Specifies one or more claim types that should be used to display the authenticated user's full name. Can be a single claim type or a comma-delimited list of claim types. | | `Dashboard:Frontend:OpenIdConnect:UsernameClaimType` | `preferred_username` | Specifies one or more claim types that should be used to display the authenticated user's username. Can be a single claim type or a comma-delimited list of claim types. | | `Dashboard:Frontend:OpenIdConnect:RequiredClaimType` | `null` | Specifies the claim that must be present for authorized users. Authorization fails without this claim. This value is optional. | | `Dashboard:Frontend:OpenIdConnect:RequiredClaimValue` | `null` | Specifies the value of the required claim. Only used if `Dashboard:Frontend:OpenIdConnect:RequireClaimType` is also specified. This value is optional. | +| `Dashboard:Frontend:OpenIdConnect:ClaimActions` | `null` | A collection of claim actions to configure how claims are mapped from the OpenID Connect user info endpoint. Each claim action can map JSON properties to claims. This value is optional. | | `Authentication:Schemes:OpenIdConnect:Authority` | `null` | URL to the identity provider (IdP). | | `Authentication:Schemes:OpenIdConnect:ClientId` | `null` | Identity of the relying party (RP). | | `Authentication:Schemes:OpenIdConnect:ClientSecret` | `null` | A secret that only the real RP would know. | @@ -101,45 +104,32 @@ Browser token authentication works by the frontend asking for a token. The token > > For more information, see [Configure ASP.NET Core to work with proxy servers and load balancers](/aspnet/core/host-and-deploy/proxy-load-balancer). -## OTLP authentication +### Claim actions -The OTLP endpoint authentication is configured with `Dashboard:Otlp:AuthMode`. The OTLP endpoint can be secured with an API key or client certificate authentication. - -API key authentication works by requiring each OTLP request to have a valid `x-otlp-api-key` header value. It must match either the primary or secondary key. - -Client certificate authentication validates the TLS connection's client certificate. When a request with a client certificate is received, two sets of validation are performed: - -- **ASP.NET Core certificate authentication validation:** By default this verifies that the certificate chains to a trusted root on the machine, the certificate hasn't expired, and that its Extended Key Usage value is appropriate for Client Authentication. For more information on this validation and how to configure it, see [Configure ASP.NET Core certificate validation](/aspnet/core/security/authentication/certauth#configure-certificate-validation). -- **Optional explicit certificate allowlist:** You can optionally configure an explicit list of allowed certificates using `AllowedCertificates`. If `AllowedCertificates` is configured and a client certificate does not match any of the listed thumbprints, the request is rejected. If no allowed certificates are specified, all certificates that pass the minimum validation are accepted. - -| Option | Default value | Description | -|--|--|--| -| `Dashboard:Otlp:AuthMode` | `Unsecured` | Can be set to `ApiKey`, `Certificate` or `Unsecured`. `Unsecured` should only be used during local development. It's not recommended when hosting the dashboard publicly or in other settings. | -| `Dashboard:Otlp:PrimaryApiKey` | `null` | Specifies the primary API key. The API key can be any text, but a value with at least 128 bits of entropy is recommended. This value is required if auth mode is API key. | -| `Dashboard:Otlp:SecondaryApiKey` | `null` | Specifies the secondary API key. The API key can be any text, but a value with at least 128 bits of entropy is recommended. This value is optional. If a second API key is specified, then the incoming `x-otlp-api-key` header value can match either the primary or secondary key. | -| `Dashboard:Otlp:SuppressUnsecuredTelemetryMessage` | `false` | Suppresses the unsecured telemetry warning message displayed in the dashboard UI and console when OTLP endpoints are configured with `Unsecured` auth mode. When set to `true`, the warning about unsecured OTLP endpoints won't be displayed. Only suppress this warning in controlled environments where security is managed externally. | -| `Dashboard:Otlp:AllowedCertificates` | `null` | Specifies a list of allowed client certificates. See [allowed certificates](#allowed-certificates) for more information. | -| Properties of | `null` | Values inside configuration section `Dashboard:Otlp:CertificateAuthOptions:*` are bound to `CertificateAuthenticationOptions`, such as `AllowedCertificateTypes`. | - -### Allowed certificates - -When using client certificate authentication you can optionally configure an explicit list of allowed certificates using `AllowedCertificates`. Each allowed certificate in the `Dashboard:Otlp:AllowedCertificates` collection supports the following properties: +Claim actions configure how claims are mapped from the JSON returned by the OpenID Connect user info endpoint to the user's claims identity. Each claim action in the `Dashboard:Frontend:OpenIdConnect:ClaimActions` collection supports the following properties: | Property | Description | |--|--| -| `Thumbprint` (required) | The SHA256 thumbprint of the certificate to allow. | +| `ClaimType` (required) | The claim type to create. | +| `JsonKey` (required) | The JSON key to map from. | +| `SubKey` (optional) | The sub-key within the JSON key to map from. Used when the value is nested within another JSON object. | +| `IsUnique` (optional) | When `true`, ensures only one claim of this type exists. If a claim already exists, it won't be added again. Defaults to `false`. | +| `ValueType` (optional) | The claim value type. Defaults to `string`. | -The following example shows how to configure allowed certificates using JSON configuration: +The following example shows how to configure claim actions using JSON configuration: ```json { "Dashboard": { - "Otlp": { - "AllowedCertificates": [ - { - "Thumbprint": "HEX_SHA256_THUMBPRINT" - } - ] + "Frontend": { + "OpenIdConnect": { + "ClaimActions": [ + { + "ClaimType": "role", + "JsonKey": "role" + } + ] + } } } } @@ -148,11 +138,27 @@ The following example shows how to configure allowed certificates using JSON con Or using environment variables for configuration: ```bash -export Dashboard__Otlp__AllowedCertificates__0__Thumbprint="HEX_SHA256_THUMBPRINT" +export Dashboard__Frontend__OpenIdConnect__ClaimActions__0__ClaimType="role" +export Dashboard__Frontend__OpenIdConnect__ClaimActions__0__JsonKey="role" ``` -> [!NOTE] -> If no allowed certificates are configured then all certificates that pass [ASP.NET Core certificate validation](/aspnet/core/security/authentication/certauth#configure-certificate-validation) can authenticate. +## OTLP + +The OTLP endpoint authentication is configured with `Dashboard:Otlp:AuthMode`. The OTLP endpoint can be secured with an API key or client certificate authentication. + +API key authentication works by requiring each OTLP request to have a valid `x-otlp-api-key` header value. It must match either the primary or secondary key. + +Client certificate authentication validates the TLS connection's client certificate. When a request with a client certificate is received, two sets of validation are performed: + +- **ASP.NET Core certificate authentication validation:** By default this verifies that the certificate chains to a trusted root on the machine, the certificate hasn't expired, and that its Extended Key Usage value is appropriate for Client Authentication. For more information on this validation and how to configure it, see [Configure ASP.NET Core certificate validation](/aspnet/core/security/authentication/certauth#configure-certificate-validation). +- **Optional explicit certificate allowlist:** You can optionally configure an explicit list of allowed certificates using `AllowedCertificates`. If `AllowedCertificates` is configured and a client certificate does not match any of the listed thumbprints, the request is rejected. If no allowed certificates are specified, all certificates that pass the minimum validation are accepted. + +| Option | Default value | Description | +|--|--|--| +| `Dashboard:Otlp:AuthMode` | `Unsecured` | Can be set to `ApiKey`, `Certificate` or `Unsecured`. `Unsecured` should only be used during local development. It's not recommended when hosting the dashboard publicly or in other settings. | +| `Dashboard:Otlp:PrimaryApiKey` | `null` | Specifies the primary API key. The API key can be any text, but a value with at least 128 bits of entropy is recommended. This value is required if auth mode is API key. | +| `Dashboard:Otlp:SecondaryApiKey` | `null` | Specifies the secondary API key. The API key can be any text, but a value with at least 128 bits of entropy is recommended. This value is optional. If a second API key is specified, then the incoming `x-otlp-api-key` header value can match either the primary or secondary key. | +| `Dashboard:Otlp:SuppressUnsecuredMessage` | `false` | Suppresses the unsecured message displayed in the dashboard when `Dashboard:Otlp:AuthMode` is `Unsecured`. This message should only be suppressed if an external frontdoor proxy is securing access to the endpoint. | ## OTLP CORS @@ -182,6 +188,21 @@ Consider the following configuration options: > [!NOTE] > The dashboard only supports the `POST` method for sending telemetry and doesn't allow configuration of the _allowed methods_ (`Access-Control-Allow-Methods`) for CORS. +## MCP + +The MCP endpoint authentication is configured with `Dashboard:Mcp:AuthMode`. The MCP endpoint can be secured with API key authentication. + +API key authentication works by requiring each MCP request to have a valid `x-mcp-api-key` header value. It must match either the primary or secondary key. + +| Option | Default value | Description | +|--|--|--| +| `Dashboard:Mcp:AuthMode` | `Unsecured` | Can be set to `ApiKey` or `Unsecured`. `Unsecured` should only be used during local development. It's not recommended when hosting the dashboard publicly or in other settings. | +| `Dashboard:Mcp:PrimaryApiKey` | `null` | Specifies the primary API key. The API key can be any text, but a value with at least 128 bits of entropy is recommended. This value is required if auth mode is API key. | +| `Dashboard:Mcp:SecondaryApiKey` | `null` | Specifies the secondary API key. The API key can be any text, but a value with at least 128 bits of entropy is recommended. This value is optional. If a second API key is specified, then the incoming `x-mcp-api-key` header value can match either the primary or secondary key. | +| `Dashboard:Mcp:SuppressUnsecuredMessage` | `false` | Suppresses the unsecured message displayed in the dashboard when `Dashboard:Mcp:AuthMode` is `Unsecured`. This message should only be suppressed if an external frontdoor proxy is securing access to the endpoint. | +| `Dashboard:Mcp:PublicUrl` | `null` | Specifies the public URL used to access the MCP server. The public URL is used when constructing links to the MCP server. If a public URL isn't specified, the MCP endpoint is used instead. This setting is important when the dashboard is accessed through a proxy and the dashboard endpoint isn't directly reachable. | +| `Dashboard:Mcp:Disabled` | `false` | Disables the MCP server and remove MCP UI in the dashboard. | + ## Resources The dashboard connects to a resource service to load and display resource information. The client is configured in the dashboard for how to connect to the service. diff --git a/docs/fundamentals/dashboard/mcp-server.md b/docs/fundamentals/dashboard/mcp-server.md new file mode 100644 index 0000000000..3851f5b8b1 --- /dev/null +++ b/docs/fundamentals/dashboard/mcp-server.md @@ -0,0 +1,105 @@ +--- +title: Aspire MCP server +description: Learn how to use Aspire MCP server to develop your apps. +ms.date: 11/10/2025 +ms.topic: reference +--- + +# Aspire MCP server + +The Aspire MCP server is a local [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server. Aspire MCP integrates Aspire into your development AI eco-system. + +With Aspire MCP, you can: + +- Query resources, including resource states, endpoints, health status and commands. +- Debug with real-time console logs. +- Investigate development time telemetry, such as structured logs and distributed tracing across resources. +- Execute resource commands. + +## Get started + +To get started, configure your local AI assistant to use Aspire MCP. + +1. Run your Aspire app. +2. Open the Aspire dashboard and click on the MCP button in the top right corner of the dashboard. This launches a dialog that contains instructions for using Aspire MCP. +3. Use the specified details in the dialog to configure your local AI assistant. + +Important settings required to use Aspire MCP: + +- `url` - Aspire MCP address. +- `type` - `http` to indicate Aspire MCP is a [streamable-HTTP MCP server](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http). +- `x-mcp-api-key` - An HTTP header with a key to secure access to the MCP server. + +:::image type="content" source="media/mcp-server/mcp-dialog.png" lightbox="media/mcp-server/mcp-dialog.png" alt-text="A screenshot of the Aspire MCP dialog in the dashboard with the url and API header highlighted."::: + +There isn't a standard way to configure AI assistants with new MCP servers. Configuration varies depending on your local AI assistant: + +- [Claude Code](https://docs.claude.com/en/docs/claude-code/mcp) +- [Codex](https://developers.openai.com/codex/mcp/) +- [Copilot CLI](https://docs.github.com/en/copilot/how-tos/use-copilot-agents/use-copilot-cli#add-an-mcp-server) +- [Cursor](https://cursor.com/docs/context/mcp#installing-mcp-servers) +- [VS Code](https://code.visualstudio.com/docs/copilot/customization/mcp-servers) +- [Visual Studio](/visualstudio/ide/mcp-servers#options-for-adding-an-mcp-server) + +## Your first prompts + +After configuration, try asking your AI assistant: + +> Are all my resources running? + +> Analyze HTTP requests performance for RESOURCE_NAME. + +> Restart unhealthy resources. + +## Tools + +The Aspire MCP server provides the following tools: + +- `list_resources` - Lists all resources, including their state, health status, source, endpoints, and commands. +- `list_console_logs` - Lists console logs for a resource. +- `list_structured_logs` - Lists structured logs, optionally filtered by resource name. +- `list_traces` - Lists distributed traces. Traces can be filtered using an optional resource name parameter. +- `list_trace_structured_logs` - Lists structured logs for a trace. +- `execute_resource_command` - Executes a resource command. This tool accepts parameters for the resource name and command name. + +By default all resources, console logs and telemetry is accessible by Aspire MCP. Resources and associated telemetry can be excluded from MCP results by annotating the resource in the app host with `ExcludeFromMcp()`. + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +var apiService = builder.AddProject("apiservice") + .ExcludeFromMcp(); + +builder.AddProject("webfrontend") + .WithExternalHttpEndpoints() + .WithReference(apiService); + +builder.Build().Run(); +``` + +## Troubleshooting + +### MCP connection secured with self-signed HTTPS certificate not supported by some AI assistants + +An MCP connection secured with HTTPS is recommended for security. However, some AI assistants currently don't support calling endpoints secured with a trusted, self-signed certificate. This includes the Aspire MCP, which is secured using [a self-signed certificate](/dotnet/core/additional-tools/self-signed-certificates-guide). + +Currently the only work around for using Aspire MCP with these AI assistants is to configure an `http` MCP endpoint. + +- If you already launch your Aspire app with [the `http` launch profile](/dotnet/aspire/fundamentals/launch-profiles) then your app isn't using HTTPS and you don't need to do anything. +- If you use HTTPS everywhere, you can configure just the MCP endpoint to use `http` by updating *launchSettings.json*. + - Set `ASPIRE_DASHBOARD_MCP_ENDPOINT_URL` to an `http` address. + - Add `ASPIRE_ALLOW_UNSECURED_TRANSPORT` set to `true`. + +:::code language="json" source="snippets/Mcp/launchSettings.json" highlight="14,15"::: + +> [!WARNING] +> This configuration will remove transport security from Aspire MCP communication. It could allow data to be read by a third party. + +## Limitations + +### Data size + +AI models have limits on how much data they can process at once. Aspire MCP may limit the amount of data returned from tools when necessary. + +- Large data fields (e.g., long exception stack traces) may be truncated. +- Requests involving large collections of telemetry may be shortened by omitting older items. diff --git a/docs/fundamentals/dashboard/media/mcp-server/mcp-dialog.png b/docs/fundamentals/dashboard/media/mcp-server/mcp-dialog.png new file mode 100644 index 0000000000..ca6404ccef Binary files /dev/null and b/docs/fundamentals/dashboard/media/mcp-server/mcp-dialog.png differ diff --git a/docs/fundamentals/dashboard/snippets/Mcp/launchSettings.json b/docs/fundamentals/dashboard/snippets/Mcp/launchSettings.json new file mode 100644 index 0000000000..55e2c2eedf --- /dev/null +++ b/docs/fundamentals/dashboard/snippets/Mcp/launchSettings.json @@ -0,0 +1,19 @@ +{ + "profiles": { + "https": { + "commandName": "Project", + "launchBrowser": true, + "dotnetRunMessages": true, + "applicationUrl": "https://localhost:15887;http://localhost:15888", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:16037", + "ASPIRE_DASHBOARD_OTLP_HTTP_ENDPOINT_URL": "https://localhost:16038", + "ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:17037", + "ASPIRE_DASHBOARD_MCP_ENDPOINT_URL": "http://localhost:16036", + "ASPIRE_ALLOW_UNSECURED_TRANSPORT": "true" + } + } + } +} \ No newline at end of file diff --git a/docs/fundamentals/orchestrate-resources.md b/docs/fundamentals/orchestrate-resources.md index 4c50f3a659..9cb3ae9182 100644 --- a/docs/fundamentals/orchestrate-resources.md +++ b/docs/fundamentals/orchestrate-resources.md @@ -285,7 +285,7 @@ var frontend = builder.AddProject("frontend") .WithReference(api); ``` -This configuration injects an environment variable like `services__api__https__0=https://api.example.com/` into the frontend project, enabling service discovery through the standard .NET service discovery mechanisms. +This configuration injects environment variables like `API_HTTPS=https://api.example.com/` and `services__api__https__0=https://api.example.com/` into the frontend project, enabling service discovery through both simplified and .NET-specific service discovery mechanisms. ### External service lifecycle diff --git a/docs/get-started/build-aspire-apps-with-nodejs.md b/docs/get-started/build-aspire-apps-with-nodejs.md index 2aaa6da893..672b48eb4a 100644 --- a/docs/get-started/build-aspire-apps-with-nodejs.md +++ b/docs/get-started/build-aspire-apps-with-nodejs.md @@ -147,10 +147,10 @@ There are several key modifications from the original Angular template. The firs :::code language="javascript" source="~/aspire-samples/samples/AspireWithJavaScript/AspireJavaScript.Angular/proxy.conf.js"::: -The Aspire AppHost sets the `services__weatherapi__http__0` environment variable, which is used to resolve the "weatherapi" service endpoint. The preceding configuration proxies HTTP requests that start with `/api` to the target URL specified in the environment variable. +The Aspire AppHost sets the `WEATHERAPI_HTTPS` and `WEATHERAPI_HTTP` environment variables, which are used to resolve the "weatherapi" service endpoints. The preceding configuration proxies HTTP requests that start with `/api` to the target URL specified in the environment variable. -Then include the proxy file to in the _angular.json_ file. -Update the `serve` target to include the `proxyConfig` option, referencing to the created _proxy.conf.js_ file. +Then include the proxy file in the _angular.json_ file. +Update the `serve` target to include the `proxyConfig` option, referencing the created _proxy.conf.js_ file. The Angular CLI will now use the proxy configuration while serving the Angular client app. :::code language="javascript" source="~/aspire-samples/samples/AspireWithJavaScript/AspireJavaScript.Angular/angular.json" range="59-73" highlight="13"::: @@ -217,7 +217,7 @@ As the `TheWelcome` integration is `mounted`, it calls the `/api/weatherforecast :::code language="typescript" source="~/aspire-samples/samples/AspireWithJavaScript/AspireJavaScript.Vue/vite.config.ts"::: -Additionally, the Vite config specifies the `server.proxy` property to forward requests to the "weatherapi" service. This is achieved by using the `services__weatherapi__http__0` environment variable, which is set by the Aspire AppHost. +Additionally, the Vite config specifies the `server.proxy` property to forward requests to the "weatherapi" service. This is achieved by using the `WEATHERAPI_HTTPS` and `WEATHERAPI_HTTP` environment variables, which are set by the Aspire AppHost. The final update from the template is made to the _TheWelcome.vue_ file. This file calls the `/api/WeatherForecast` endpoint to retrieve the weather forecast data, and displays the data in a table. It includes [CSS, HTML, and TypeScript updates](https://github.com/dotnet/aspire-samples/blob/ef6868b0999c6eea3d42a10f2b20433c5ea93720/samples/AspireWithJavaScript/AspireJavaScript.Vue/src/components/TheWelcome.vue). diff --git a/docs/github/github-models-integration.md b/docs/github/github-models-integration.md index 9e63f86656..77fdcf40b7 100644 --- a/docs/github/github-models-integration.md +++ b/docs/github/github-models-integration.md @@ -39,7 +39,8 @@ To add a `GitHubModelResource` to your AppHost project, call the `AddGitHubModel ```csharp var builder = DistributedApplication.CreateBuilder(args); -var chat = builder.AddGitHubModel("chat", "openai/gpt-4o-mini"); +var model = GitHubModel.OpenAI.OpenAIGpt4oMini; +var chat = builder.AddGitHubModel("chat", model); builder.AddProject() .WithReference(chat); @@ -47,7 +48,10 @@ builder.AddProject() // After adding all resources, run the app... ``` -The preceding code adds a GitHub Model resource named `chat` using the `openai/gpt-4o-mini` model. The method passes the connection information to the `ExampleProject` project. +The preceding code adds a GitHub Model resource named `chat` using the constant for OpenAI's GPT-4o-mini model. The method passes the connection information to the `ExampleProject` project. + +> [!TIP] +> Use the strongly-typed constants to avoid typos and ensure you're using valid model identifiers. These constants are grouped by publisher (for example, `GitHubModel.OpenAI.OpenAIGpt4oMini`, `GitHubModel.Microsoft.Phi4MiniInstruct`, `GitHubModel.DeepSeek.DeepSeekV30324`). ### Specify an organization @@ -57,7 +61,8 @@ For organization-specific requests, you can specify an organization parameter: var builder = DistributedApplication.CreateBuilder(args); var organization = builder.AddParameter("github-org"); -var chat = builder.AddGitHubModel("chat", "openai/gpt-4o-mini", organization); +var model = GitHubModel.OpenAI.OpenAIGpt4oMini; +var chat = builder.AddGitHubModel("chat", model, organization); builder.AddProject() .WithReference(chat); @@ -76,7 +81,8 @@ The GitHub Models integration supports multiple ways to configure authentication By default, the integration creates a parameter named `{resource_name}-gh-apikey` that automatically falls back to the `GITHUB_TOKEN` environment variable: ```csharp -var chat = builder.AddGitHubModel("chat", "openai/gpt-4o-mini"); +var model = GitHubModel.OpenAI.OpenAIGpt4oMini; +var chat = builder.AddGitHubModel("chat", model); ``` Then in user secrets: @@ -95,7 +101,8 @@ You can also specify a custom parameter for the API key: ```csharp var apiKey = builder.AddParameter("my-api-key", secret: true); -var chat = builder.AddGitHubModel("chat", "openai/gpt-4o-mini") +var model = GitHubModel.OpenAI.OpenAIGpt4oMini; +var chat = builder.AddGitHubModel("chat", model) .WithApiKey(apiKey); ``` @@ -114,7 +121,8 @@ Then in user secrets: You can add health checks to verify the GitHub Models endpoint accessibility and API key validity: ```csharp -var chat = builder.AddGitHubModel("chat", "openai/gpt-4o-mini") +var model = GitHubModel.OpenAI.OpenAIGpt4oMini; +var chat = builder.AddGitHubModel("chat", model) .WithHealthCheck(); ``` @@ -123,14 +131,14 @@ var chat = builder.AddGitHubModel("chat", "openai/gpt-4o-mini") ### Available models -GitHub Models supports various AI models. Some popular options include: +GitHub Models supports various AI models. Use the strongly-typed constants for the most up-to-date list of available models. Some popular options include: -- `openai/gpt-4o-mini` -- `openai/gpt-4o` -- `deepseek/DeepSeek-V3-0324` -- `microsoft/Phi-4-mini-instruct` +- `GitHubModel.OpenAI.OpenAIGpt4oMini` +- `GitHubModel.OpenAI.OpenAIGpt41Mini` +- `GitHubModel.DeepSeek.DeepSeekV30324` +- `GitHubModel.Microsoft.Phi4MiniInstruct` -Check the [GitHub Models documentation](https://docs.github.com/github-models) for the most up-to-date list of available models. +Check the [GitHub Models documentation](https://docs.github.com/github-models) for more information about these models and their capabilities. ## Client integration @@ -171,7 +179,6 @@ public class ExampleService(ChatCompletionsClient client) public async Task GetResponseAsync(string prompt) { var response = await client.GetChatCompletionsAsync( - "openai/gpt-4o-mini", new[] { new ChatMessage(ChatRole.User, prompt) @@ -237,7 +244,7 @@ public class ChatService(OpenAIClient client) { public async Task GetChatResponseAsync(string prompt) { - var chatClient = client.GetChatClient("openai/gpt-4o-mini"); + var chatClient = client.GetChatClient(GitHubModel.OpenAI.OpenAIGpt4oMini); var response = await chatClient.CompleteChatAsync( new[] @@ -271,7 +278,8 @@ When running an app in GitHub Codespaces or GitHub Actions, the `GITHUB_TOKEN` e ```csharp // No additional configuration needed in Codespaces/GitHub Actions -var chat = builder.AddGitHubModel("chat", "openai/gpt-4o-mini"); +var model = GitHubModel.OpenAI.OpenAIGpt4oMini; +var chat = builder.AddGitHubModel("chat", model); ``` ##### Personal access tokens for local development diff --git a/docs/snippets/azure/AppHost/AppHost.cs b/docs/snippets/azure/AppHost/AppHost.cs index 10f1624e1a..e492135deb 100644 --- a/docs/snippets/azure/AppHost/AppHost.cs +++ b/docs/snippets/azure/AppHost/AppHost.cs @@ -6,7 +6,7 @@ var foundryDeployment = foundry.AddDeployment( name: "chat", modelName: "Phi-4", - modelVersion: "1", + modelVersion: "7", format: "Microsoft"); builder.AddAzureAppConfiguration("config"); diff --git a/docs/testing/snippets/testing/mstest/AspireApp.Tests/EnvVarTests.cs b/docs/testing/snippets/testing/mstest/AspireApp.Tests/EnvVarTests.cs index d0bfbfd2dd..6b969f3ae7 100644 --- a/docs/testing/snippets/testing/mstest/AspireApp.Tests/EnvVarTests.cs +++ b/docs/testing/snippets/testing/mstest/AspireApp.Tests/EnvVarTests.cs @@ -21,7 +21,7 @@ public async Task WebResourceEnvVarsResolveToApiService() // Assert CollectionAssert.Contains(envVars, new KeyValuePair( - key: "services__apiservice__https__0", + key: "APISERVICE_HTTPS", value: "{apiservice.bindings.https.url}")); } } diff --git a/docs/testing/snippets/testing/nunit/AspireApp.Tests/EnvVarTests.cs b/docs/testing/snippets/testing/nunit/AspireApp.Tests/EnvVarTests.cs index e93ee69c51..9aef6e94a4 100644 --- a/docs/testing/snippets/testing/nunit/AspireApp.Tests/EnvVarTests.cs +++ b/docs/testing/snippets/testing/nunit/AspireApp.Tests/EnvVarTests.cs @@ -20,7 +20,7 @@ public async Task WebResourceEnvVarsResolveToApiService() // Assert Assert.That(envVars, Does.Contain( new KeyValuePair( - key: "services__apiservice__https__0", + key: "APISERVICE_HTTPS", value: "{apiservice.bindings.https.url}"))); } } diff --git a/docs/testing/snippets/testing/xunit/AspireApp.Tests/EnvVarTests.cs b/docs/testing/snippets/testing/xunit/AspireApp.Tests/EnvVarTests.cs index 434a1beb87..febd8169bb 100644 --- a/docs/testing/snippets/testing/xunit/AspireApp.Tests/EnvVarTests.cs +++ b/docs/testing/snippets/testing/xunit/AspireApp.Tests/EnvVarTests.cs @@ -22,7 +22,7 @@ public async Task WebResourceEnvVarsResolveToApiService() { var (key, value) = kvp; - return key is "services__apiservice__https__0" + return key is "APISERVICE_HTTPS" && value is "{apiservice.bindings.https.url}"; }); } diff --git a/docs/toc.yml b/docs/toc.yml index 69c1f3e181..01f3fd0cad 100644 --- a/docs/toc.yml +++ b/docs/toc.yml @@ -34,8 +34,8 @@ items: href: get-started/github-codespaces.md - name: Dev Containers href: get-started/dev-containers.md - - name: What's new in Aspire 9.5 - href: whats-new/dotnet-aspire-9.5.md + - name: What's new in Aspire 13 + href: https://aspire.dev/whats-new/aspire-13/ - name: Upgrade to Aspire 9.5.2 href: get-started/upgrade-to-aspire-9.md @@ -60,6 +60,9 @@ items: href: get-started/build-aspire-apps-with-python.md - name: AppHost configuration href: app-host/configuration.md + - name: Certificate trust customization + href: app-host/certificate-trust.md + displayName: certificate trust,TLS,HTTPS,development certificate,certificate authority - name: Customize resources items: - name: Persistent container services @@ -103,6 +106,8 @@ items: href: fundamentals/dashboard/explore.md - name: GitHub Copilot href: fundamentals/dashboard/copilot.md + - name: Aspire MCP + href: fundamentals/dashboard/mcp-server.md - name: Standalone mode items: - name: Overview @@ -496,6 +501,9 @@ items: href: deployment/overview.md - name: Building custom deployment pipelines href: fundamentals/custom-deployments.md + - name: Deployment state caching + href: deployment/deployment-state-caching.md + displayName: deployment state,deployment cache,clear-cache,deployment settings,aspire deploy - name: Azure deployment with Aspire items: - name: Deploy to Azure Container Apps using Aspire CLI @@ -593,6 +601,12 @@ items: href: diagnostics/aspirecosmosdb001.md - name: ASPIREHOSTINGPYTHON001 href: diagnostics/aspirehostingpython001.md + - name: ASPIREPIPELINES001 + href: diagnostics/aspirepipelines001.md + - name: ASPIREPIPELINES002 + href: diagnostics/aspirepipelines002.md + - name: ASPIREPIPELINES003 + href: diagnostics/aspirepipelines003.md - name: ASPIREPROXYENDPOINTS001 href: diagnostics/aspireproxyendpoints001.md - name: ASPIREPUBLISHERS001 @@ -631,6 +645,12 @@ items: href: diagnostics/aspireproxyendpoints001.md - name: Hosting Python apps href: diagnostics/aspirehostingpython001.md + - name: Pipeline infrastructure APIs are experimental + href: diagnostics/aspirepipelines001.md + - name: Deployment state manager APIs are experimental + href: diagnostics/aspirepipelines002.md + - name: Container image build APIs are experimental + href: diagnostics/aspirepipelines003.md - name: Publishing APIs are experimental href: diagnostics/aspirepublishers001.md - name: Publishing to Azure APIs are experimental diff --git a/docs/whats-new/index.yml b/docs/whats-new/index.yml index f46fe050b9..72db2317c8 100644 --- a/docs/whats-new/index.yml +++ b/docs/whats-new/index.yml @@ -5,13 +5,15 @@ summary: Welcome to what's new in Aspire docs. Use this page to quickly find the metadata: title: Aspire what's new? description: Learn about new and updated content in Aspire docs. - ms.date: 09/24/2025 + ms.date: 11/10/2025 ms.topic: landing-page landingContent: - title: Aspire release documentation linkLists: - linkListType: whats-new links: + - text: What's new in Aspire 13.0 + url: https://aspire.dev/whats-new/aspire-13/ - text: What's new in Aspire 9.5 url: dotnet-aspire-9.5.md - text: What's new in Aspire 9.4 diff --git a/docs/whats-new/toc.yml b/docs/whats-new/toc.yml index af81bd3620..cbd0e2a7cc 100644 --- a/docs/whats-new/toc.yml +++ b/docs/whats-new/toc.yml @@ -6,6 +6,8 @@ items: - name: Latest product updates expanded: true items: + - name: What's new in Aspire 13.0 + href: https://aspire.dev/whats-new/aspire-13/ - name: What's new in Aspire 9.5 href: dotnet-aspire-9.5.md - name: What's new in Aspire 9.4