diff --git a/.github/prompts/adhere-to-writing-style.prompt.md b/.github/prompts/adhere-to-writing-style.prompt.md index 5140ca11f9..52ee18ed89 100644 --- a/.github/prompts/adhere-to-writing-style.prompt.md +++ b/.github/prompts/adhere-to-writing-style.prompt.md @@ -7,6 +7,10 @@ Write for readers who may have a limited vocabulary or are not native English sp - Use active voice - Use the second person (you, your) to address the reader directly +## Lists + +Use lists more sparingly. Use them only when they help to clarify the content. Use bullet points for unordered lists and numbered lists for ordered lists. Regardless of the type of list, always use sentence-style capitalization, complete sentences, and proper punctuation. + ## Headings Headings use sentence-style capitalization. Always capitalize the first word of a heading. Do not use gerunds (e.g., "Using" or "Creating") in heading. diff --git a/.gitignore b/.gitignore index acae18877d..15b90b63e0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,8 @@ syntax: glob +.aspire/ +**/.aspire/ + # Python virtual environments .venv diff --git a/docs/app-host/configuration.md b/docs/app-host/configuration.md index 8485526328..9bf94c2172 100644 --- a/docs/app-host/configuration.md +++ b/docs/app-host/configuration.md @@ -55,6 +55,7 @@ For more information, see [.NET Aspire and launch profiles](../fundamentals/laun |--|--|--| | `ASPIRE_ALLOW_UNSECURED_TRANSPORT` | `false` | Allows communication with the app host without https. `ASPNETCORE_URLS` (dashboard address) and `ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL` (app host resource service address) must be secured with HTTPS unless true. | | `ASPIRE_CONTAINER_RUNTIME` | `docker` | Allows the user of alternative container runtimes for resources backed by containers. Possible values are `docker` (default) or `podman`. See [Setup and tooling overview for more details](../fundamentals/setup-tooling.md). | +| `ASPIRE_VERSION_CHECK_DISABLED` | `false` | When set to `true`, .NET Aspire doesn't check for newer versions on startup. | ## Resource service diff --git a/docs/app-host/eventing.md b/docs/app-host/eventing.md index 827c6cc352..049843ab9a 100644 --- a/docs/app-host/eventing.md +++ b/docs/app-host/eventing.md @@ -1,7 +1,7 @@ --- title: Eventing in .NET Aspire description: Learn how to use the .NET eventing features with .NET Aspire. -ms.date: 04/04/2025 +ms.date: 07/10/2025 --- # Eventing in .NET Aspire @@ -17,48 +17,55 @@ In this article, you learn how to use the eventing features in .NET Aspire. The following events are available in the app host and occur in the following order: 1. : This event is raised before the app host starts. -1. : This event is raised after the app host allocated endpoints. +1. : This event is raised per resource after its endpoints are allocated. 1. : This event is raised after the app host created resources. All of the preceding events are analogous to the [app host life cycles](xref:dotnet/aspire/app-host#app-host-life-cycles). That is, an implementation of the could handle these events just the same. With the eventing API, however, you can run arbitrary code when these events are raised and event define custom events—any event that implements the interface. ### Subscribe to app host events -To subscribe to the built-in app host events, use the eventing API. After you have a distributed application builder instance, walk up to the property and call the API. Consider the following sample app host _Program.cs_ file: +To subscribe to the built-in app host events, use the eventing API. After you have a distributed application builder instance, walk up to the property and call the API. Consider the following sample app host _AppHost.cs_ file: -:::code source="snippets/AspireApp/AspireApp.AppHost/Program.cs"::: +:::code source="snippets/AspireApp/AspireApp.AppHost/AppHost.cs"::: The preceding code is based on the starter template with the addition of the calls to the `Subscribe` API. The `Subscribe` API returns a instance that you can use to unsubscribe from the event. It's common to discard the returned subscriptions, as you don't usually need to unsubscribe from events as the entire app is torn down when the app host is shut down. When the app host is run, by the time the .NET Aspire dashboard is displayed, you should see the following log output in the console: -:::code language="Plaintext" source="snippets/AspireApp/AspireApp.AppHost/Console.txt" highlight="2,10-14,20"::: +:::code language="Plaintext" source="snippets/AspireApp/AspireApp.AppHost/Console.txt" highlight="2,10,12,14,16,22"::: -The log output confirms that event handlers are executed in the order of the app host life cycle events. The subscription order doesn't affect execution order. The `BeforeStartEvent` is triggered first, followed by `AfterEndpointsAllocatedEvent`, then each resource has its `ResourceEndpointsAllocatedEvent` event fired, and finally `AfterResourcesCreatedEvent`. +The log output confirms that event handlers are executed in the order of the app host life cycle events. The subscription order doesn't affect execution order. The `BeforeStartEvent` is triggered first, followed by each resource's `ResourceEndpointsAllocatedEvent`, and finally `AfterResourcesCreatedEvent`. ## Resource eventing In addition to the app host events, you can also subscribe to resource events. Resource events are raised specific to an individual resource. Resource events are defined as implementations of the interface. The following resource events are available in the listed order: -1. `InitializeResourceEvent`: Raised by orchestrators to signal to resources that they should initialize themselves. +1. : Raised by orchestrators to signal to resources that they should initialize themselves. +1. : Raised when the orchestrator allocates endpoints for a resource. 1. : Raised when a connection string becomes available for a resource. 1. : Raised before the orchestrator starts a new resource. 1. : Raised when a resource initially transitions to a ready state. ### Subscribe to resource events -To subscribe to resource events, use the eventing API. After you have a distributed application builder instance, walk up to the property and call the API. Consider the following sample app host _Program.cs_ file: +To subscribe to resource events, use the convenience-based extension methods—`On*`. After you have a distributed application builder instance, and a resource builder, walk up to the instance and chain a call to the desired `On*` event API. Consider the following sample _AppHost.cs_ file: -:::code source="snippets/AspireApp/AspireApp.ResourceAppHost/Program.cs"::: +:::code source="snippets/AspireApp/AspireApp.ResourceAppHost/AppHost.cs"::: -The preceding code subscribes to the `InitializeResourceEvent`, `ResourceReadyEvent`, `ConnectionStringAvailableEvent`, and `BeforeResourceStartedEvent` events on the `cache` resource. When is called, it returns an where `T` is a . The resource builder exposes the resource as the property. The resource in question is then passed to the `Subscribe` API to subscribe to the events on the resource. +The preceding code subscribes to the `InitializeResourceEvent`, `ResourceReadyEvent`, `ResourceEndpointsAllocatedEvent`, `ConnectionStringAvailableEvent`, and `BeforeResourceStartedEvent` events on the `cache` resource. When is called, it returns an where `T` is a . Chain calls to the `On*` methods to subscribe to the events. The `On*` methods return the same instance, so you can chain multiple calls: + +- `OnInitializeResource`: Subscribes to the . +- `OnResourceEndpointsAllocated`: Subscribes to the event. +- `OnConnectionStringAvailable`: Subscribes to the event. +- `OnBeforeResourceStarted`: Subscribes to the event. +- `OnResourceReady`: Subscribes to the event. When the app host is run, by the time the .NET Aspire dashboard is displayed, you should see the following log output in the console: -:::code language="Plaintext" source="snippets/AspireApp/AspireApp.ResourceAppHost/Console.txt" highlight="8,10,12,18"::: +:::code language="Plaintext" source="snippets/AspireApp/AspireApp.ResourceAppHost/Console.txt" highlight="8,10,12,14,20"::: > [!NOTE] -> Some events are blocking. For example, when the `BeforeResourceStartEvent` is published, the startup of the resource will be blocked until all subscriptions for that event on a given resource have completed executing. Whether an event is blocking or not depends on how it is published (see the following section). +> Some events block execution. For example, when the `BeforeResourceStartedEvent` is published, the resource startup blocks until all subscriptions for that event on a given resource finish executing. Whether an event blocks or not depends on how you publish it (see the following section). ## Publish events @@ -74,7 +81,7 @@ Then, you can subscribe and publish the event by calling the either of the follo When events are dispatched, you can control how the events are dispatched to subscribers. The event dispatch behavior is specified with the `EventDispatchBehavior` enum. The following behaviors are available: - : Fires events sequentially and blocks until they're all processed. -- : Fires events concurrently and blocks until they are all processed. +- : Fires events concurrently and blocks until they're all processed. - : Fires events sequentially but doesn't block. - : Fires events concurrently but doesn't block. @@ -82,7 +89,7 @@ The default behavior is `EventDispatchBehavior.BlockingSequential`. To override ## App Host life cycle events -As you have seen, eventing is the most flexible approach. However, in this section you learn about the alternative: life cycle events. +Eventing offers the most flexibility. However, this section explains the alternative: life cycle events. The .NET Aspire app host exposes several life cycles that you can hook into by implementing the interface. The following lifecycle methods are available: @@ -96,7 +103,7 @@ The .NET Aspire app host exposes several life cycles that you can hook into by i To register a life cycle hook, implement the interface and register the hook with the app host using the API: -:::code source="../fundamentals/snippets/lifecycles/AspireApp/AspireApp.AppHost/Program.cs"::: +:::code source="../fundamentals/snippets/lifecycles/AspireApp/AspireApp.AppHost/AppHost.cs"::: The preceding code: diff --git a/docs/app-host/snippets/AspireApp/AspireApp.AppHost/Program.cs b/docs/app-host/snippets/AspireApp/AspireApp.AppHost/AppHost.cs similarity index 71% rename from docs/app-host/snippets/AspireApp/AspireApp.AppHost/Program.cs rename to docs/app-host/snippets/AspireApp/AspireApp.AppHost/AppHost.cs index 125ee9ba4b..ab26940f0b 100644 --- a/docs/app-host/snippets/AspireApp/AspireApp.AppHost/Program.cs +++ b/docs/app-host/snippets/AspireApp/AspireApp.AppHost/AppHost.cs @@ -15,32 +15,21 @@ .WaitFor(apiService); builder.Eventing.Subscribe( - (@event, cancellationToken) => - { - // The event doesn't expose an IServiceProvider, just write to the console. - Console.WriteLine($""" - 3. '{@event.Resource.Name}' ResourceEndpointsAllocatedEvent - """); - - return Task.CompletedTask; - }); - -builder.Eventing.Subscribe( static (@event, cancellationToken) => { var logger = @event.Services.GetRequiredService>(); - logger.LogInformation("1. BeforeStartEvent"); + logger.LogInformation("2. \"{ResourceName}\" ResourceEndpointsAllocatedEvent", @event.Resource.Name); return Task.CompletedTask; }); -builder.Eventing.Subscribe( +builder.Eventing.Subscribe( static (@event, cancellationToken) => { var logger = @event.Services.GetRequiredService>(); - logger.LogInformation("2. AfterEndpointsAllocatedEvent"); + logger.LogInformation("1. BeforeStartEvent"); return Task.CompletedTask; }); @@ -50,7 +39,7 @@ { var logger = @event.Services.GetRequiredService>(); - logger.LogInformation("4. AfterResourcesCreatedEvent"); + logger.LogInformation("3. AfterResourcesCreatedEvent"); return Task.CompletedTask; }); diff --git a/docs/app-host/snippets/AspireApp/AspireApp.AppHost/AspireApp.AppHost.csproj b/docs/app-host/snippets/AspireApp/AspireApp.AppHost/AspireApp.AppHost.csproj index 34bd2e5773..e8f1054dfc 100644 --- a/docs/app-host/snippets/AspireApp/AspireApp.AppHost/AspireApp.AppHost.csproj +++ b/docs/app-host/snippets/AspireApp/AspireApp.AppHost/AspireApp.AppHost.csproj @@ -1,6 +1,6 @@ - + Exe @@ -16,8 +16,8 @@ - - + + diff --git a/docs/app-host/snippets/AspireApp/AspireApp.AppHost/Console.txt b/docs/app-host/snippets/AspireApp/AspireApp.AppHost/Console.txt index a3eadd2048..24e01cf25f 100644 --- a/docs/app-host/snippets/AspireApp/AspireApp.AppHost/Console.txt +++ b/docs/app-host/snippets/AspireApp/AspireApp.AppHost/Console.txt @@ -1,22 +1,24 @@ info: Program[0] 1. BeforeStartEvent info: Aspire.Hosting.DistributedApplication[0] - Aspire version: 9.3.0-preview.1.25262.2+6d54dc081cd2e7ea435e33f7c0e62ff6946ae66d + Aspire version: 9.4.0 info: Aspire.Hosting.DistributedApplication[0] Distributed application starting. info: Aspire.Hosting.DistributedApplication[0] Application host directory is: ../AspireApp/AspireApp.AppHost info: Program[0] - 2. AfterEndpointsAllocatedEvent - 3. 'aspire-dashboard' ResourceEndpointsAllocatedEvent - 3. 'cache' ResourceEndpointsAllocatedEvent - 3. 'apiservice' ResourceEndpointsAllocatedEvent - 3. 'webfrontend' ResourceEndpointsAllocatedEvent + 2. "cache" ResourceEndpointsAllocatedEvent +info: Program[0] + 2. "apiservice" ResourceEndpointsAllocatedEvent +info: Program[0] + 2. "webfrontend" ResourceEndpointsAllocatedEvent +info: Program[0] + 2. "aspire-dashboard" ResourceEndpointsAllocatedEvent info: Aspire.Hosting.DistributedApplication[0] Now listening on: https://localhost:17178 info: Aspire.Hosting.DistributedApplication[0] Login to the dashboard at https://localhost:17178/login?t= info: Program[0] - 4. AfterResourcesCreatedEvent + 3. AfterResourcesCreatedEvent info: Aspire.Hosting.DistributedApplication[0] Distributed application started. Press Ctrl+C to shut down. diff --git a/docs/app-host/snippets/AspireApp/AspireApp.ResourceAppHost/Program.cs b/docs/app-host/snippets/AspireApp/AspireApp.ResourceAppHost/AppHost.cs similarity index 54% rename from docs/app-host/snippets/AspireApp/AspireApp.ResourceAppHost/Program.cs rename to docs/app-host/snippets/AspireApp/AspireApp.ResourceAppHost/AppHost.cs index 0350aa75b3..2b98ae75ab 100644 --- a/docs/app-host/snippets/AspireApp/AspireApp.ResourceAppHost/Program.cs +++ b/docs/app-host/snippets/AspireApp/AspireApp.ResourceAppHost/AppHost.cs @@ -5,45 +5,52 @@ var cache = builder.AddRedis("cache"); -builder.Eventing.Subscribe( - cache.Resource, - static (@event, cancellationToken) => +cache.OnResourceReady(static (resource, @event, cancellationToken) => { var logger = @event.Services.GetRequiredService>(); - logger.LogInformation("4. ResourceReadyEvent"); + logger.LogInformation("5. OnResourceReady"); return Task.CompletedTask; }); -builder.Eventing.Subscribe(cache.Resource, - static (@event, cancellationToken) => +cache.OnInitializeResource( + static (resource, @event, cancellationToken) => { var logger = @event.Services.GetRequiredService>(); - logger.LogInformation("1. InitializeResourceEvent"); + logger.LogInformation("1. OnInitializeResource"); return Task.CompletedTask; }); -builder.Eventing.Subscribe( - cache.Resource, - static (@event, cancellationToken) => +cache.OnBeforeResourceStarted( + static (resource, @event, cancellationToken) => { var logger = @event.Services.GetRequiredService>(); - logger.LogInformation("3. BeforeResourceStartedEvent"); + logger.LogInformation("4. OnBeforeResourceStarted"); + + + return Task.CompletedTask; + }); + +cache.OnResourceEndpointsAllocated( + static (resource, @event, cancellationToken) => + { + var logger = @event.Services.GetRequiredService>(); + + logger.LogInformation("2. OnResourceEndpointsAllocated"); return Task.CompletedTask; }); -builder.Eventing.Subscribe( - cache.Resource, - static (@event, cancellationToken) => +cache.OnConnectionStringAvailable( + static (resource, @event, cancellationToken) => { var logger = @event.Services.GetRequiredService>(); - logger.LogInformation("2. ConnectionStringAvailableEvent"); + logger.LogInformation("3. OnConnectionStringAvailable"); return Task.CompletedTask; }); diff --git a/docs/app-host/snippets/AspireApp/AspireApp.ResourceAppHost/AspireApp.ResourceAppHost.csproj b/docs/app-host/snippets/AspireApp/AspireApp.ResourceAppHost/AspireApp.ResourceAppHost.csproj index 34bd2e5773..e8f1054dfc 100644 --- a/docs/app-host/snippets/AspireApp/AspireApp.ResourceAppHost/AspireApp.ResourceAppHost.csproj +++ b/docs/app-host/snippets/AspireApp/AspireApp.ResourceAppHost/AspireApp.ResourceAppHost.csproj @@ -1,6 +1,6 @@ - + Exe @@ -16,8 +16,8 @@ - - + + diff --git a/docs/app-host/snippets/AspireApp/AspireApp.ResourceAppHost/Console.txt b/docs/app-host/snippets/AspireApp/AspireApp.ResourceAppHost/Console.txt index cdfecf5a0b..7724fe58cd 100644 --- a/docs/app-host/snippets/AspireApp/AspireApp.ResourceAppHost/Console.txt +++ b/docs/app-host/snippets/AspireApp/AspireApp.ResourceAppHost/Console.txt @@ -1,20 +1,22 @@ info: Aspire.Hosting.DistributedApplication[0] - Aspire version: 9.3.0 + Aspire version: 9.4.0 info: Aspire.Hosting.DistributedApplication[0] Distributed application starting. info: Aspire.Hosting.DistributedApplication[0] Application host directory is: ../AspireApp/AspireApp.AppHost info: Program[0] - 1. InitializeResourceEvent + 1. OnInitializeResource info: Program[0] - 2. ConnectionStringAvailableEvent + 2. OnResourceEndpointsAllocated info: Program[0] - 3. BeforeResourceStartedEvent + 3. OnConnectionStringAvailable +info: Program[0] + 4. OnBeforeResourceStarted info: Aspire.Hosting.DistributedApplication[0] Now listening on: https://localhost:17222 info: Aspire.Hosting.DistributedApplication[0] Login to the dashboard at https://localhost:17222/login?t= info: Program[0] - 4. ResourceReadyEvent + 5. OnResourceReady info: Aspire.Hosting.DistributedApplication[0] Distributed application started. Press Ctrl+C to shut down. \ No newline at end of file diff --git a/docs/app-host/snippets/AspireApp/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj b/docs/app-host/snippets/AspireApp/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj index ae37426e09..9183e963ee 100644 --- a/docs/app-host/snippets/AspireApp/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj +++ b/docs/app-host/snippets/AspireApp/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj @@ -11,7 +11,7 @@ - + diff --git a/docs/app-host/snippets/AspireApp/AspireApp.Web/AspireApp.Web.csproj b/docs/app-host/snippets/AspireApp/AspireApp.Web/AspireApp.Web.csproj index 9349588100..64ac49c577 100644 --- a/docs/app-host/snippets/AspireApp/AspireApp.Web/AspireApp.Web.csproj +++ b/docs/app-host/snippets/AspireApp/AspireApp.Web/AspireApp.Web.csproj @@ -11,7 +11,7 @@ - + diff --git a/docs/architecture/overview.md b/docs/architecture/overview.md index 096f4617a8..b813b952d8 100644 --- a/docs/architecture/overview.md +++ b/docs/architecture/overview.md @@ -1,7 +1,7 @@ --- title: .NET Aspire architecture overview description: Learn about the overall architecture of .NET Aspire, including its integrations, orchestration, and networking capabilities. -ms.date: 05/09/2025 +ms.date: 07/11/2025 --- # .NET Aspire architecture overview @@ -165,7 +165,9 @@ Continuing from the [diagram in the previous](#app-host-dcp-flow) section, consi :::image type="content" source="media/dcp-architecture-thumb.png" alt-text="A diagram depicting the architecture of the Developer Control Plane (DCP)." lightbox="media/dcp-architecture.png"::: -DCP logs are streamed back to the app host, which then forwards them to the developer dashboard. While the developer dashboard exposes commands such as start, stop, and restart, these commands are not part of DCP itself. Instead, they are implemented by the app model runtime, specifically within its "dashboard service" component. These commands operate by manipulating DCP objects—creating new ones, deleting old ones, or updating their properties. For example, restarting a .NET project involves stopping and deleting the existing representing the project and creating a new one with the same specifications. For more information on commands, see [Custom resource commands in .NET Aspire](../fundamentals/custom-resource-commands.md). +DCP logs are streamed back to the app host, which then forwards them to the developer dashboard. While the developer dashboard exposes commands such as start, stop, and restart, these commands are not part of DCP itself. Instead, they are implemented by the app model runtime, specifically within its "dashboard service" component. These commands operate by manipulating DCP objects—creating new ones, deleting old ones, or updating their properties. For example, restarting a .NET project involves stopping and deleting the existing representing the project and creating a new one with the same specifications. + +For more information on container networking, see [How container networks are managed](../fundamentals/networking-overview.md#how-container-networks-are-managed). ## Developer dashboard diff --git a/docs/authentication/snippets/AspireApp/AspireApp.AppHost/AspireApp.AppHost.csproj b/docs/authentication/snippets/AspireApp/AspireApp.AppHost/AspireApp.AppHost.csproj index d58c1ae47b..ead7411d6e 100644 --- a/docs/authentication/snippets/AspireApp/AspireApp.AppHost/AspireApp.AppHost.csproj +++ b/docs/authentication/snippets/AspireApp/AspireApp.AppHost/AspireApp.AppHost.csproj @@ -1,6 +1,6 @@ - + Exe @@ -16,8 +16,8 @@ - - + + diff --git a/docs/authentication/snippets/AspireApp/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj b/docs/authentication/snippets/AspireApp/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj index ae37426e09..9183e963ee 100644 --- a/docs/authentication/snippets/AspireApp/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj +++ b/docs/authentication/snippets/AspireApp/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj @@ -11,7 +11,7 @@ - + diff --git a/docs/azure/azure-app-configuration-integration.md b/docs/azure/azure-app-configuration-integration.md index 4cb22d44f0..85cc1251c3 100644 --- a/docs/azure/azure-app-configuration-integration.md +++ b/docs/azure/azure-app-configuration-integration.md @@ -1,7 +1,7 @@ --- title: Azure App Configuration integration description: Learn how to use Azure App Configuration with .NET Aspire. -ms.date: 05/15/2025 +ms.date: 07/22/2025 --- # .NET Aspire Azure App Configuration integration @@ -54,11 +54,11 @@ When you add an to the If you're new to [Bicep](/azure/azure-resource-manager/bicep/overview), it's a domain-specific language for defining Azure resources. With .NET Aspire, you don't need to write Bicep by-hand, instead the provisioning APIs generate Bicep for you. When you publish your app, the generated Bicep is output alongside the manifest file. When you add an Azure App Configuration resource, the following Bicep is generated: -:::code language="bicep" source="../snippets/azure/AppHost/config.module.bicep"::: +:::code language="bicep" source="../snippets/azure/AppHost/config/config.bicep"::: The preceding Bicep is a module that provisions an Azure App Configuration resource. Additionally, role assignments are created for the Azure resource in a separate module: -:::code language="bicep" source="../snippets/azure/AppHost/config-roles.module.bicep"::: +:::code language="bicep" source="../snippets/azure/AppHost/config-roles/config-roles.bicep"::: The generated Bicep is a starting point and is influenced by changes to the provisioning infrastructure in C#. Customizations to the Bicep file directly will be overwritten, so make changes through the C# provisioning APIs to ensure they're reflected in the generated files. diff --git a/docs/azure/customize-azure-resources.md b/docs/azure/customize-azure-resources.md index f2a6a9855e..0db64b27a2 100644 --- a/docs/azure/customize-azure-resources.md +++ b/docs/azure/customize-azure-resources.md @@ -1,7 +1,7 @@ --- title: Customize Azure resources description: Describes how to customize your Azure infrastructure using code in .NET Aspire solutions. -ms.date: 06/23/2025 +ms.date: 07/22/2025 uid: dotnet/aspire/integrations/customize-azure-resources --- @@ -85,7 +85,7 @@ The functionality demonstrates how to add Azure infrastructure to your app host Consider the resulting Bicep file: -:::code language="bicep" source="../snippets/azure/AppHost/acr.module.bicep"::: +:::code language="bicep" source="../snippets/azure/AppHost/acr/acr.bicep"::: The Bicep file reflects the desired configuration of the Azure Container Registry, as defined by the `AddAzureInfrastructure` API. diff --git a/docs/azure/integrations-overview.md b/docs/azure/integrations-overview.md index 2b5cf65b00..05d0274de7 100644 --- a/docs/azure/integrations-overview.md +++ b/docs/azure/integrations-overview.md @@ -1,7 +1,7 @@ --- title: Azure integrations overview description: Overview of the Azure integrations available in the .NET Aspire. -ms.date: 05/09/2025 +ms.date: 07/22/2025 uid: dotnet/aspire/integrations/azure-overview --- @@ -25,11 +25,12 @@ Some Azure services can be emulated to run locally. Currently, .NET Aspire suppo | Hosting integration | Description | |--|--| -| Azure Cosmos DB | Call on the `IResourceBuilder` to configure the Cosmos DB resource to be [emulated with the NoSQL API](/azure/cosmos-db/how-to-develop-emulator). | -| Azure Event Hubs | Call on the `IResourceBuilder` to configure the Event Hubs resource to be [emulated](/azure/event-hubs/overview-emulator). | -| Azure Service Bus | Call on the `IResourceBuilder` to configure the Service Bus resource to be [emulated with Service Bus emulator](/azure/service-bus-messaging/overview-emulator). | -| Azure SignalR Service | Call on the `IResourceBuilder` to configure the SignalR resource to be [emulated with Azure SignalR emulator](/azure/azure-signalr/signalr-howto-emulator). | -| Azure Storage | Call on the `IResourceBuilder` to configure the Storage resource to be [emulated with Azurite](/azure/storage/common/storage-use-azurite). | +| [Azure AI Foundry](../azureai/azureai-foundry-integration.md) | Call `RunAsFoundryLocal` on the `IResourceBuilder` to configure the resource to be [emulated with Foundry Local](/azure/ai-foundry/foundry-local/get-started). | +| [Azure Cosmos DB](../database/azure-cosmos-db-integration.md) | Call on the `IResourceBuilder` to configure the Cosmos DB resource to be [emulated with the NoSQL API](/azure/cosmos-db/how-to-develop-emulator). | +| [Azure Event Hubs](../messaging/azure-event-hubs-integration.md) | Call on the `IResourceBuilder` to configure the Event Hubs resource to be [emulated](/azure/event-hubs/overview-emulator). | +| [Azure Service Bus](../messaging/azure-service-bus-integration.md) | Call on the `IResourceBuilder` to configure the Service Bus resource to be [emulated with Service Bus emulator](/azure/service-bus-messaging/overview-emulator). | +| [Azure SignalR Service](../real-time/azure-signalr-scenario.md) | Call on the `IResourceBuilder` to configure the SignalR resource to be [emulated with Azure SignalR emulator](/azure/azure-signalr/signalr-howto-emulator). | +| [Azure Storage](../storage/azure-storage-blobs-integration.md) | Call on the `IResourceBuilder` to configure the Storage resource to be [emulated with Azurite](/azure/storage/common/storage-use-azurite). | To have your Azure resources use the local emulators, chain a call the `RunAsEmulator` method on the Azure resource builder. This method configures the Azure resource to use the local emulator instead of the actual Azure service. @@ -44,9 +45,9 @@ Currently, .NET Aspire supports the following Azure services as containers: | Hosting integration | Details | |--|--| -| Azure Cache for Redis | Call on the `IResourceBuilder` to configure it to run locally in a container, based on the `docker.io/library/redis` image. | -| Azure PostgreSQL Flexible Server | Call on the `IResourceBuilder` to configure it to run locally in a container, based on the `docker.io/library/postgres` image. | -| Azure SQL Server | Call on the `IResourceBuilder` to configure it to run locally in a container, based on the `mcr.microsoft.com/mssql/server` image. | +| [Azure Cache for Redis](../caching/azure-cache-for-redis-integration.md) | Call on the `IResourceBuilder` to configure it to run locally in a container, based on the `docker.io/library/redis` image. | +| [Azure PostgreSQL Flexible Server](../database/azure-postgresql-integration.md) | Call on the `IResourceBuilder` to configure it to run locally in a container, based on the `docker.io/library/postgres` image. | +| [Azure SQL Server](../database/azure-sql-integration.md) | Call on the `IResourceBuilder` to configure it to run locally in a container, based on the `mcr.microsoft.com/mssql/server` image. | > [!NOTE] > Like emulators, calling `RunAsContainer` on an Azure resource builder doesn't effect the [publishing manifest](../deployment/manifest-format.md). When you publish your app, the [generated Bicep file](customize-azure-resources.md) reflects the actual Azure service, not the local container. diff --git a/docs/azure/local-provisioning.md b/docs/azure/local-provisioning.md index 8567e50ebc..32d193fed8 100644 --- a/docs/azure/local-provisioning.md +++ b/docs/azure/local-provisioning.md @@ -1,38 +1,42 @@ --- title: Local Azure provisioning description: Learn how to use Azure resources in your local development environment. -ms.date: 04/09/2025 +ms.date: 07/22/2025 uid: dotnet/aspire/local-azure-provisioning --- # Local Azure provisioning -.NET Aspire simplifies local cloud-native app development with its compelling app host model. This model allows you to run your app locally with the same configuration and services as in Azure. In this article you learn how to provision Azure resources from your local development environment through the [.NET Aspire app host](xref:dotnet/aspire/app-host). +.NET Aspire simplifies local cloud-native app development with its compelling AppHost model. This model allows you to run your app locally with the same configuration and services as in Azure. In this article, you learn how to provision Azure resources from your local development environment through the [.NET Aspire AppHost](xref:dotnet/aspire/app-host). + +The .NET Aspire dashboard provides an [interactive experience](../extensibility/interaction-service.md) for configuring Azure resources, automatically prompting you for required settings when they're missing. This streamlined approach eliminates the need for manual configuration file editing in most scenarios. > [!NOTE] > To be clear, resources are provisioned in Azure, but the provisioning process is initiated from your local development environment. To optimize your local development experience, consider using emulator or containers when available. For more information, see [Typical developer experience](integrations-overview.md#typical-developer-experience). ## Requirements -This article assumes that you have an Azure account and subscription. If you don't have an Azure account, you can create a free one at [Azure Free Account](https://azure.microsoft.com/free/). For provisioning functionality to work correctly, you'll need to be authenticated with Azure. Ensure that you have the [Azure Developer CLI](/cli/azure/install-azure-cli) installed. Additionally, you'll need to provide some configuration values so that the provisioning logic can create resources on your behalf. +This article assumes that you have an Azure account and subscription. If you don't have an Azure account, you can create a free one at [Azure Free Account](https://azure.microsoft.com/free/). For provisioning functionality to work correctly, you need to be authenticated with Azure. Ensure that you have the [Azure Developer CLI](/cli/azure/install-azure-cli) installed. + +When you run your AppHost with Azure resources, the .NET Aspire dashboard automatically prompts you for any missing configuration values, making the setup process straightforward and interactive. -## App host provisioning APIs +## AppHost provisioning APIs -The app host provides a set of APIs to express Azure resources. These APIs are available as extension methods in .NET Aspire Azure hosting libraries, extending the interface. When you add Azure resources to your app host, they'll add the appropriate provisioning functionality implicitly. In other words, you don't need to call any provisioning APIs directly. +The AppHost provides a set of APIs to express Azure resources. These APIs are available as extension methods in .NET Aspire Azure hosting libraries, extending the interface. When you add Azure resources to your AppHost, they add the appropriate provisioning functionality implicitly. In other words, you don't need to call any provisioning APIs directly. -When the app host starts, the following provisioning logic is executed: +When the AppHost starts, the following provisioning logic is executed: 1. The `Azure` configuration section is validated. -1. When invalid the dashboard and app host output provides hints as to what's missing. For more information, see [Missing configuration value hints](#missing-configuration-value-hints). -1. When valid Azure resources are conditionally provisioned: - 1. If an Azure deployment for a given resource doesn't exist, it's created and configured as a deployment. - 1. The configuration of said deployment is stamped with a checksum as a means to support only provisioning resources as necessary. +1. When invalid, the dashboard prompts you to enter the required configuration values or provides detailed error information. For more information, see [Configuration prompts and error handling](#configuration-prompts-and-error-handling). +1. When valid, Azure resources are conditionally provisioned: + 1. If an Azure deployment for a given resource doesn't exist, the AppHost creates and configures it as a deployment. + 1. The AppHost stamps the deployment configuration with a checksum to provision resources only when necessary. ### Use existing Azure resources -The app host automatically manages provisioning of Azure resources. The first time the app host runs, it provisions the resources specified in the app host. Subsequent runs don't provision the resources again unless the app host configuration changes. +The AppHost automatically manages provisioning of Azure resources. The first time the AppHost runs, it provisions the resources specified in the AppHost. Subsequent runs don't provision the resources again unless the AppHost configuration changes. -If you've already provisioned Azure resources outside of the app host and want to use them, you can provide the connection string with the API as shown in the following Azure Key Vault example: +To use Azure resources that you provision outside of the AppHost, provide the connection string with the API, as shown in the following Azure Key Vault example: ```csharp // Service registration @@ -45,9 +49,9 @@ builder.AddProject() .WithReference(secrets) ``` -The preceding code snippet shows how to add an Azure Key Vault to the app host. The API is used to add the Azure Key Vault to the app host. The `AddConnectionString` API is used to provide the connection string to the app host. +The preceding code snippet shows how to add an Azure Key Vault to the AppHost. The API is used to add the Azure Key Vault to the AppHost. The `AddConnectionString` API is used to provide the connection string to the AppHost. -Alternatively, for some Azure resources, you can opt-in to running them as an emulator with the `RunAsEmulator` API. This API is available for [Azure Cosmos DB](../database/azure-cosmos-db-integration.md) and [Azure Storage](../storage/azure-storage-integrations.md) integrations. For example, to run Azure Cosmos DB as an emulator, you can use the following code snippet: +Alternatively, for some Azure resources, you can opt in to running them as an emulator with the `RunAsEmulator` API. This API is available for [Azure Cosmos DB](../database/azure-cosmos-db-integration.md) and [Azure Storage](../storage/azure-storage-integrations.md) integrations. For example, to run Azure Cosmos DB as an emulator, you can use the following code snippet: ```csharp var cosmos = builder.AddAzureCosmosDB("cosmos") @@ -58,11 +62,38 @@ The API configures an ### .NET Aspire Azure hosting integrations -If you're using Azure resources in your app host, you're using one or more of the [.NET Aspire Azure hosting integrations](integrations-overview.md). These hosting libraries provide extension methods to the interface to add Azure resources to your app host. +If you're using Azure resources in your AppHost, you're using one or more of the [.NET Aspire Azure hosting integrations](integrations-overview.md). These hosting libraries provide extension methods to the interface to add Azure resources to your AppHost. ## Configuration -When utilizing Azure resources in your local development environment, you need to provide the necessary configuration values. Configuration values are specified under the `Azure` section: +When utilizing Azure resources in your local development environment, you need to provide the necessary configuration values. You can provide these values in two ways: + +- **Interactive prompting** (recommended): Let the .NET Aspire dashboard prompt you for the required values. +- **Manual configuration**: Specify the values directly in your configuration files. + +### Interactive configuration prompting + +When you run your AppHost and it requires Azure resources, the .NET Aspire dashboard automatically prompts you for any missing Azure configuration values. This interactive approach simplifies the setup process and eliminates the need to manually configure settings files. + +When the AppHost detects missing Azure configuration, the dashboard displays a message bar with an **Enter values** button: + +:::image type="content" source="media/azure-missing-config-prompt.png" lightbox="media/azure-missing-config-prompt.png" alt-text=".NET Aspire dashboard: Azure configuration prompt message bar."::: + +Clicking **Enter values** opens a dialog where you can provide the required Azure settings: + +:::image type="content" source="media/azure-config-dialog.png" lightbox="media/azure-config-dialog.png" alt-text=".NET Aspire dashboard: Azure configuration dialog."::: + +The configuration values you need to provide are: + +- **Subscription ID**: Your Azure subscription ID. +- **Resource Group**: The name of the resource group to use (a default name is suggested). +- **Location**: The Azure region to use. + +The dashboard automatically saves these values to your user secrets for future use, so you only need to enter them once per project. For more information on user secrets, see [Manage app secrets](/aspnet/core/security/app-secrets). + +### Manual configuration + +Alternatively, you can manually specify configuration values under the `Azure` section in your _:::no-loc text="appsettings.json":::_ file or user secrets: - `SubscriptionId`: The Azure subscription ID. - `AllowResourceGroupCreation`: A boolean value that indicates whether to create a new resource group. @@ -83,15 +114,15 @@ Consider the following example _:::no-loc text="appsettings.json":::_ configurat ``` > [!IMPORTANT] -> It's recommended to store these values as app secrets. For more information, see [Manage app secrets](/aspnet/core/security/app-secrets). +> Store these values as app secrets. For more information, see [Manage app secrets](/aspnet/core/security/app-secrets). -After you've configured the necessary values, you can start provisioning Azure resources in your local development environment. +Once you configure the necessary values—either through interactive prompting or manual configuration—you can provision Azure resources in your local development environment. ### Azure provisioning credential store -The .NET Aspire app host uses a credential store for Azure resource authentication and authorization. Depending on your subscription, the correct credential store may be needed for multi-tenant provisioning scenarios. +The .NET Aspire AppHost uses a credential store for Azure resource authentication and authorization. Depending on your subscription, the correct credential store might be needed for multitenant provisioning scenarios. -With the [📦 Aspire.Hosting.Azure](https://nuget.org/packages/Aspire.Hosting.Azure) NuGet package installed, and if your app host depends on Azure resources, the default Azure credential store relies on the . To change this behavior, you can set the credential store value in the _:::no-loc text="appsettings.json":::_ file, as shown in the following example: +With the [📦 Aspire.Hosting.Azure](https://nuget.org/packages/Aspire.Hosting.Azure) NuGet package installed, and if your AppHost depends on Azure resources, the default Azure credential store relies on the . To change this behavior, you can set the credential store value in the _:::no-loc text="appsettings.json":::_ file, as shown in the following example: ```json { @@ -114,17 +145,19 @@ As with all [configuration-based settings](/dotnet/core/extensions/configuration ### Tooling support -In Visual Studio, you can use Connected Services to configure the default Azure provisioning settings. Select the app host project, right-click on the **Connected Services** node, and select **Azure Resource Provisioning Settings**: +In Visual Studio, you can use Connected Services to configure the default Azure provisioning settings. Select the AppHost project, right-click on the **Connected Services** node, and select **Azure Resource Provisioning Settings**: -:::image type="content" loc-scope="visual-studio" source="media/azure-resource-provisioning-settings.png" lightbox="media/azure-resource-provisioning-settings.png" alt-text="Visual Studio 2022: .NET Aspire App Host project, Connected Services context menu."::: +:::image type="content" loc-scope="visual-studio" source="media/azure-resource-provisioning-settings.png" lightbox="media/azure-resource-provisioning-settings.png" alt-text="Visual Studio 2022: .NET Aspire AppHost project, Connected Services context menu."::: -This will open a dialog where you can configure the Azure provisioning settings, as shown in the following screenshot: +This opens a dialog where you can configure the Azure provisioning settings, as shown in the following screenshot: :::image type="content" loc-scope="visual-studio" source="media/azure-provisioning-settings-dialog.png" lightbox="media/azure-provisioning-settings-dialog.png" alt-text="Visual Studio 2022: Azure Resource Provisioning Settings dialog."::: -### Missing configuration value hints +### Configuration prompts and error handling + +When the `Azure` configuration section is missing, has missing values, or is invalid, the [.NET Aspire dashboard](../fundamentals/dashboard/overview.md) provides interactive prompts to help you configure the required values. The dashboard displays a message bar prompting you to **Enter values** for the missing configuration. -When the `Azure` configuration section is missing, has missing values, or is invalid, the [.NET Aspire dashboard](../fundamentals/dashboard/overview.md) provides useful hints. For example, consider an app host that's missing the `SubscriptionId` configuration value that's attempting to use an Azure Key Vault resource. The **Resources** page indicates the **State** as **Missing subscription configuration**: +If you dismiss the prompt or there are validation errors, the dashboard provides detailed error information. For example, consider an AppHost that's missing the `SubscriptionId` configuration value that's attempting to use an Azure Key Vault resource. The **Resources** page indicates the **State** as **Missing subscription configuration**: :::image type="content" source="media/resources-kv-missing-subscription.png" alt-text=".NET Aspire dashboard: Missing subscription configuration."::: diff --git a/docs/azure/media/azure-config-dialog.png b/docs/azure/media/azure-config-dialog.png new file mode 100644 index 0000000000..0ec3eee617 Binary files /dev/null and b/docs/azure/media/azure-config-dialog.png differ diff --git a/docs/azure/media/azure-missing-config-prompt.png b/docs/azure/media/azure-missing-config-prompt.png new file mode 100644 index 0000000000..c5bf08606e Binary files /dev/null and b/docs/azure/media/azure-missing-config-prompt.png differ diff --git a/docs/azure/snippets/aca/AspireAca.AppHost/AspireApp.AppHost/AspireApp.AppHost.csproj b/docs/azure/snippets/aca/AspireAca.AppHost/AspireApp.AppHost/AspireApp.AppHost.csproj index b025e4e233..5772b29f70 100644 --- a/docs/azure/snippets/aca/AspireAca.AppHost/AspireApp.AppHost/AspireApp.AppHost.csproj +++ b/docs/azure/snippets/aca/AspireAca.AppHost/AspireApp.AppHost/AspireApp.AppHost.csproj @@ -1,6 +1,6 @@ - + Exe @@ -12,8 +12,8 @@ - - + + diff --git a/docs/azure/snippets/acr/AspireAcr.AppHost/AspireAcr.AppHost/AspireAcr.AppHost.csproj b/docs/azure/snippets/acr/AspireAcr.AppHost/AspireAcr.AppHost/AspireAcr.AppHost.csproj index cd762575c0..48e85f3ecf 100644 --- a/docs/azure/snippets/acr/AspireAcr.AppHost/AspireAcr.AppHost/AspireAcr.AppHost.csproj +++ b/docs/azure/snippets/acr/AspireAcr.AppHost/AspireAcr.AppHost/AspireAcr.AppHost.csproj @@ -1,6 +1,6 @@ - + Exe @@ -12,9 +12,9 @@ - - - + + + diff --git a/docs/azure/snippets/bicep/AppHost.Bicep/AppHost.Bicep.csproj b/docs/azure/snippets/bicep/AppHost.Bicep/AppHost.Bicep.csproj index ec02676eee..a6f103822e 100644 --- a/docs/azure/snippets/bicep/AppHost.Bicep/AppHost.Bicep.csproj +++ b/docs/azure/snippets/bicep/AppHost.Bicep/AppHost.Bicep.csproj @@ -1,5 +1,5 @@ - + Exe net9.0 @@ -8,8 +8,8 @@ 5e45e8bd-353b-48d8-ac57-a89e5ee3f8ec - - + + diff --git a/docs/azureai/azureai-foundry-integration.md b/docs/azureai/azureai-foundry-integration.md new file mode 100644 index 0000000000..9b67c7cdde --- /dev/null +++ b/docs/azureai/azureai-foundry-integration.md @@ -0,0 +1,353 @@ +--- +title: ".NET Aspire Azure AI Foundry integration (Preview)" +description: "Learn how to integrate Azure AI Foundry with .NET Aspire applications, including hosting and client integration." +ms.date: 07/22/2025 +ai-usage: ai-assisted +titleSuffix: '' +--- + +# .NET Aspire Azure AI Foundry integration (Preview) + +[!INCLUDE [includes-hosting-and-client](../includes/includes-hosting-and-client.md)] + +[Azure AI Foundry](https://ai.azure.com/) is an AI platform that provides access to cutting-edge foundation models, tools for AI development, and scalable infrastructure for building intelligent applications. The .NET Aspire Azure AI Foundry integration enables you to connect to Azure AI Foundry or run models locally using Foundry Local from your .NET applications. + +## Hosting integration + +The .NET Aspire [Azure AI Foundry](/azure/ai-foundry/) hosting integration models Azure AI Foundry resources as `AzureAIFoundryResource`. To access these types and APIs for expressing them within your [AppHost](../fundamentals/app-host-overview.md) project, install the [📦 Aspire.Hosting.Azure.AIFoundry](https://www.nuget.org/packages/Aspire.Hosting.Azure.AIFoundry) NuGet package: + +### [.NET CLI](#tab/dotnet-cli) + +```dotnetcli +dotnet add package Aspire.Hosting.Azure.AIFoundry +``` + +### [PackageReference](#tab/package-reference) + +```xml + +``` + +--- + +For more information, see [dotnet add package](/dotnet/core/tools/dotnet-add-package) or [Manage package dependencies in .NET applications](/dotnet/core/tools/dependencies). + +### Add an Azure AI Foundry resource + +To add an `AzureAIFoundryResource` to your app host project, call the `AddAzureAIFoundry` method: + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +var foundry = builder.AddAzureAIFoundry("foundry"); + +builder.AddProject() + .WithReference(foundry); + +// After adding all resources, run the app... +``` + +The preceding code adds an Azure AI Foundry resource named `foundry` to the app host project. The method passes the connection information to the `ExampleProject` project. + + + +> [!IMPORTANT] +> When you call `AddAzureAIFoundry`, it implicitly calls —which adds support for generating Azure resources dynamically during app startup. The app must configure the appropriate subscription and location. For more information, see [Local provisioning: Configuration](/dotnet/aspire/azure/local-provisioning#configuration). + +### Add an Azure AI Foundry deployment resource + + + +To add an Azure AI Foundry deployment resource, call the `AddDeployment` method: + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +var foundry = builder.AddAzureAIFoundry("foundry"); + +var chat = foundry.AddDeployment("chat", "Phi-4", "1", "Microsoft"); + +builder.AddProject() + .WithReference(chat) + .WaitFor(chat); + +// After adding all resources, run the app... +``` + +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/how-to/deploy-models-open) 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. + +### Configure deployment properties + + + +You can customize deployment properties using the `WithProperties` method: + +```csharp +var chat = foundry.AddDeployment("chat", "Phi-4", "1", "Microsoft") + .WithProperties(deployment => + { + deployment.SkuName = "Standard"; + deployment.SkuCapacity = 10; + }); +``` + +The preceding code sets the SKU name to `Standard` and capacity to `10` for the deployment. + +### Provisioning-generated Bicep + +If you're new to [Bicep](/azure/azure-resource-manager/bicep/overview), it's a domain-specific language for defining Azure resources. With .NET Aspire, you don't need to write Bicep by-hand, instead the provisioning APIs generate Bicep for you. When you publish your app, the generated Bicep provisions an Azure AI Foundry resource with standard defaults. + +:::code language="bicep" source="../snippets/azure/AppHost/ai-foundry/ai-foundry.bicep"::: + +The preceding Bicep is a module that provisions an Azure Cognitive Services resource configured for AI Services. Additionally, role assignments are created for the Azure resource in a separate module: + +:::code language="bicep" source="../snippets/azure/AppHost/ai-foundry-roles/ai-foundry-roles.bicep"::: + +The generated Bicep is a starting point and is influenced by changes to the provisioning infrastructure in C#. Customizations to the Bicep file directly will be overwritten, so make changes through the C# provisioning APIs to ensure they're reflected in the generated files. + +### Customize provisioning infrastructure + +All .NET Aspire Azure resources are subclasses of the type. This enables customization of the generated Bicep by providing a fluent API to configure the Azure resources—using the API: + +:::code language="csharp" source="../snippets/azure/AppHost/Program.ConfigureAIFoundryInfra.cs" id="configure"::: + +The preceding code: + +- Chains a call to the API: + - The `infra` parameter is an instance of the type. + - The provisionable resources are retrieved by calling the method. + - The single resource is retrieved. + - The property is assigned to a new instance of with an `E0` name and tier. + - A tag is added to the Cognitive Services resource with a key of `ExampleKey` and a value of `Example value`. + +### Connect to an existing Azure AI Foundry service + +You might have an existing Azure AI Foundry service that you want to connect to. You can chain a call to annotate that your `AzureAIFoundryResource` is an existing resource: + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +var existingFoundryName = builder.AddParameter("existingFoundryName"); +var existingFoundryResourceGroup = builder.AddParameter("existingFoundryResourceGroup"); + +var foundry = builder.AddAzureAIFoundry("foundry") + .AsExisting(existingFoundryName, existingFoundryResourceGroup); + +builder.AddProject() + .WithReference(foundry); + +// After adding all resources, run the app... +``` + +[!INCLUDE [azure-configuration](../azure/includes/azure-configuration.md)] + +For more information on treating Azure AI Foundry resources as existing resources, see [Use existing Azure resources](/dotnet/aspire/azure/integrations-overview#use-existing-azure-resources). + +> [!NOTE] +> Alternatively, instead of representing an Azure AI Foundry resource, you can add a connection string to the app host. This approach is weakly typed, and doesn't work with role assignments or infrastructure customizations. For more information, see [Add existing Azure resources with connection strings](/dotnet/aspire/azure/integrations-overview#add-existing-azure-resources-with-connection-strings). + +### Use Foundry Local for development + +Aspire supports the usage of Foundry Local for local development. Add the following to your AppHost project: + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +var foundry = builder.AddAzureAIFoundry("foundry") + .RunAsFoundryLocal(); + +var chat = foundry.AddDeployment("chat", "phi-3.5-mini", "1", "Microsoft"); + +builder.AddProject() + .WithReference(chat) + .WaitFor(chat); + +// After adding all resources, run the app... +``` + +When the AppHost starts up, the local foundry service is also started. This requires the local machine to have [Foundry Local](/azure/ai-foundry/foundry-local/get-started) installed and running. + + + +The `RunAsFoundryLocal()` 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. + +### Assign roles to resources + + + +You can assign specific roles to resources that need to access the Azure AI Foundry service. Use the `WithRoleAssignments` method: + +```csharp +var foundry = builder.AddAzureAIFoundry("foundry"); + +builder.AddProject("api") + .WithRoleAssignments(foundry, CognitiveServicesBuiltInRole.CognitiveServicesUser) + .WithReference(foundry); +``` + +The preceding code assigns the `CognitiveServicesUser` role to the `api` project, granting it the necessary permissions to access the Azure AI Foundry resource. + +## Client integration + +To get started with the .NET Aspire Azure AI Foundry client integration, install the [📦 Aspire.Azure.AI.Inference](https://www.nuget.org/packages/Aspire.Azure.AI.Inference) NuGet package in the client-consuming project, that is, the project for the application that uses the Azure AI Foundry client. + +### [.NET CLI](#tab/dotnet-cli) + +```dotnetcli +dotnet add package Aspire.Azure.AI.Inference +``` + +### [PackageReference](#tab/package-reference) + +```xml + +``` + +--- + +### Add an Azure AI Foundry client + +In the Program.cs file of your client-consuming project, use the [AddAzureChatCompletionsClient(IHostApplicationBuilder, String)](/dotnet/api/microsoft.extensions.hosting.aspireazureaiinferenceextensions.addazurechatcompletionsclient) method to register a `ChatCompletionsClient` for dependency injection (DI). The method requires a connection name parameter. + +```csharp +builder.AddAzureChatCompletionsClient(connectionName: "chat"); +``` + +> [!TIP] +> The `connectionName` parameter must match the name used when adding the Azure AI Foundry deployment resource in the app host project. For more information, see Add an Azure AI Foundry deployment resource. + +After adding the `ChatCompletionsClient`, you can retrieve the client instance using dependency injection: + +```csharp +public class ExampleService(ChatCompletionsClient client) +{ + // Use client... +} +``` + +For more information, see: + +- [Azure.AI.Inference documentation](https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/ai/Azure.AI.Inference/README.md) for examples on using the `ChatCompletionsClient`. +- [Dependency injection in .NET](/dotnet/core/extensions/dependency-injection) for details on dependency injection. + +### Add Azure AI Foundry client with registered `IChatClient` + +If you're interested in using the [IChatClient](/dotnet/api/microsoft.extensions.ai.ichatclient) interface, with the Azure AI Foundry client, simply chain the [AddChatClient](/dotnet/api/microsoft.extensions.hosting.aspirechatcompletionsclientbuilderchatclientextensions.addchatclient) API to the `AddAzureChatCompletionsClient` method: + +```csharp +builder.AddAzureChatCompletionsClient(connectionName: "chat") + .AddChatClient(); +``` + +For more information on the `IChatClient` and its corresponding library, see [Artificial intelligence in .NET (Preview)](/dotnet/core/extensions/artificial-intelligence). + +### Alternative: Use OpenAI client for compatible models + +For models that are compatible with the OpenAI API, you can also use the [📦 Aspire.OpenAI](https://www.nuget.org/packages/Aspire.OpenAI) client integration: + +```csharp +builder.AddOpenAIClient("chat") + .AddChatClient(); +``` + +This approach works well with models that support the OpenAI API format. + +### Configuration + +The .NET Aspire Azure AI Foundry library provides multiple options to configure the Azure AI Foundry connection based on the requirements and conventions of your project. Either an `Endpoint` and `DeploymentId`, or a `ConnectionString` is required to be supplied. + +#### Use a connection string + +When using a connection string from the `ConnectionStrings` configuration section, you can provide the name of the connection string when calling `builder.AddAzureChatCompletionsClient`: + +```csharp +builder.AddAzureChatCompletionsClient("chat"); +``` + +The connection string is retrieved from the `ConnectionStrings` configuration section, and there are two supported formats: + +##### Azure AI Foundry Endpoint + +The recommended approach is to use an Endpoint, which works with the `ChatCompletionsClientSettings.Credential` property to establish a connection. If no credential is configured, the `DefaultAzureCredential` is used. + +```json +{ + "ConnectionStrings": { + "chat": "Endpoint=https://{endpoint}/;DeploymentId={deploymentName}" + } +} +``` + +##### Connection string + +Alternatively, a custom connection string can be used: + +```json +{ + "ConnectionStrings": { + "chat": "Endpoint=https://{endpoint}/;Key={account_key};DeploymentId={deploymentName}" + } +} +``` + +#### Use configuration providers + +The .NET Aspire Azure AI Inference library supports . It loads the `ChatCompletionsClientSettings` from configuration by using the `Aspire:Azure:AI:Inference` key. Example appsettings.json that configures some of the options: + +```json +{ + "Aspire": { + "Azure": { + "AI": { + "Inference": { + "DisableTracing": false, + "ClientOptions": { + "UserAgentApplicationId": "myapp" + } + } + } + } + } +} +``` + +For the complete Azure AI Inference client integration JSON schema, see [Aspire.Azure.AI.Inference/ConfigurationSchema.json](https://github.com/dotnet/aspire/blob/main/src/Components/Aspire.Azure.AI.Inference/ConfigurationSchema.json). + +#### Use inline delegates + +You can pass the `Action configureSettings` delegate to set up some or all the options inline, for example to disable tracing from code: + +```csharp +builder.AddAzureChatCompletionsClient( + "chat", + static settings => settings.DisableTracing = true); +``` + +[!INCLUDE [integration-observability-and-telemetry](../includes/integration-observability-and-telemetry.md)] + +#### Logging + +The .NET Aspire Azure AI Foundry integration uses the following log categories: + +- `Azure` +- `Azure.Core` +- `Azure.Identity` + +#### Tracing + +The .NET Aspire Azure AI Foundry integration emits tracing activities using OpenTelemetry for operations performed with the `ChatCompletionsClient`. + +## See also + +- [Azure AI Foundry](https://azure.microsoft.com/products/ai-services/ai-foundry/) +- [.NET Aspire integrations overview](/dotnet/aspire/fundamentals/integrations-overview) +- [.NET Aspire Azure integrations overview](/dotnet/aspire/azure/integrations-overview) +- [.NET Aspire GitHub repo](https://github.com/dotnet/aspire) diff --git a/docs/azureai/azureai-openai-integration.md b/docs/azureai/azureai-openai-integration.md index 71f29de9a0..1214e0340b 100644 --- a/docs/azureai/azureai-openai-integration.md +++ b/docs/azureai/azureai-openai-integration.md @@ -1,7 +1,7 @@ --- title: .NET Aspire Azure OpenAI integration (Preview) description: Learn how to use the .NET Aspire Azure OpenAI integration. -ms.date: 04/15/2025 +ms.date: 07/22/2025 --- # .NET Aspire Azure OpenAI integration (Preview) @@ -81,11 +81,11 @@ The preceding code: If you're new to [Bicep](/azure/azure-resource-manager/bicep/overview), it's a domain-specific language for defining Azure resources. With .NET Aspire, you don't need to write Bicep by-hand, instead the provisioning APIs generate Bicep for you. When you publish your app, the generated Bicep provisions an Azure OpenAI resource with standard defaults. -:::code language="bicep" source="../snippets/azure/AppHost/openai.module.bicep"::: +:::code language="bicep" source="../snippets/azure/AppHost/openai/openai.bicep"::: The preceding Bicep is a module that provisions an Azure Cognitive Services resource. Additionally, role assignments are created for the Azure resource in a separate module: -:::code language="bicep" source="../snippets/azure/AppHost/openai-roles.module.bicep"::: +:::code language="bicep" source="../snippets/azure/AppHost/openai-roles/openai-roles.bicep"::: The generated Bicep is a starting point and is influenced by changes to the provisioning infrastructure in C#. Customizations to the Bicep file directly will be overwritten, so make changes through the C# provisioning APIs to ensure they are reflected in the generated files. diff --git a/docs/azureai/azureai-search-document-integration.md b/docs/azureai/azureai-search-document-integration.md index e1c0849f4b..4894aaf558 100644 --- a/docs/azureai/azureai-search-document-integration.md +++ b/docs/azureai/azureai-search-document-integration.md @@ -1,7 +1,7 @@ --- title: .NET Aspire Azure AI Search integration description: Learn how to integrate Azure AI Search with .NET Aspire. -ms.date: 04/03/2025 +ms.date: 07/22/2025 --- # .NET Aspire Azure AI Search integration @@ -55,11 +55,11 @@ The preceding code adds an Azure AI Search resource named `search` to the app ho If you're new to [Bicep](/azure/azure-resource-manager/bicep/overview), it's a domain-specific language for defining Azure resources. With .NET Aspire, you don't need to write Bicep by hand; instead, the provisioning APIs generate Bicep for you. When you publish your app, the generated Bicep is output alongside the manifest file. When you add an Azure AI Search resource, Bicep is generated to provision the search service with appropriate defaults. -:::code language="bicep" source="../snippets/azure/AppHost/search.module.bicep"::: +:::code language="bicep" source="../snippets/azure/AppHost/search/search.bicep"::: The preceding Bicep is a module that provisions an Azure AI Search service resource. Additionally, role assignments are created for the Azure resource in a separate module: -:::code language="bicep" source="../snippets/azure/AppHost/search-roles.module.bicep"::: +:::code language="bicep" source="../snippets/azure/AppHost/search-roles/search-roles.bicep"::: The generated Bicep is a starting point and is influenced by changes to the provisioning infrastructure in C#. Customizations to the Bicep file directly will be overwritten, so make changes through the C# provisioning APIs to ensure they are reflected in the generated files. diff --git a/docs/caching/includes/azure-redis-app-host.md b/docs/caching/includes/azure-redis-app-host.md index 394449d8ee..cb6570a760 100644 --- a/docs/caching/includes/azure-redis-app-host.md +++ b/docs/caching/includes/azure-redis-app-host.md @@ -48,11 +48,11 @@ The preceding call to `AddAzureRedis` configures the Redis server resource to be If you're new to [Bicep](/azure/azure-resource-manager/bicep/overview), it's a domain-specific language for defining Azure resources. With .NET Aspire, you don't need to write Bicep by-hand, instead the provisioning APIs generate Bicep for you. When you publish your app, the generated Bicep is output alongside the manifest file. When you add an Azure Cache for Redis resource, the following Bicep is generated: -:::code language="bicep" source="../../snippets/azure/AppHost/redis.module.bicep"::: +:::code language="bicep" source="../../snippets/azure/AppHost/redis/redis.bicep"::: The preceding Bicep is a module that provisions an Azure Cache for Redis resource. Additionally, role assignments are created for the Azure resource in a separate module: -:::code language="bicep" source="../../snippets/azure/AppHost/redis-roles.module.bicep"::: +:::code language="bicep" source="../../snippets/azure/AppHost/redis-roles/redis-roles.bicep"::: In addition to the Azure Cache for Redis, it also provisions an access policy assignment to the application access to the cache. The generated Bicep is a starting point and is influenced by changes to the provisioning infrastructure in C#. Customizations to the Bicep file directly will be overwritten, so make changes through the C# provisioning APIs to ensure they are reflected in the generated files. diff --git a/docs/cli-reference/aspire-add.md b/docs/cli-reference/aspire-add.md new file mode 100644 index 0000000000..b382c486d3 --- /dev/null +++ b/docs/cli-reference/aspire-add.md @@ -0,0 +1,68 @@ +--- +title: aspire add command +description: Learn about the aspire add command and its usage. This command adds an integration package to an Aspire AppHost project. +ms.date: 07/11/2025 +--- +# aspire add command + +**This article applies to:** ✔️ Aspire CLI 9.4.0 and later versions + +## Name + +`aspire add` - Add an integration to the Aspire project. + +## Synopsis + +```Command +aspire add [] [options] +``` + +## Description + +The `aspire add` command searches for an integration package and adds it to the Aspire AppHost. + +[!INCLUDE [project-search-logic-description](includes/project-search-logic-description.md)] + +## Arguments + +The following arguments are available: + +- **`integration`** + + The name of the integration to add (for example: redis, postgres). + + If a partial name or invalid name is provided, the CLI searches NuGet for approximate matches and prints them in the terminal for the user to select. If no results are found, all packages are listed. + +## Options + +The following options are available: + +- [!INCLUDE [option-project](includes/option-project.md)] + +- **`-v, --version`** + + The version of the integration to add. + +- **`-s, --source`** + + The NuGet source to use for the integration. + +- [!INCLUDE [option-help](includes/option-help.md)] + +- [!INCLUDE [option-debug](includes/option-debug.md)] + +- [!INCLUDE [option-wait](includes/option-wait.md)] + +## Examples + +- Finds an AppHost project and lists all Aspire integration packages from NuGet: + + ```Command + aspire add + ``` + +- Finds an AppHost project and adds the **kafka** (Aspire.Hosting.Kafka) integration package: + + ```Command + aspire add kafka --version 9.3.2 + ``` diff --git a/docs/cli-reference/aspire-config-delete.md b/docs/cli-reference/aspire-config-delete.md new file mode 100644 index 0000000000..d2afc91ce6 --- /dev/null +++ b/docs/cli-reference/aspire-config-delete.md @@ -0,0 +1,51 @@ +--- +title: aspire config delete command +description: Learn about the aspire config delete command and its usage. This command deletes an Aspire CLI config value by key name. +ms.date: 07/11/2025 +--- +# aspire config delete command + +**This article applies to:** ✔️ Aspire CLI 9.4.0 and later versions + +## Name + +`aspire config delete` - Delete a configuration value. + +## Synopsis + +```Command +aspire config delete [options] +``` + +## Description + +The `aspire config delete` command deletes a config value by key name. + +[!INCLUDE [config-file-description](includes/config-file-description.md)] + +The command returns the following exit codes: + +- `0`—The command succeeded. +- `1`—The supplied key doesn't exist in the config file, or the config file is missing. + +## Arguments + +The following arguments are available: + +- **`key`** + + The configuration key to delete. + +## Options + +The following options are available: + +- **`-g, --global`** + + Delete the configuration value from the global `$HOME/.aspire/settings.json` instead of the local settings file. + +- [!INCLUDE [option-help](includes/option-help.md)] + +- [!INCLUDE [option-debug](includes/option-debug.md)] + +- [!INCLUDE [option-wait](includes/option-wait.md)] diff --git a/docs/cli-reference/aspire-config-get.md b/docs/cli-reference/aspire-config-get.md new file mode 100644 index 0000000000..40326a5f03 --- /dev/null +++ b/docs/cli-reference/aspire-config-get.md @@ -0,0 +1,49 @@ +--- +title: aspire config get command +description: Learn about the aspire config get command and its usage. This command gets an Aspire CLI config value by key name. +ms.date: 07/11/2025 +--- +# aspire config get command + +**This article applies to:** ✔️ Aspire CLI 9.4.0 and later versions + +## Name + +`aspire config get` - Get a configuration value. + +## Synopsis + +```Command +aspire config get [options] +``` + +## Description + +The `aspire config get` command retrieves a config value by key name. + +[!INCLUDE [config-file-description](includes/config-file-description.md)] + +If the config value doesn't exist in a local settings file, the config file is retrieved from the global settings file. + +The command returns the following exit codes: + +- `0`—The command succeeded. +- `10`—The supplied key doesn't exist in the config file, or the config file is missing. + +## Arguments + +The following arguments are available: + +- **`key`** + + The configuration key to retrieve. + +## Options + +The following options are available: + +- [!INCLUDE [option-help](includes/option-help.md)] + +- [!INCLUDE [option-debug](includes/option-debug.md)] + +- [!INCLUDE [option-wait](includes/option-wait.md)] diff --git a/docs/cli-reference/aspire-config-list.md b/docs/cli-reference/aspire-config-list.md new file mode 100644 index 0000000000..d2b3a1b75e --- /dev/null +++ b/docs/cli-reference/aspire-config-list.md @@ -0,0 +1,36 @@ +--- +title: aspire config list command +description: Learn about the aspire config list command and its usage. This command lists all Aspire CLI config values. +ms.date: 07/11/2025 +--- +# aspire config list command + +**This article applies to:** ✔️ Aspire CLI 9.4.0 and later versions + +## Name + +`aspire config list` - List all configuration values. + +## Synopsis + +```Command +aspire config list [options] +``` + +## Description + +The `aspire config list` command lists all config value by key name. + +[!INCLUDE [config-file-description](includes/config-file-description.md)] + +This command lists both the global and local settings. If a local setting overrides a global setting, only the local setting is displayed. + +## Options + +The following options are available: + +- [!INCLUDE [option-help](includes/option-help.md)] + +- [!INCLUDE [option-debug](includes/option-debug.md)] + +- [!INCLUDE [option-wait](includes/option-wait.md)] diff --git a/docs/cli-reference/aspire-config-set.md b/docs/cli-reference/aspire-config-set.md new file mode 100644 index 0000000000..4b53ed3685 --- /dev/null +++ b/docs/cli-reference/aspire-config-set.md @@ -0,0 +1,56 @@ +--- +title: aspire config set command +description: Learn about the aspire config set command and its usage. This command sets an Aspire CLI config value by key name. +ms.date: 07/11/2025 +--- +# aspire config set command + +**This article applies to:** ✔️ Aspire CLI 9.4.0 and later versions + +## Name + +`aspire config set` - Set a configuration value. + +## Synopsis + +```Command +aspire config set [options] +``` + +## Description + +The `aspire config set` command sets a config value by key name. + +[!INCLUDE [config-file-description](includes/config-file-description.md)] + +## Arguments + +The following arguments are available: + +- **`key`** + + The configuration key to set. + +- **`value`** + + The configuration value to set. + +## Options + +The following options are available: + +- **`-g, --global`** + + Set the configuration value globally in `$HOME/.aspire/settings.json` instead of the local settings file. + +- [!INCLUDE [option-help](includes/option-help.md)] + +- [!INCLUDE [option-debug](includes/option-debug.md)] + +- [!INCLUDE [option-wait](includes/option-wait.md)] + +## Settings + +The following config settings are available: + +[!INCLUDE [config-settings-table](includes/config-settings-table.md)] diff --git a/docs/cli-reference/aspire-config.md b/docs/cli-reference/aspire-config.md new file mode 100644 index 0000000000..4bc73d729d --- /dev/null +++ b/docs/cli-reference/aspire-config.md @@ -0,0 +1,51 @@ +--- +title: aspire config command +description: Learn about the aspire config command and its usage. This command driver is used to manage the Aspire CLI config settings. +ms.date: 07/24/2025 +--- +# aspire config command + +**This article applies to:** ✔️ Aspire CLI 9.4.0 and later versions + +## Name + +`aspire config` - Manage configuration settings. + +## Synopsis + +```Command +aspire config [command] [options] +``` + +## Description + +The `aspire config` command manages the Aspire CLI config settings. + +[!INCLUDE [config-file-description](includes/config-file-description.md)] + +## Options + +The following options are available: + +- [!INCLUDE [option-help](includes/option-help.md)] + +- [!INCLUDE [option-debug](includes/option-debug.md)] + +- [!INCLUDE [option-wait](includes/option-wait.md)] + +## Commands + +The following commands are available: + +| Command | Status | Function | +|-----------------------------------------------------------|--------|--------------------------------| +| [`aspire config list`](aspire-config-list.md) | Stable | List all configuration values. | +| [`aspire config get `](aspire-config-get.md) | Stable | Get a configuration value. | +| [`aspire config set `](aspire-config-set.md) | Stable | Set a configuration value. | +| [`aspire config delete `](aspire-config-delete.md) | Stable | Delete a configuration value. | + +## Settings + +The following config settings are available: + +[!INCLUDE [config-settings-table](includes/config-settings-table.md)] diff --git a/docs/cli-reference/aspire-deploy.md b/docs/cli-reference/aspire-deploy.md new file mode 100644 index 0000000000..361807fde6 --- /dev/null +++ b/docs/cli-reference/aspire-deploy.md @@ -0,0 +1,84 @@ +--- +title: aspire deploy command +description: Learn about the aspire deploy command and its usage. This command first runs publishing mode, then invokes resource deployments declared by the app host. +ms.date: 07/24/2025 +--- +# aspire deploy command (Preview) + +**This article applies to:** ✔️ Aspire CLI 9.4.0 and later versions. + +> [!NOTE] +> This command is disabled by default. To use it, turn on the feature toggle by running: +> +> ```Aspire +> aspire config set features.deployCommandEnabled true +> ``` +> +> For more information, see [aspire config command](aspire-config.md). + +## Name + +`aspire deploy` - Deploy a codebase orchestrated with Aspire to specified targets. + +[!INCLUDE [mode-preview](includes/mode-preview.md)] + +## Synopsis + +```Command +aspire deploy [options] [[--] ...] +``` + +## Description + +The `aspire deploy` command first invokes the [`aspire publish`](./aspire-publish.md) command. After which, Aspire invokes all `DeployingCallbackAnnotation` resource annotations, in the order they're declared. + +[!INCLUDE [project-search-logic-description](includes/project-search-logic-description.md)] + +The command performs the following steps to deploy an app orchestrated with Aspire: + +- Creates or modifies the `.aspire/settings.json` config file in the current directory, and sets the `appHostPath` config value to the path of the AppHost project file. +- Installs or verifies that Aspire's local hosting certificates are installed and trusted. +- Builds the AppHost project and its resources. +- Starts the AppHost and its resources. +- Invokes all resource annotations. +- Invokes all `DeployingCallbackAnnotation` resource annotations. + +## Options + +The following options are available: + +- **`--`** + + Delimits arguments to `aspire publish` from arguments for the app host. All arguments after this delimiter are passed to the apphost. + +- [!INCLUDE [option-project](includes/option-project.md)] + +- **`-o, --output-path`** + + The output path for deployment artifacts. Defaults to a folder named _deploy_ in the current directory. + +- [!INCLUDE [option-help](includes/option-help.md)] + +- [!INCLUDE [option-debug](includes/option-debug.md)] + +- [!INCLUDE [option-wait](includes/option-wait.md)] + +## Examples + +- Search the current directory structure for AppHost projects to build, publish, and deploy: + + ```Command + aspire deploy + ``` + +- Publish and deploy an Aspire apphost and its dependencies: + + ```Command + aspire deploy --project './projects/apphost/orchestration.AppHost.csproj' + ``` + +- Publish and deploy an Aspire AppHost with arguments: + + ```Command + aspire deploy --project './projects/apphost/orchestration.AppHost.csproj' -- -fast + ``` diff --git a/docs/cli-reference/aspire-exec.md b/docs/cli-reference/aspire-exec.md new file mode 100644 index 0000000000..60f5cd2df1 --- /dev/null +++ b/docs/cli-reference/aspire-exec.md @@ -0,0 +1,69 @@ +--- +title: aspire exec command +description: Learn about the aspire exec command and its usage. This command builds and runs an Aspire AppHost project, then sends commands to a resource. +ms.date: 07/25/2025 +--- +# aspire exec command + +**This article applies to:** ✔️ Aspire CLI 9.4.0 and later versions. + +> [!NOTE] +> This command is disabled by default. To use it, turn on the feature toggle by running: +> +> ```Aspire +> aspire config set features.execCommandEnabled true +> ``` +> +> For more information, see [aspire config command](aspire-config.md). + +## Name + +`aspire exec` - Run an Aspire AppHost to execute a command against the resource. + +[!INCLUDE [mode-preview](includes/mode-preview.md)] + +## Synopsis + +```Command +aspire exec [options] [[--] ...] +``` + +## Description + +The `aspire exec` command runs a command in the context of one of the resources defined in the AppHost. + +You must specify either the `--resource` or the `--start-resource` option, and you must provide parameters with the `--` option. + +[!INCLUDE [project-search-logic-description](includes/project-search-logic-description.md)] + +## Options + +The following options are available: + +- **`--`** + + Delimits arguments to `aspire exec` from arguments for the resource. All arguments after this delimiter are passed to the resource. + +- [!INCLUDE [option-project](includes/option-project.md)] + +- **`-r, --resource`** + + The name of the target resource to execute the command against. + +- **`-s, --start-resource`** + + The name of the target resource to start and execute the command against. + +- [!INCLUDE [option-help](includes/option-help.md)] + +- [!INCLUDE [option-debug](includes/option-debug.md)] + +- [!INCLUDE [option-wait](includes/option-wait.md)] + +## Examples + +- Builds and runs the AppHost project, then sends the command `migrate` to the `database1` resource: + + ```Command + aspire exec --resource database1 -- migrate + ``` diff --git a/docs/cli-reference/aspire-new.md b/docs/cli-reference/aspire-new.md new file mode 100644 index 0000000000..7d793a6dc6 --- /dev/null +++ b/docs/cli-reference/aspire-new.md @@ -0,0 +1,78 @@ +--- +title: aspire new command +description: Learn about the aspire new command and its usage. This command creates new Aspire projects or solutions. +ms.date: 07/11/2025 +--- +# aspire new command + +**This article applies to:** ✔️ Aspire CLI 9.4.0 and later versions. + +## Name + +`aspire new` - Create a new Aspire project or solution. + +## Synopsis + +```Command +aspire new [command] [options] +``` + +## Description + +The `aspire new` command is the driver for creating Aspire projects, apps, or solutions, based on the Aspire templates. Each command specifies the template to use, and the options for the driver specify the options for the template. + +This command defaults to **interactive** mode. When executed without any options, the command prompts you for the project template and version, name, and output folder. When the `--name`, `--output`, and `--version` options are provided, the command runs **non-interactive** and generates files based on the command template. + +## Options + +The following options are available: + +- **`-n, --name`** + + The name of the project to create. + +- **`-o, --output`** + + The output path for the project. + +- **`-s, --source`** + + The NuGet source to use for the project templates. + +- **`-v, --version`** + + The version of the project templates to use. + +- [!INCLUDE [option-help](includes/option-help.md)] + +- [!INCLUDE [option-debug](includes/option-debug.md)] + +- [!INCLUDE [option-wait](includes/option-wait.md)] + +## Commands + +Each command represents a template. Pass the `--help` parameter to the template command to print the options available to the template. + +| Command | Template | +|--------------------------|------------------------------| +| `aspire` | Aspire Empty App | +| `aspire-apphost` | Aspire App Host | +| `aspire-mstest` | Aspire Test Project (MSTest) | +| `aspire-nunit` | Aspire Test Project (NUnit) | +| `aspire-servicedefaults` | Aspire Service Defaults | +| `aspire-starter` | Aspire Starter App | +| `aspire-xunit` | Aspire Test Project (xUnit) | + +## Examples + +- Create an Aspire solution from the template. Because the template was selected (`aspire-starter`), you're prompted for the name, output folder, and template version. + + ```Command + aspire new aspire-starter + ``` + +- Create an AppHost project named `aspireapp` from the **9.4.0** templates and place the output in a folder named `aspire1`. + + ```Command + aspire new aspire-apphost --version 9.4.0 --name aspireapp1 --output ./aspire1 + ``` diff --git a/docs/cli-reference/aspire-publish.md b/docs/cli-reference/aspire-publish.md new file mode 100644 index 0000000000..a0b4a60b99 --- /dev/null +++ b/docs/cli-reference/aspire-publish.md @@ -0,0 +1,74 @@ +--- +title: aspire publish command +description: Learn about the aspire publish command and its usage. This command invokes resource publishers declared by the apphost to serialize resources to disk. +ms.date: 07/11/2025 +--- +# aspire publish command + +**This article applies to:** ✔️ Aspire CLI 9.4.0 and later versions. + +## Name + +`aspire publish` - Generates deployment artifacts for an Aspire app host project. + +[!INCLUDE [mode-preview](includes/mode-preview.md)] + +## Synopsis + +```Command +aspire publish [options] [[--] ...] +``` + +## Description + +The `aspire publish` command publishes resources by serializing them to disk. When this command is run, Aspire invokes registered annotations for resources, in the order they're declared. These annotations serialize a resource so that it can be consumed by deployment tools. + +[!INCLUDE [project-search-logic-description](includes/project-search-logic-description.md)] + +The command performs the following steps to run an Aspire AppHost: + +- Creates or modifies the `.aspire/settings.json` config file in the current directory, and sets the `appHostPath` config value to the path of the AppHost project file. +- Installs or verifies that Aspire's local hosting certificates are installed and trusted. +- Builds the AppHost project and its resources. +- Starts the AppHost and its resources. +- Invokes all annotations for resources. + +## Options + +The following options are available: + +- **`--`** + + Delimits arguments to `aspire publish` from arguments for the AppHost. All arguments after this delimiter are passed to the AppHost. + +- [!INCLUDE [option-project](includes/option-project.md)] + +- **`-o, --output-path`** + + The output path for the generated artifacts. Defaults the current directory. + +- [!INCLUDE [option-help](includes/option-help.md)] + +- [!INCLUDE [option-debug](includes/option-debug.md)] + +- [!INCLUDE [option-wait](includes/option-wait.md)] + +## Examples + +- Search the current directory structure for AppHost projects to build and publish: + + ```Command + aspire publish + ``` + +- Publish a specific AppHost project: + + ```Command + aspire publish --project './projects/apphost/orchestration.AppHost.csproj' + ``` + +- Publish a specific AppHost project with arguments: + + ```Command + aspire publish --project './projects/apphost/orchestration.AppHost.csproj' -- -fast + ``` diff --git a/docs/cli-reference/aspire-run.md b/docs/cli-reference/aspire-run.md new file mode 100644 index 0000000000..122db63fb0 --- /dev/null +++ b/docs/cli-reference/aspire-run.md @@ -0,0 +1,80 @@ +--- +title: aspire run command +description: Learn about the aspire run command and its usage. This command runs an Aspire app host. +ms.date: 07/11/2025 +--- +# aspire run command + +**This article applies to:** ✔️ Aspire CLI 9.4.0 and later versions. + +## Name + +`aspire run` - Run an Aspire AppHost in development mode. + +## Synopsis + +```Command +aspire run [options] [[--] ...] +``` + +## Description + +The `aspire run` command runs the AppHost project in development mode, which configures the Aspire environment, builds and starts resources defined by the app host, launches the web dashboard, and prints a list of endpoints. + +[!INCLUDE [project-search-logic-description](includes/project-search-logic-description.md)] + +The command performs the following steps to run an Aspire AppHost: + +- Creates or modifies the `.aspire/settings.json` config file in the current directory, and sets the `appHostPath` config value to the path of the AppHost project file. +- Installs or verifies that Aspire's local hosting certificates are installed and trusted. +- Builds the AppHost project and its resources. +- Starts the AppHost and its resources. +- Starts the dashboard. + +The following snippet is an example of the output displayed by the `aspire run` command: + +```Aspire CLI +Dashboard: https://localhost:17178/login?t=17f974bf68e390b0d4548af8d7e38b65 + + Logs: /home/vscode/.aspire/cli/logs/apphost-1295-2025-07-14-18-16-13.log +``` + +## Options + +The following options are available: + +- **`--`** + + Delimits arguments to `aspire run` from arguments for the app host being run. All arguments after this delimiter are passed to the app host run. + +- [!INCLUDE [option-project](includes/option-project.md)] + +- **`-w, --watch`** + + Start project resources in watch mode. + +- [!INCLUDE [option-help](includes/option-help.md)] + +- [!INCLUDE [option-debug](includes/option-debug.md)] + +- [!INCLUDE [option-wait](includes/option-wait.md)] + +## Examples + +- Search the current directory structure for AppHost projects to build and run: + + ```Command + aspire run + ``` + +- Run a specific AppHost project: + + ```Command + aspire run --project './projects/apphost/orchestration.AppHost.csproj' + ``` + +- Run a specific AppHost project with arguments: + + ```Command + aspire run --project './projects/apphost/orchestration.AppHost.csproj' -- -fast + ``` diff --git a/docs/cli-reference/aspire.md b/docs/cli-reference/aspire.md new file mode 100644 index 0000000000..8ba31e7476 --- /dev/null +++ b/docs/cli-reference/aspire.md @@ -0,0 +1,70 @@ +--- +title: aspire command +description: Learn about the aspire command (the generic driver for the Aspire CLI) and its usage. +ms.date: 07/11/2025 +--- +# aspire command + +**This article applies to:** ✔️ Aspire CLI 9.4.0 and later versions. + +## Name + +`aspire` - The generic driver for the Aspire CLI. + +## Synopsis + +To get information about the available commands and the environment: + +```Command +aspire [command] [options] +``` + +## Description + +The `aspire` command provides commands for working with Aspire projects. For example, `aspire run` runs your Aspire AppHost. + +## Options + +The following options are available when `aspire` is used by itself, without specifying a command. For example, `aspire --version`. + +- [!INCLUDE [option-help](includes/option-help.md)] + +- [!INCLUDE [option-version](includes/option-version.md)] + +- [!INCLUDE [option-debug](includes/option-debug.md)] + +- [!INCLUDE [option-wait](includes/option-wait.md)] + +## Commands + +The following commands are available: + +| Command | Status | Function | +|--------------------------------------------------|---------|--------------------------------------------------------------------------| +| [`aspire add`](aspire-add.md) | Stable | Add an integration to the Aspire project. | +| [`aspire config`](aspire-config.md) | Stable | Configures the Aspire environment. | +| [`aspire deploy`](aspire-deploy.md) | Preview | Deploys the artifacts created by `aspire publish`. | +| [`aspire exec`](aspire-exec.md) | Preview | Similar to the `aspire run` command, but passes commands to the apphost. | +| [`aspire new`](aspire-new.md) | Stable | Create an Aspire sample project from a template. | +| [`aspire publish`](aspire-publish.md) | Preview | Generates deployment artifacts for an Aspire apphost project. | +| [`aspire run`](aspire-run.md) | Stable | Run an Aspire apphost for local development. | + + + +## Examples + +- Create an Aspire solution from the template: + + ```Command + aspire new aspire-starter + ``` + +- Run an Aspire AppHost: + + ```Command + aspire run + ``` diff --git a/docs/cli-reference/includes/config-file-description.md b/docs/cli-reference/includes/config-file-description.md new file mode 100644 index 0000000000..6c140adc2f --- /dev/null +++ b/docs/cli-reference/includes/config-file-description.md @@ -0,0 +1,14 @@ +--- +ms.date: 07/11/2025 +ms.topic: include +--- + +Aspire supports a local and global settings file. Settings defined in a local settings file override those set in the global settings file. + +- **Local settings** + + A local settings file is stored at `.aspire/settings.json` under the current directory. + +- **Global settings** + + The global settings file is stored at `$HOME/.aspire/settings.json`. diff --git a/docs/cli-reference/includes/config-settings-table.md b/docs/cli-reference/includes/config-settings-table.md new file mode 100644 index 0000000000..1a942f590c --- /dev/null +++ b/docs/cli-reference/includes/config-settings-table.md @@ -0,0 +1,17 @@ +--- +ms.date: 07/25/2025 +ms.topic: include +--- + +| Setting | Description | +| ------------- | ----------- | +| `appHostPath` | The path to the AppHost project that the Aspire CLI uses by default. This setting is set by the Aspire CLI when you select a project or when Aspire is first run and detects an AppHost project. | +| `features.deployCommandEnabled` | Enables or disables the `deploy` command in Aspire CLI. Set to `true` to allow deployment features, or `false` to disable them. | +| `features.execCommandEnabled` | Enables or disables the `exec` command in Aspire CLI. Set to `true` to allow execution features, or `false` to disable them. | + + + + diff --git a/docs/cli-reference/includes/mode-preview.md b/docs/cli-reference/includes/mode-preview.md new file mode 100644 index 0000000000..d220c730c7 --- /dev/null +++ b/docs/cli-reference/includes/mode-preview.md @@ -0,0 +1,7 @@ +--- +ms.date: 07/11/2025 +ms.topic: include +--- + +> [!IMPORTANT] +> This command is in preview and subject to change. diff --git a/docs/cli-reference/includes/option-debug.md b/docs/cli-reference/includes/option-debug.md new file mode 100644 index 0000000000..da55b77ef0 --- /dev/null +++ b/docs/cli-reference/includes/option-debug.md @@ -0,0 +1,7 @@ +--- +ms.date: 07/11/2025 +ms.topic: include +--- +**`-d, --debug`** + + Enable debug logging to the console, which prints detailed information about what .NET Aspire CLI is doing when a command is run. diff --git a/docs/cli-reference/includes/option-help.md b/docs/cli-reference/includes/option-help.md new file mode 100644 index 0000000000..43cb668cda --- /dev/null +++ b/docs/cli-reference/includes/option-help.md @@ -0,0 +1,7 @@ +--- +ms.date: 07/11/2025 +ms.topic: include +--- +**`-?, -h, --help`** + + Prints help and usage documentation for the available commands and options. diff --git a/docs/cli-reference/includes/option-project.md b/docs/cli-reference/includes/option-project.md new file mode 100644 index 0000000000..a078225fea --- /dev/null +++ b/docs/cli-reference/includes/option-project.md @@ -0,0 +1,7 @@ +--- +ms.date: 07/11/2025 +ms.topic: include +--- +**`--project`** + + The path to the Aspire AppHost project file. diff --git a/docs/cli-reference/includes/option-version.md b/docs/cli-reference/includes/option-version.md new file mode 100644 index 0000000000..6db6a6547f --- /dev/null +++ b/docs/cli-reference/includes/option-version.md @@ -0,0 +1,7 @@ +--- +ms.date: 07/11/2025 +ms.topic: include +--- +**`--version`** + + Prints the version of the Aspire CLI tool. diff --git a/docs/cli-reference/includes/option-wait.md b/docs/cli-reference/includes/option-wait.md new file mode 100644 index 0000000000..2e8097aaf5 --- /dev/null +++ b/docs/cli-reference/includes/option-wait.md @@ -0,0 +1,7 @@ +--- +ms.date: 07/11/2025 +ms.topic: include +--- +**`--wait-for-debugger`** + + Wait for a debugger to attach before running a command. diff --git a/docs/cli-reference/includes/project-search-logic-description.md b/docs/cli-reference/includes/project-search-logic-description.md new file mode 100644 index 0000000000..b59c75129f --- /dev/null +++ b/docs/cli-reference/includes/project-search-logic-description.md @@ -0,0 +1,20 @@ +--- +ms.date: 07/11/2025 +ms.topic: include +--- + +The Aspire CLI uses the following logic, in order, to determine which AppHost project to process: + +- The `--project` option. + + This option specifies the path to a project to process. + +- The `.aspire/settings.json` config file. + + If the config file path exists in the current directory, it's used. If not, the CLI walks up the directory structure looking for the config file. If it finds a config file, it reads the `appHostPath` setting value as the project to process. + +- Searches the current directory and subdirectories. + + Starting in the current directory, the CLI gathers all AppHost projects from that directory and below. If a single project is discovered, it's automatically selected. If multiple projects are discovered, they're printed to the terminal for the user to manually select one of the projects. + + Once a project is selected, either automatically or manually, the path to the project is stored in the `.aspire/settings.json` config file. diff --git a/docs/cli/install.md b/docs/cli/install.md new file mode 100644 index 0000000000..e8ca9d2f9e --- /dev/null +++ b/docs/cli/install.md @@ -0,0 +1,50 @@ +--- +title: Install .NET Aspire CLI +description: Learn how to install .NET Aspire CLI, which is a .NET global tool. Use the .NET Aspire CLI to create, run, and manage .NET Aspire projects. +author: adegeo +ms.author: adegeo +ms.topic: install-set-up-deploy +ms.date: 07/24/2025 + +#customer intent: As a developer, I want to install the .NET Aspire CLI so that I can create, run, and manage .NET Aspire projects. + +--- + +# Install .NET Aspire CLI + +This article teaches you how to install the Aspire CLI, which is a CLI tool used to manage your .NET Aspire projects. + +## Prerequisites + +- [.NET SDK 9.0](https://dotnet.microsoft.com/download/dotnet/9.0). + +## Install as a native executable + +The compiled version of the Aspire CLI can be installed using the Aspire CLI installation script. + +[!INCLUDE [install-aspire-cli](../includes/install-aspire-cli.md)] + +## Install as a .NET global tool + +Use the `dotnet tool` command to install the Aspire CLI global tool. The name of the global tool is [Aspire.Cli](https://www.nuget.org/packages/Aspire.CLI). + +01. Open a terminal. +01. Run the following command to install Aspire CLI: + + ```dotnetcli + dotnet tool install -g Aspire.Cli --prerelease + ``` + +## Validation + +To validate that the global tool is installed, use the `--version` option to query Aspire CLI for a version number: + +```Aspire +aspire --version +``` + +If that command works, you're presented with the version of the Aspire CLI tool: + +```Aspire +9.4.0 +``` diff --git a/docs/cli/overview.md b/docs/cli/overview.md new file mode 100644 index 0000000000..2af46c32df --- /dev/null +++ b/docs/cli/overview.md @@ -0,0 +1,92 @@ +--- +title: .NET Aspire CLI Overview and Commands +description: Learn about the Aspire CLI commands for creating projects, running an app host, and adding integrations. Get started with command-line tools to build and manage distributed applications efficiently. +ms.date: 06/26/2025 +ms.topic: overview +ms.custom: + - ai-gen-docs-bap + - ai-gen-title + - ai-seo-date:06/26/2025 + - ai-gen-description +--- + +# Aspire CLI Overview + +The Aspire CLI (`aspire` command) is a cross-platform tool that provides command-line functionality to create, manage, run, and publish polyglot Aspire projects. Use the Aspire CLI to streamline development workflows and coordinate services for distributed applications. + +The Aspire CLI is an interactive-first experience. + +- [Install the Aspire CLI.](install.md) +- [`aspire` command reference.](../cli-reference/aspire.md) + +## Use templates + +[_Command reference: `aspire new`_](../cli-reference/aspire-new.md) + +The `aspire new` command is an interactive-first CLI experience, and is used to create one or more Aspire projects. As part of creating a project, Azure CLI ensures that the latest Aspire project templates are installed into the `dotnet` system. + +Use the `aspire new` command to create an Aspire project from a list of templates. Once a template is selected, the name of the project is set, and the output folder is chosen, `aspire` downloads the latest templates and generates one or more projects. + +While command line parameters can be used to automate the creation of an Aspire project, the Aspire CLI is an interactive-first experience. + +## Start the Aspire AppHost + +[_Command reference: `aspire run`_](../cli-reference/aspire-run.md) + +The `aspire run` command runs the AppHost project in development mode, which configures the Aspire environment, builds and starts resources defined by the app host, launches the web dashboard, and prints a list of endpoints. + +When `aspire run` starts, it searches the current directory for an AppHost project. If a project isn't found, the sub directories are searched until one is found. If no AppHost project is found, Aspire stops. Once a project is found, Aspire CLI takes the following steps: + +- Installs or verifies that Aspire's local hosting certificates are installed and trusted. +- Builds the AppHost project and its resources. +- Starts the AppHost and its resources. +- Starts the dashboard. + +The following snippet is an example of the output displayed by the `aspire run` command: + +```Aspire CLI +Dashboard: https://localhost:17178/login?t=17f974bf68e390b0d4548af8d7e38b65 + + Logs: /home/vscode/.aspire/cli/logs/apphost-1295-2025-07-14-18-16-13.log +``` + +## Add integrations + +[_Command reference: `aspire add`_](../cli-reference/aspire-add.md) + +The `aspire add` command is an easy way to add official integration packages to your AppHost project. Use this as an alternative to a NuGet search through your IDE. You can run `aspire add ` if you know the name or NuGet ID of the integration package. If you omit a name or ID, the tool provides a list of packages to choose from. If you provide a partial name or ID, the tool filters the list of packages with items that match the provided value. + +## Publish Aspire applications (preview) + +[_Command reference: `aspire publish`_](../cli-reference/aspire-publish.md) + +The `aspire publish` command publishes resources by serializing them to disk. When this command is run, Aspire invokes registered resource annotations, in the order they're declared. These annotations serialize a resource so that it can be consumed by deployment tools. + +Some integrations automatically register a `PublishingCallbackAnnotation` for you, for example: + +- generates Bicep assets. +- generates docker-compose yaml. +- generates Kubernetes Helm charts. + +## Deploy Aspire applications (preview) + +[_Command reference: `aspire deploy`_](../cli-reference/aspire-deploy.md) + +The `aspire deploy` command is similar to `aspire publish`. After Aspire has invoked the publishing annotations to serialize resources to disk, it invokes `DeployingCallbackAnnotation` resource annotations, in the order they're declared. + +As of Aspire 9.4, Aspire doesn't include any default deployment annotations for its resources, you must use the `DeployingCallbackAnnotation` to build your own. + +> [!TIP] +> Consider this a good way to deploy your Aspire solution to a staging or testing environment. + +## Manage Aspire configuration (preview) + +[_Command reference: `aspire config`_](../cli-reference/aspire-config.md) + +The `aspire config` command lets you manage Aspire CLI configuration settings. Use it to `list`, `get`, `set`, or `delete` configuration values that control CLI behavior. This command is also used to toggle features on or off. + +## Run commands in resource context (preview) + +[_Command reference: `aspire exec`_](../cli-reference/aspire-exec.md) + +The `aspire exec` command runs a command in the context of a specific Aspire resource, inheriting that resource's configuration, including environment variables, connection strings, and working directory. This is particularly useful for scenarios like running Entity Framework migrations where you need to run commands with the same configuration as your application. For example, you can run `aspire exec --resource api -- dotnet ef migrations add Init` to run Entity Framework commands with the proper database connection strings automatically configured. diff --git a/docs/community-toolkit/snippets/Dapr/Dapr.AppHost/Dapr.AppHost.csproj b/docs/community-toolkit/snippets/Dapr/Dapr.AppHost/Dapr.AppHost.csproj index 1410c77362..d26b5d7ba9 100644 --- a/docs/community-toolkit/snippets/Dapr/Dapr.AppHost/Dapr.AppHost.csproj +++ b/docs/community-toolkit/snippets/Dapr/Dapr.AppHost/Dapr.AppHost.csproj @@ -1,5 +1,5 @@ - + Exe net9.0 @@ -12,7 +12,7 @@ - + \ No newline at end of file diff --git a/docs/community-toolkit/snippets/Dapr/Dapr.ServiceDefaults/Dapr.ServiceDefaults.csproj b/docs/community-toolkit/snippets/Dapr/Dapr.ServiceDefaults/Dapr.ServiceDefaults.csproj index ae37426e09..9183e963ee 100644 --- a/docs/community-toolkit/snippets/Dapr/Dapr.ServiceDefaults/Dapr.ServiceDefaults.csproj +++ b/docs/community-toolkit/snippets/Dapr/Dapr.ServiceDefaults/Dapr.ServiceDefaults.csproj @@ -11,7 +11,7 @@ - + diff --git a/docs/compatibility/9.3/remove-publisher-apis.md b/docs/compatibility/9.3/remove-publisher-apis.md index 8fc346ba53..81e3ed46e4 100644 --- a/docs/compatibility/9.3/remove-publisher-apis.md +++ b/docs/compatibility/9.3/remove-publisher-apis.md @@ -24,9 +24,9 @@ In .NET Aspire 9.2, the publisher API was introduced in preview, allowing the us These publishers were added using the following methods: -- -- -- +- `Aspire.Hosting.DockerComposePublisherExtensions.AddDockerComposePublisher*` +- `Aspire.Hosting.KubernetesPublisherExtensions.AddKubernetesPublisher*` +- `Aspire.Hosting.AzurePublisherExtensions.AddAzurePublisher*` Multiple publishers could be added, and the `aspire publish` CLI command allowed users to select one for publishing. @@ -78,6 +78,6 @@ var azure = builder.AddAzureEnvironment("azure"); ## Affected APIs -- -- -- +- Aspire.Hosting.DockerComposePublisherExtensions.AddDockerComposePublisher* +- Aspire.Hosting.KubernetesPublisherExtensions.AddKubernetesPublisher* +- Aspire.Hosting.AzurePublisherExtensions.AddAzurePublisher* diff --git a/docs/compatibility/9.4/add-azure-openai-default-changes.md b/docs/compatibility/9.4/add-azure-openai-default-changes.md new file mode 100644 index 0000000000..76ab3b5226 --- /dev/null +++ b/docs/compatibility/9.4/add-azure-openai-default-changes.md @@ -0,0 +1,52 @@ +--- +title: "Breaking change - AddAzureOpenAI defaults to CognitiveServicesOpenAIUser instead of CognitiveServicesOpenAIContributor" +description: "Learn about the breaking change in .NET Aspire 9.4 where AddAzureOpenAI defaults to a lower privilege role." +ms.date: 7/11/2025 +ai-usage: ai-assisted +ms.custom: https://github.com/dotnet/docs-aspire/issues/3936 +--- + +# AddAzureOpenAI defaults to CognitiveServicesOpenAIUser instead of CognitiveServicesOpenAIContributor + +In .NET Aspire 9.4, the default role assigned to applications using `AddAzureOpenAI` was changed from `CognitiveServicesOpenAIContributor` to `CognitiveServicesOpenAIUser`. This change improves security by assigning a lower privilege role by default, ensuring applications only have the permissions necessary for inference tasks. + +## Version introduced + +.NET Aspire 9.4 + +## Previous behavior + +Previously, applications referencing an Azure OpenAI account were assigned as the `CognitiveServicesOpenAIContributor` role by default. This role allowed applications to manage OpenAI deployments, which is a higher privilege than typically required for inference tasks. + +## New behavior + +Applications referencing an Azure OpenAI account are now assigned the `CognitiveServicesOpenAIUser` role by default. This role provides permissions for inference tasks without allowing management of OpenAI deployments. If higher privileges are required, you can configure the necessary roles using the `WithRoleAssignments` API. + +Example: + +```csharp +using Azure.Provisioning.CognitiveServices; + +var openai = builder.AddAzureOpenAI("openai"); + +builder.AddProject("api") + .WithRoleAssignments(openai, CognitiveServicesBuiltInRole.CognitiveServicesOpenAIContributor); +``` + +## Type of breaking change + +This is a [behavioral change](../categories.md#behavioral-change). + +## Reason for change + +The `CognitiveServicesOpenAIContributor` role provides excessive privileges for most applications, as managing OpenAI deployments isn't typically required. Assigning the `CognitiveServicesOpenAIUser` role by default enhances security by limiting permissions to inference tasks. For applications requiring higher privileges, roles can be explicitly configured using the API. + +For more information, see [GitHub PR #10293](https://github.com/dotnet/aspire/pull/10293). + +## Recommended action + +If your application requires higher privileges than the `CognitiveServicesOpenAIUser` role, explicitly configure the necessary roles using the `WithRoleAssignments` API. See the [New behavior](#new-behavior) section for an example of how to do this. + +## Affected APIs + +- `Aspire.Hosting.AzureOpenAIExtensions.AddAzureOpenAI` diff --git a/docs/compatibility/9.4/azure-bicep-parameters-deprecated.md b/docs/compatibility/9.4/azure-bicep-parameters-deprecated.md new file mode 100644 index 0000000000..64e2f5a812 --- /dev/null +++ b/docs/compatibility/9.4/azure-bicep-parameters-deprecated.md @@ -0,0 +1,116 @@ +--- +title: "Breaking change - Deprecating various known parameters in AzureBicepResource" +description: "Learn about the breaking change in Aspire where known parameter injection in AzureBicepResource has been deprecated." +ms.date: 7/4/2025 +ai-usage: ai-assisted +ms.custom: https://github.com/dotnet/docs-aspire/issues/3675 +--- + +# Deprecating various known parameters in AzureBicepResource + +Known parameter injection in `AzureBicepResource` has been deprecated. The parameters `AzureBicepResource.KnownParameters.KeyVaultName`, `AzureBicepResource.KnownParameters.LogAnalyticsWorkspaceId`, `containerAppEnvironmentId`, and `containerAppEnvironmentName` are now marked as `[Obsolete]` and ignored during infrastructure generation. Developers must explicitly model these resources or use new helper APIs to ensure proper configuration. + +## Version introduced + +This change applies to Aspire starting from version 7.4.2025. + +## Previous behavior + +Previously, Aspire automatically injected the following known parameters into Bicep templates at build time without requiring explicit assignment: + +| Bicep parameter name | Known parameter constant | Typical use | +|--|--|--| +| `keyVaultName` | `KeyVaultName` | Reference a Key Vault for secrets mapped to `AzureBicepResource.GetSecretOutput`. | +| `logAnalyticsWorkspaceId` | `LogAnalyticsWorkspaceId` | Reference the Log Analytics Workspace ID associated with the environment. | +| `containerAppEnvironmentId` | N/A | Reference the container app environment ID. | +| `containerAppEnvironmentName` | N/A | Reference the container app environment name. | + +For example: + +```bicep +// keyvault.bicep +param keyVaultName string +resource kv 'Microsoft.KeyVault/vaults@2023-07-01' = { + name: keyVaultName + ... +} +``` + +```csharp +builder.AddBicepTemplateFile("kv", "keyvault.bicep"); // No parameter assignment needed +builder.AddContainer("api", "image"); +``` + +.NET Aspire resolved these parameters automatically, allowing templates to deploy without explicit wiring. + +## New behavior + +.NET Aspire no longer pre-populates the deprecated parameters. If a Bicep template declares any of these parameters without explicit assignment, deployment fails with an "undefined parameter" error. Developers must now explicitly model resources and pass their values to templates. + +For example: + +```csharp +var env = builder.AddAzureContainerAppEnvironment("env"); +var kv = builder.AddAzureKeyVault("kv"); +var la = builder.AddAzureLogAnalyticsWorkspace("la"); + +builder.AddBicepTemplateFile("kvTemplate", "keyvault.bicep") + .WithParameter("keyVaultName", kv.NameOutputReference); + +builder.AddBicepTemplateFile("apiTemplate", "api.bicep") + .WithParameter("containerAppEnvironmentName", env.NameOutputReference); +``` + +Inside the Bicep template: + +```bicep +param containerAppEnvironmentName string + +resource env 'Microsoft.App/managedEnvironments@2024-03-01' existing = { + name: containerAppEnvironmentName +} + +var environmentId = env.id +``` + +## Type of breaking change + +This is both a [source incompatible](../categories.md#source-compatibility) and [behavioral](../categories.md#behavioral-change) change. + +## Reason for change + +.NET Aspire now supports modeling multiple compute environments in a single application graph. Automatically injecting global parameters created ambiguity, hid dependencies, and complicated debugging. This change enforces explicit wiring, ensuring predictable behavior and enabling future scenarios where resources target specific environments. + +## Recommended action + +1. **Stop using obsolete constants** + + Remove any code that relies on `AzureBicepResource.KnownParameters.KeyVaultName`, `AzureBicepResource.KnownParameters.LogAnalyticsWorkspaceId`, `containerAppEnvironmentId`, or `containerAppEnvironmentName`. + +1. **Model resources explicitly** + + Define resources like Key Vaults, Log Analytics Workspaces, and Container App Environments explicitly in your code. + + ```csharp + var env = builder.AddAzureContainerAppEnvironment("env"); + var kv = builder.AddAzureKeyVault("kv"); + var la = builder.AddAzureLogAnalyticsWorkspace("la"); + ``` + +1. **Pass parameters explicitly** + + Use strongly-typed properties like `NameOutputReference` to pass resource values to templates. + + ```csharp + builder.AddBicepTemplateFile("template", "file.bicep") + .WithParameter("keyVaultName", kv.NameOutputReference); + ``` + +1. **Address warnings** + + Update code to resolve `[Obsolete]` warnings by replacing deprecated constants with explicit resource definitions. + +## Affected APIs + +- `AzureBicepResource.KnownParameters.KeyVaultName`: Obsolete. +- `AzureBicepResource.KnownParameters.LogAnalyticsWorkspaceId`: Obsolete. diff --git a/docs/compatibility/9.4/azure-storage-apis-renamed.md b/docs/compatibility/9.4/azure-storage-apis-renamed.md new file mode 100644 index 0000000000..fea6365a74 --- /dev/null +++ b/docs/compatibility/9.4/azure-storage-apis-renamed.md @@ -0,0 +1,123 @@ +--- +title: "Breaking change - Azure Storage APIs renamed and refactored" +description: "Learn about the breaking changes in .NET Aspire 9.4 where Azure Storage APIs were renamed and refactored for clarity and consistency." +ms.date: 07/08/2025 +ai-usage: ai-assisted +ms.custom: https://github.com/dotnet/docs-aspire/issues/3930 +--- + +# Azure Storage APIs renamed and refactored + +In .NET Aspire 9.4, several Azure Storage APIs were renamed and refactored for clarity and consistency. These changes affect how you add and configure Azure Blob, Queue, and Table storage resources and clients in your Aspire applications. The new API names better align with Azure resource naming and reduce confusion. + +## Version introduced + +.NET Aspire 9.4 + +## Previous behavior + +Previously, you used methods like `AddBlobs`, `AddBlobContainer`, `AddQueues`, and `AddTables` to add Azure Storage resources. + +**Hosting integration example:** + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +var storage = builder.AddAzureStorage("storage"); + +var blobs = storage.AddBlobs("blobs"); +var blobContainer = blobs.AddBlobContainer("container"); + +var queues = storage.AddQueues("queues"); +var tables = storage.AddTables("tables"); +``` + +Client registration methods also used names like `AddAzureBlobClient`, `AddAzureQueueClient`, and `AddAzureTableClient`. + +**Client integration example:** + +```csharp +var builder = WebApplication.CreateBuilder(args); + +builder.AddAzureBlobClient("storage"); +builder.AddAzureQueueClient("storage"); +builder.AddAzureTableClient("storage"); +``` + +## New behavior + +Now, the API uses more explicit names that match Azure resource types. For example, use `AddBlobService`, `AddBlobContainer`, `AddQueueService`, `AddQueue`, `AddTableService`, and `AddTable`. + +**Hosting integration example:** + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +var storage = builder.AddAzureStorage("storage"); + +var blobs = storage.AddBlobService("blobService"); +blobs.AddBlobContainer("container"); + +var queues = storage.AddQueueService("queueService"); +queues.AddQueue("queue"); + +var tables = storage.AddTableService("tableService"); +``` + +Client registration methods now use names like `AddAzureBlobServiceClient`, `AddAzureQueueServiceClient`, and `AddAzureTableServiceClient`. + +**Client integration example:** + +```csharp +var builder = WebApplication.CreateBuilder(args); + +builder.AddAzureBlobServiceClient("storage"); +builder.AddAzureQueueServiceClient("storage"); +builder.AddAzureTableServiceClient("storage"); +``` + +### API changes summary + +The following table summarizes the key hosting integration API changes: + +| Obsolete API | New API | Notes | +|--|--|--| +| `AddBlobs` | `AddBlobService` | — | +| `AddBlobContainer` | `AddBlobContainer` | New API uses `IResourceBuilder` overload. | +| `AddTables` | `AddTableService` | — | +| `AddQueues` | `AddQueueService` | — | +| N/A | `AddQueue` | — | + +The following table summarizes the key client registration API changes: + +| Obsolete API | New API | +|--|--| +| `AddAzureBlobClient` | `AddAzureBlobServiceClient` | +| `AddAzureQueueClient` | `AddAzureQueueServiceClient` | +| `AddAzureTableClient` | `AddAzureTableServiceClient` | + +## Type of breaking change + +This change is a [binary incompatible](../categories.md#binary-compatibility) and [source incompatible](../categories.md#source-compatibility) change. + +## Reason for change + +The new API names provide consistency with Azure client libraries and resource granularity. This reduces confusion and makes it easier to understand and maintain Aspire applications that use Azure Storage resources. For more information, see the [GitHub issue](https://github.com/dotnet/docs-aspire/issues/3930). + +## Recommended action + +1. Update your code to use the new method names for adding and configuring Azure Storage resources and clients. +1. Replace any obsolete method calls with their new equivalents as shown in the examples above. +1. Recompile your application to ensure compatibility with .NET Aspire 9.4. + +## Affected APIs + +- `AddBlobs` +- `AddBlobContainer` +- `AddTables` +- `AddQueues` +- `AddAzureBlobClient` +- `AddAzureQueueClient` +- `AddAzureTableClient` + +For a complete list of changes, see the [pull request](https://github.com/dotnet/aspire/pull/10241). diff --git a/docs/compatibility/9.4/getsecretoutput-deprecated.md b/docs/compatibility/9.4/getsecretoutput-deprecated.md new file mode 100644 index 0000000000..a826f460ec --- /dev/null +++ b/docs/compatibility/9.4/getsecretoutput-deprecated.md @@ -0,0 +1,76 @@ +--- +title: "Breaking change - BicepSecretOutputReference and GetSecretOutput are now obsolete" +description: "Learn about the breaking change in .NET Aspire 9.4 where BicepSecretOutputReference, GetSecretOutput, and related automatic Key Vault logic are deprecated." +ms.date: 07/08/2025 +ai-usage: ai-assisted +ms.custom: https://github.com/dotnet/docs-aspire/issues/3670 +--- + +# BicepSecretOutputReference and GetSecretOutput are now obsolete + +In .NET Aspire 9.4, the `BicepSecretOutputReference` type, the `GetSecretOutput(...)` helper method, and the overload of `WithEnvironment` that accepted a `BicepSecretOutputReference` are now obsolete. Automatic Key Vault generation and secret wiring logic were removed. Projects that relied on these APIs for automatic secret management must migrate to explicit Key Vault resource modeling and secret references. + +## Version introduced + +.NET Aspire 9.4 + +## Previous behavior + +Previously, you could use `GetSecretOutput(...)` to obtain a `BicepSecretOutputReference` from a resource, and pass it to `WithEnvironment`. Aspire would automatically generate a Key Vault and wire up the secret URI for you. + +Example: + +```csharp +var db = builder.AddAzureCosmosDB("mydb").WithAccessKeyAuthentication(); + +builder.AddContainer("api", "image") + .WithEnvironment("ConnStr", db.GetSecretOutput("connectionString")); +``` + +## New behavior + +Now, Aspire no longer creates Key Vaults or secrets automatically. You must explicitly create or reference a Key Vault and use an explicit secret reference. + +Example: + +```csharp +var kv = builder.AddAzureKeyVault("kv"); +builder.AddContainer("api", "image") + .WithEnvironment("ConnStr", kv.GetSecret("connectionString")); +``` + +`GetSecretOutput(...)` is now obsolete and will be removed in a future release. The overload of `WithEnvironment` that accepted a `BicepSecretOutputReference` is also obsolete. + +## Type of breaking change + +This change is a [binary incompatible](../categories.md#binary-compatibility) and [source incompatible](../categories.md#source-compatibility) change. + +## Reason for change + +Implicit Key Vault creation made deployments opaque and fragile. Removing the secret-output shortcut aligns Aspire with its explicit-resource philosophy, giving you full control over secret management and simplifying infrastructure generation. For more information, see the [GitHub issue](https://github.com/dotnet/docs-aspire/issues/3670). + +## Recommended action + +1. Create or reference a Key Vault in your Aspire graph: + + ```csharp + var kv = builder.AddAzureKeyVault("kv"); + ``` + +1. Replace `GetSecretOutput` usage with an explicit secret reference: + + ```csharp + builder.AddContainer("api", "image") + .WithEnvironment("ConnStr", kv.GetSecret("connectionString")); + ``` + +1. Remove obsolete `WithEnvironment(string, BicepSecretOutputReference)` overloads and switch to `WithEnvironment(string, IAzureKeyVaultSecretReference)` (or another appropriate overload). + +Aspire's resources with support for keys were updated to handle this new change. + +## Affected APIs + +- +- +- +- Automatic Key Vault generation and secret wiring logic (removed) diff --git a/docs/compatibility/9.4/hybrid-compute-support-dropped.md b/docs/compatibility/9.4/hybrid-compute-support-dropped.md new file mode 100644 index 0000000000..96dc837d50 --- /dev/null +++ b/docs/compatibility/9.4/hybrid-compute-support-dropped.md @@ -0,0 +1,64 @@ +--- +title: "Breaking change - Hybrid compute mode between azd and Aspire apps dropped" +description: "Learn about the breaking change in .NET Aspire 9.4 where hybrid compute mode support between azd and .NET Aspire apps was dropped." +ms.date: 07/22/2025 +ai-usage: ai-assisted +ms.custom: https://github.com/dotnet/docs-aspire/issues/3669 +--- + +# Hybrid compute mode between azd and Aspire apps dropped + +.NET Aspire 9.4 removes hybrid compute mode. In this mode, Azure Developer CLI (azd) creates the Azure Container Apps Environment, and .NET Aspire generates the individual Container App Bicep modules. Projects that rely on azd-owned environments must now model an `AzureContainerAppEnvironmentResource` inside the .NET Aspire application. + +## Version introduced + +.NET Aspire 9.4 + +## Previous behavior + +You use `PublishAsAzureContainerApp()` to automatically connect to infrastructure that azd generates when you deploy: + +```csharp +builder.AddContainer("api", "image") + .PublishAsAzureContainerApp((infra, app) => { }) // ← automatically added infra + .WithReference(db); +``` + +The `PublishAsAzureContainerApp()` method would automatically handle the connection to the Azure Container Apps Environment created by azd. + +## New behavior + +You must now explicitly add an Azure Container Apps Environment resource to your .NET Aspire application: + +```csharp +builder.AddAzureContainerAppEnvironment("env"); // Required to target ACA +builder.AddContainer("api", "image") + .WithReference(db); +``` + +The environment resource must be explicitly defined before you can deploy containers to Azure Container Apps. + +## Type of breaking change + +This is a [behavioral change](../categories.md#behavioral-change). + +## Reason for change + +The hybrid path created hidden coupling and ambiguity: + +- Known-parameter substitution (Key Vault names, volume storage accounts, etc.) couldn't be resolved reliably when multiple compute environments existed. +- The change aligns .NET Aspire with the "compute-environment-as-resource" design and simplifies infrastructure generation logic. + +## Recommended action + +Add an environment resource to your .NET Aspire application using the `AddAzureContainerAppEnvironment` method: + +```csharp +builder.AddAzureContainerAppEnvironment("env"); +``` + +This explicit declaration ensures that your application has a clear, unambiguous reference to the Azure Container Apps Environment. + +## Affected APIs + +- `PublishAsAzureContainerApp` diff --git a/docs/compatibility/9.4/index.md b/docs/compatibility/9.4/index.md new file mode 100644 index 0000000000..1518310eb0 --- /dev/null +++ b/docs/compatibility/9.4/index.md @@ -0,0 +1,26 @@ +--- +title: Breaking changes in .NET Aspire 9.4 +titleSuffix: "" +description: Navigate to the breaking changes in .NET Aspire 9.4. +ms.date: 07/22/2025 +--- + +# Breaking changes in .NET Aspire 9.4 + +If you're migrating an app to .NET Aspire 9.4, 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 .NET Aspire 9.4. + +## Breaking changes + +| Title | Type of change | Introduced version | +|--|--|--| +| [AddAzureOpenAI defaults to CognitiveServicesOpenAIUser role](add-azure-openai-default-changes.md) | Behavioral change | 9.4 | +| [Azure Storage APIs renamed and refactored](azure-storage-apis-renamed.md) | Binary incompatible, source incompatible | 9.4 | +| [BicepSecretOutputReference and GetSecretOutput are now obsolete](getsecretoutput-deprecated.md) | Binary incompatible, source incompatible | 9.4 | +| [Deprecating various known parameters in AzureBicepResource](azure-bicep-parameters-deprecated.md) | Source incompatible, behavioral change | 9.4 | +| [Hybrid compute mode between azd and Aspire apps dropped](hybrid-compute-support-dropped.md) | Behavioral change | 9.4 | +| [Local auth is disabled by default on Azure resources](local-auth-disabled-for-azure-resources.md) | Behavioral change | 9.4 | diff --git a/docs/compatibility/9.4/local-auth-disabled-for-azure-resources.md b/docs/compatibility/9.4/local-auth-disabled-for-azure-resources.md new file mode 100644 index 0000000000..66f36e8a2d --- /dev/null +++ b/docs/compatibility/9.4/local-auth-disabled-for-azure-resources.md @@ -0,0 +1,53 @@ +--- +title: "Breaking change - Local auth is disabled by default on Azure resources" +description: "Learn about the breaking change in Aspire 9.4 where local authentication is disabled by default for certain Azure resources." +ms.date: 7/4/2025 +ai-usage: ai-assisted +ms.custom: https://github.com/dotnet/docs-aspire/issues/3723 +--- + +# Local authentication is disabled by default on Azure resources + +Starting in .NET Aspire 9.4, local authentication is disabled by default for Azure EventHubs and Azure WebPubSub integrations. This change improves security by aligning with Azure environments that reject resources with local authentication enabled. + +## Version introduced + +.NET Aspire 9.4 + +## Previous behavior + +Previously, Azure EventHubs and Azure WebPubSub resources were created with local authentication enabled by default (`disableLocalAuth = false`). + +## New behavior + +Now, Azure EventHubs and Azure WebPubSub resources are created with local authentication disabled by default (`disableLocalAuth = true`). + +## Type of breaking change + +This is a [behavioral change](../categories.md#behavioral-change). + +## Reason for change + +Disabling local authentication by default provides a more secure configuration. Some Azure environments reject resources with local authentication enabled, and this change ensures compatibility with those environments. + +## Recommended action + +If you are using the .NET Aspire client integrations for these services, no changes are required, and your application will continue to function as expected. + +If you're using a SAS token or other connection string with an access key, you must either: + +1. Re-enable local authentication using the method. +1. Update your application to use Entra ID authentication. + +### Example: Re-enabling local authentication + +1. In the corresponding Azure resource, chain a call to `ConfigureInfrastructure`. +1. Get the instance of the provisioning resource type in question, for example: + + - Azure Event Hubs: . For more information on configuring infra, see [Customize provisioning infrastructure](../../messaging/azure-event-hubs-integration.md#customize-provisioning-infrastructure). + - Azure Web PubSub: . For more information on configuring infra, see [Customize provisioning infrastructure](../../messaging/azure-web-pubsub-integration.md#customize-provisioning-infrastructure). + +## Affected APIs + +- `AddAzureEventHubs` +- `AddAzureWebPubSub` diff --git a/docs/compatibility/toc.yml b/docs/compatibility/toc.yml index 1dc0ede560..a949261561 100644 --- a/docs/compatibility/toc.yml +++ b/docs/compatibility/toc.yml @@ -5,8 +5,27 @@ items: href: ../get-started/aspire-overview.md - name: Breaking changes href: breaking-changes.md -- name: .NET Aspire 9.3 +- name: .NET Aspire 9.4 expanded: true + items: + - name: Overview + href: 9.4/index.md + - name: Breaking changes in 9.4 + expanded: true + items: + - name: AddAzureOpenAI defaults to CognitiveServicesOpenAIUser role + href: 9.4/add-azure-openai-default-changes.md + - name: Azure Storage APIs renamed and refactored + href: 9.4/azure-storage-apis-renamed.md + - name: BicepSecretOutputReference and GetSecretOutput are now obsolete + href: 9.4/getsecretoutput-deprecated.md + - name: Deprecating various known parameters in AzureBicepResource + href: 9.4/azure-bicep-parameters-deprecated.md + - name: Hybrid compute mode between azd and Aspire apps dropped + href: 9.4/hybrid-compute-support-dropped.md + - name: Local auth disabled by default on Azure resources + href: 9.4/local-auth-disabled-for-azure-resources.md +- name: .NET Aspire 9.3 items: - name: Overview href: 9.3/index.md diff --git a/docs/database/ef-core-migrations.md b/docs/database/ef-core-migrations.md index 8210cc890a..368d68a870 100644 --- a/docs/database/ef-core-migrations.md +++ b/docs/database/ef-core-migrations.md @@ -181,7 +181,7 @@ To create a service that applies the migrations: ```dotnetcli cd SupportTicketApi.MigrationService - dotnet add package Aspire.Microsoft.EntityFrameworkCore.SqlServer -v "9.3.1" + dotnet add package Aspire.Microsoft.EntityFrameworkCore.SqlServer -v "9.4.0" ``` > [!TIP] diff --git a/docs/database/includes/cosmos-app-host.md b/docs/database/includes/cosmos-app-host.md index a3021aa8a1..4e46064275 100644 --- a/docs/database/includes/cosmos-app-host.md +++ b/docs/database/includes/cosmos-app-host.md @@ -49,11 +49,11 @@ When you add an to the app host, it If you're new to [Bicep](/azure/azure-resource-manager/bicep/overview), it's a domain-specific language for defining Azure resources. With .NET Aspire, you don't need to write Bicep by-hand, instead the provisioning APIs generate Bicep for you. When you publish your app, the generated Bicep is output alongside the manifest file. When you add an Azure Cosmos DB resource, the following Bicep is generated: -:::code language="bicep" source="../../snippets/azure/AppHost/cosmos.module.bicep"::: +:::code language="bicep" source="../../snippets/azure/AppHost/cosmos/cosmos.bicep"::: The preceding Bicep is a module that provisions an Azure Cosmos DB account resource. Additionally, role assignments are created for the Azure resource in a separate module: -:::code language="bicep" source="../../snippets/azure/AppHost/cosmos-roles.module.bicep"::: +:::code language="bicep" source="../../snippets/azure/AppHost/cosmos-roles/cosmos-roles.bicep"::: The generated Bicep is a starting point and is influenced by changes to the provisioning infrastructure in C#. Customizations to the Bicep file directly will be overwritten, so make changes through the C# provisioning APIs to ensure they are reflected in the generated files. diff --git a/docs/database/includes/postgresql-flexible-server.md b/docs/database/includes/postgresql-flexible-server.md index ce462453bd..4dff5b6003 100644 --- a/docs/database/includes/postgresql-flexible-server.md +++ b/docs/database/includes/postgresql-flexible-server.md @@ -60,11 +60,11 @@ The preceding call to `AddAzurePostgresFlexibleServer` configures the PostgresSQ If you're new to [Bicep](/azure/azure-resource-manager/bicep/overview), it's a domain-specific language for defining Azure resources. With .NET Aspire, you don't need to write Bicep by hand, because the provisioning APIs generate Bicep for you. When you publish your app, the generated Bicep is output alongside the manifest file. When you add an Azure PostgreSQL resource, the following Bicep is generated: -:::code language="bicep" source="../../snippets/azure/AppHost/postgres-flexible.module.bicep"::: +:::code language="bicep" source="../../snippets/azure/AppHost/postgres-flexible/postgres-flexible.bicep"::: The preceding Bicep is a module that provisions an Azure PostgreSQL flexible server resource. Additionally, role assignments are created for the Azure resource in a separate module: -:::code language="bicep" source="../../snippets/azure/AppHost/postgres-flexible-roles.module.bicep"::: +:::code language="bicep" source="../../snippets/azure/AppHost/postgres-flexible-roles/postgres-flexible-roles.bicep"::: In addition to the PostgreSQL flexible server, it also provisions an Azure Firewall rule to allow all Azure IP addresses. Finally, an administrator is created for the PostgreSQL server, and the connection string is outputted as an output variable. The generated Bicep is a starting point and is influenced by changes to the provisioning infrastructure in C#. Customizations to the Bicep file directly will be overwritten, so make changes through the C# provisioning APIs to ensure they are reflected in the generated files. diff --git a/docs/database/postgresql-integration.md b/docs/database/postgresql-integration.md index 045c9d4c2e..9109180d2b 100644 --- a/docs/database/postgresql-integration.md +++ b/docs/database/postgresql-integration.md @@ -1,7 +1,7 @@ --- title: .NET Aspire PostgreSQL integration description: Learn how to integrate PostgreSQL with .NET Aspire applications, using both hosting and client integrations. -ms.date: 02/07/2025 +ms.date: 07/22/2025 uid: database/postgresql-integration --- @@ -21,7 +21,7 @@ The PostgreSQL hosting integration automatically adds a health check for the Pos The hosting integration relies on the [📦 AspNetCore.HealthChecks.Npgsql](https://www.nuget.org/packages/AspNetCore.HealthChecks.Npgsql) NuGet package. -## Using with non-.NET applications +### Using with non-.NET applications The PostgreSQL hosting integration can be used with any application technology, not just .NET applications. When you use to reference a PostgreSQL resource, connection information is automatically injected as environment variables into the referencing application. diff --git a/docs/database/qdrant-integration.md b/docs/database/qdrant-integration.md index 6cd94be8b8..dfe4cedebf 100644 --- a/docs/database/qdrant-integration.md +++ b/docs/database/qdrant-integration.md @@ -1,7 +1,7 @@ --- title: .NET Aspire Qdrant integration description: Learn how to use the .NET Aspire Qdrant integration, which includes both hosting and client integrations. -ms.date: 01/13/2025 +ms.date: 07/22/2025 uid: database/qdrant-integration --- @@ -158,7 +158,7 @@ dotnet add package Aspire.Qdrant.Client --- -## Add a Qdrant client +### Add a Qdrant client In the _Program.cs_ file of your client-consuming project, call the extension method on any to register a `QdrantClient` for use through the dependency injection container. The method takes a connection name parameter. @@ -180,7 +180,7 @@ public class ExampleService(QdrantClient client) For more information on dependency injection, see [.NET dependency injection](/dotnet/core/extensions/dependency-injection). -## Add keyed Qdrant client +### Add keyed Qdrant client There might be situations where you want to register multiple `QdrantClient` instances with different connection names. To register keyed Qdrant clients, call the method: diff --git a/docs/database/snippets/cosmos-db/AspireApp.ApiService/AspireApp.ApiService.csproj b/docs/database/snippets/cosmos-db/AspireApp.ApiService/AspireApp.ApiService.csproj index 1d55543532..1eeb8e4b43 100644 --- a/docs/database/snippets/cosmos-db/AspireApp.ApiService/AspireApp.ApiService.csproj +++ b/docs/database/snippets/cosmos-db/AspireApp.ApiService/AspireApp.ApiService.csproj @@ -7,7 +7,7 @@ - + diff --git a/docs/database/snippets/cosmos-db/AspireApp.AppHost/AspireApp.AppHost.csproj b/docs/database/snippets/cosmos-db/AspireApp.AppHost/AspireApp.AppHost.csproj index bad0fb4fcd..f8df77a71f 100644 --- a/docs/database/snippets/cosmos-db/AspireApp.AppHost/AspireApp.AppHost.csproj +++ b/docs/database/snippets/cosmos-db/AspireApp.AppHost/AspireApp.AppHost.csproj @@ -1,5 +1,5 @@ - + Exe net9.0 @@ -12,7 +12,7 @@ - - + + \ No newline at end of file diff --git a/docs/database/snippets/cosmos-db/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj b/docs/database/snippets/cosmos-db/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj index ae37426e09..9183e963ee 100644 --- a/docs/database/snippets/cosmos-db/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj +++ b/docs/database/snippets/cosmos-db/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj @@ -11,7 +11,7 @@ - + diff --git a/docs/database/snippets/mysql-seed-data/MySQLSeedData.csproj b/docs/database/snippets/mysql-seed-data/MySQLSeedData.csproj index 4bb09d8378..9ca6230e29 100644 --- a/docs/database/snippets/mysql-seed-data/MySQLSeedData.csproj +++ b/docs/database/snippets/mysql-seed-data/MySQLSeedData.csproj @@ -1,6 +1,6 @@ - + Exe @@ -11,8 +11,8 @@ - - + + diff --git a/docs/database/snippets/postgres-data-volume-deploy/PostgresDataVolumeDeploy.csproj b/docs/database/snippets/postgres-data-volume-deploy/PostgresDataVolumeDeploy.csproj index 44c9cc6200..80c53f23f7 100644 --- a/docs/database/snippets/postgres-data-volume-deploy/PostgresDataVolumeDeploy.csproj +++ b/docs/database/snippets/postgres-data-volume-deploy/PostgresDataVolumeDeploy.csproj @@ -1,6 +1,6 @@ - + Exe @@ -11,8 +11,8 @@ - - + + diff --git a/docs/database/snippets/sql-server-bind-mount/AspireApp.AppHost/AspireApp.AppHost.csproj b/docs/database/snippets/sql-server-bind-mount/AspireApp.AppHost/AspireApp.AppHost.csproj index 303004f595..1e9451e09b 100644 --- a/docs/database/snippets/sql-server-bind-mount/AspireApp.AppHost/AspireApp.AppHost.csproj +++ b/docs/database/snippets/sql-server-bind-mount/AspireApp.AppHost/AspireApp.AppHost.csproj @@ -1,6 +1,6 @@ - + Exe @@ -11,8 +11,8 @@ - - + + diff --git a/docs/database/snippets/sql-server-bind-mount/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj b/docs/database/snippets/sql-server-bind-mount/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj index ae37426e09..9183e963ee 100644 --- a/docs/database/snippets/sql-server-bind-mount/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj +++ b/docs/database/snippets/sql-server-bind-mount/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj @@ -11,7 +11,7 @@ - + diff --git a/docs/database/snippets/sql-server-creation-script/AspireApp.AppHost/AspireApp.AppHost.csproj b/docs/database/snippets/sql-server-creation-script/AspireApp.AppHost/AspireApp.AppHost.csproj index 9fa0545165..27154969c4 100644 --- a/docs/database/snippets/sql-server-creation-script/AspireApp.AppHost/AspireApp.AppHost.csproj +++ b/docs/database/snippets/sql-server-creation-script/AspireApp.AppHost/AspireApp.AppHost.csproj @@ -1,6 +1,6 @@ - + Exe @@ -11,8 +11,8 @@ - - + + diff --git a/docs/database/snippets/sql-server-creation-script/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj b/docs/database/snippets/sql-server-creation-script/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj index ae37426e09..9183e963ee 100644 --- a/docs/database/snippets/sql-server-creation-script/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj +++ b/docs/database/snippets/sql-server-creation-script/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj @@ -11,7 +11,7 @@ - + diff --git a/docs/database/snippets/sql-server-data-volume/AspireApp.AppHost/AspireApp.AppHost.csproj b/docs/database/snippets/sql-server-data-volume/AspireApp.AppHost/AspireApp.AppHost.csproj index 3e861c60de..689f70c888 100644 --- a/docs/database/snippets/sql-server-data-volume/AspireApp.AppHost/AspireApp.AppHost.csproj +++ b/docs/database/snippets/sql-server-data-volume/AspireApp.AppHost/AspireApp.AppHost.csproj @@ -1,6 +1,6 @@ - + Exe @@ -11,8 +11,8 @@ - - + + diff --git a/docs/database/snippets/sql-server-data-volume/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj b/docs/database/snippets/sql-server-data-volume/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj index ae37426e09..9183e963ee 100644 --- a/docs/database/snippets/sql-server-data-volume/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj +++ b/docs/database/snippets/sql-server-data-volume/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj @@ -11,7 +11,7 @@ - + diff --git a/docs/database/snippets/sql-server-integration/AspireApp.AppHost/AspireApp.AppHost.csproj b/docs/database/snippets/sql-server-integration/AspireApp.AppHost/AspireApp.AppHost.csproj index 17e18adf71..513da65e20 100644 --- a/docs/database/snippets/sql-server-integration/AspireApp.AppHost/AspireApp.AppHost.csproj +++ b/docs/database/snippets/sql-server-integration/AspireApp.AppHost/AspireApp.AppHost.csproj @@ -1,6 +1,6 @@ - + Exe @@ -11,8 +11,8 @@ - - + + diff --git a/docs/database/snippets/sql-server-integration/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj b/docs/database/snippets/sql-server-integration/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj index ae37426e09..9183e963ee 100644 --- a/docs/database/snippets/sql-server-integration/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj +++ b/docs/database/snippets/sql-server-integration/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj @@ -11,7 +11,7 @@ - + diff --git a/docs/database/snippets/sql-server-parameters/AspireApp.AppHost/AspireApp.AppHost.csproj b/docs/database/snippets/sql-server-parameters/AspireApp.AppHost/AspireApp.AppHost.csproj index bfecd7917b..6ede698ce9 100644 --- a/docs/database/snippets/sql-server-parameters/AspireApp.AppHost/AspireApp.AppHost.csproj +++ b/docs/database/snippets/sql-server-parameters/AspireApp.AppHost/AspireApp.AppHost.csproj @@ -1,6 +1,6 @@ - + Exe @@ -11,8 +11,8 @@ - - + + diff --git a/docs/database/snippets/sql-server-parameters/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj b/docs/database/snippets/sql-server-parameters/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj index ae37426e09..9183e963ee 100644 --- a/docs/database/snippets/sql-server-parameters/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj +++ b/docs/database/snippets/sql-server-parameters/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj @@ -11,7 +11,7 @@ - + diff --git a/docs/database/snippets/tutorial/aspiresqldeployazure/AspireSql.AppHost/AspireSql.AppHost.csproj b/docs/database/snippets/tutorial/aspiresqldeployazure/AspireSql.AppHost/AspireSql.AppHost.csproj index 616ba66f37..0a48f028d2 100644 --- a/docs/database/snippets/tutorial/aspiresqldeployazure/AspireSql.AppHost/AspireSql.AppHost.csproj +++ b/docs/database/snippets/tutorial/aspiresqldeployazure/AspireSql.AppHost/AspireSql.AppHost.csproj @@ -1,5 +1,5 @@ - + Exe net9.0 @@ -12,8 +12,8 @@ - - - + + + diff --git a/docs/database/snippets/tutorial/aspiresqldeployazure/AspireSql.ServiceDefaults/AspireSql.ServiceDefaults.csproj b/docs/database/snippets/tutorial/aspiresqldeployazure/AspireSql.ServiceDefaults/AspireSql.ServiceDefaults.csproj index ae37426e09..9183e963ee 100644 --- a/docs/database/snippets/tutorial/aspiresqldeployazure/AspireSql.ServiceDefaults/AspireSql.ServiceDefaults.csproj +++ b/docs/database/snippets/tutorial/aspiresqldeployazure/AspireSql.ServiceDefaults/AspireSql.ServiceDefaults.csproj @@ -11,7 +11,7 @@ - + diff --git a/docs/database/snippets/tutorial/aspiresqldeploycontainer/AspireSql.AppHost/AspireSql.AppHost.csproj b/docs/database/snippets/tutorial/aspiresqldeploycontainer/AspireSql.AppHost/AspireSql.AppHost.csproj index 0a5db04180..32fe2185f6 100644 --- a/docs/database/snippets/tutorial/aspiresqldeploycontainer/AspireSql.AppHost/AspireSql.AppHost.csproj +++ b/docs/database/snippets/tutorial/aspiresqldeploycontainer/AspireSql.AppHost/AspireSql.AppHost.csproj @@ -1,5 +1,5 @@ - + Exe net9.0 @@ -12,7 +12,7 @@ - - + + diff --git a/docs/database/snippets/tutorial/aspiresqldeploycontainer/AspireSql.ServiceDefaults/AspireSql.ServiceDefaults.csproj b/docs/database/snippets/tutorial/aspiresqldeploycontainer/AspireSql.ServiceDefaults/AspireSql.ServiceDefaults.csproj index ae37426e09..9183e963ee 100644 --- a/docs/database/snippets/tutorial/aspiresqldeploycontainer/AspireSql.ServiceDefaults/AspireSql.ServiceDefaults.csproj +++ b/docs/database/snippets/tutorial/aspiresqldeploycontainer/AspireSql.ServiceDefaults/AspireSql.ServiceDefaults.csproj @@ -11,7 +11,7 @@ - + diff --git a/docs/database/snippets/tutorial/aspiresqlefcore/AspireSQLEFCore.AppHost/AspireSQLEFCore.AppHost.csproj b/docs/database/snippets/tutorial/aspiresqlefcore/AspireSQLEFCore.AppHost/AspireSQLEFCore.AppHost.csproj index 7d12cda31b..7b55439f47 100644 --- a/docs/database/snippets/tutorial/aspiresqlefcore/AspireSQLEFCore.AppHost/AspireSQLEFCore.AppHost.csproj +++ b/docs/database/snippets/tutorial/aspiresqlefcore/AspireSQLEFCore.AppHost/AspireSQLEFCore.AppHost.csproj @@ -1,5 +1,5 @@ - + Exe net9.0 @@ -8,8 +8,8 @@ 65600b1c-627d-4255-a706-bf7e21108831 - - + + diff --git a/docs/database/snippets/tutorial/aspiresqlefcore/AspireSQLEFCore.ServiceDefaults/AspireSQLEFCore.ServiceDefaults.csproj b/docs/database/snippets/tutorial/aspiresqlefcore/AspireSQLEFCore.ServiceDefaults/AspireSQLEFCore.ServiceDefaults.csproj index ae37426e09..9183e963ee 100644 --- a/docs/database/snippets/tutorial/aspiresqlefcore/AspireSQLEFCore.ServiceDefaults/AspireSQLEFCore.ServiceDefaults.csproj +++ b/docs/database/snippets/tutorial/aspiresqlefcore/AspireSQLEFCore.ServiceDefaults/AspireSQLEFCore.ServiceDefaults.csproj @@ -11,7 +11,7 @@ - + diff --git a/docs/database/snippets/tutorial/aspiresqlefcore/AspireSQLEFCore/AspireSQLEFCore.csproj b/docs/database/snippets/tutorial/aspiresqlefcore/AspireSQLEFCore/AspireSQLEFCore.csproj index cadccd3e30..abcb0bbfa6 100644 --- a/docs/database/snippets/tutorial/aspiresqlefcore/AspireSQLEFCore/AspireSQLEFCore.csproj +++ b/docs/database/snippets/tutorial/aspiresqlefcore/AspireSQLEFCore/AspireSQLEFCore.csproj @@ -7,7 +7,7 @@ - + diff --git a/docs/diagnostics/aspire008.md b/docs/diagnostics/aspire008.md new file mode 100644 index 0000000000..5e77450543 --- /dev/null +++ b/docs/diagnostics/aspire008.md @@ -0,0 +1,65 @@ +--- +title: Compiler Error ASPIRE008 +description: Learn more about compiler Error ASPIRE008. The Aspire workload that this project depends on is now deprecated. +ms.date: 07/22/2025 +f1_keywords: + - "ASPIRE008" +helpviewer_keywords: + - "ASPIRE008" +--- + +# Compiler Error ASPIRE008 + +**Version introduced:** 8.2.3 + +> The Aspire workload that this project depends on is now deprecated. + +This error appears when a project uses a version of .NET Aspire that relies on the SDK workload, which is now deprecated. The error guides you to migrate your project to a supported version of .NET Aspire that uses the SDK approach instead of the workload. + +## Example + +The following AppHost project file uses the deprecated Aspire workload: + +```xml + + + + Exe + net8.0 + enable + enable + true + 98048c9c-bf28-46ba-a98e-63767ee5e3a8 + + + + + + + +``` + +For more information on the Aspire SDK, see [.NET Aspire SDK](../fundamentals/dotnet-aspire-sdk.md). + +## To correct this error + +Follow the migration guide at to upgrade your project to a supported version of .NET Aspire that uses the SDK approach. + +The migration typically involves: + +1. Updating your AppHost project file to use the `Aspire.AppHost.Sdk`. +1. Removing references to the deprecated workload. +1. Updating package references to supported versions. + +## Suppress the error + +> [!WARNING] +> Suppressing this error isn't recommended, as it leaves your project depending on an unsupported version of .NET Aspire. + +If you need to temporarily suppress this error, add the following property to your project file: + +```xml + + true + +``` diff --git a/docs/diagnostics/aspireazure001.md b/docs/diagnostics/aspireazure001.md index 58ebecff2a..9f4b853a8e 100644 --- a/docs/diagnostics/aspireazure001.md +++ b/docs/diagnostics/aspireazure001.md @@ -14,7 +14,7 @@ helpviewer_keywords: > Publishers are for evaluation purposes only and are subject to change or removal in future updates. Suppress this diagnostic to proceed. -The .NET Aspire Azure hosting integration now ships with a publisher. If you're using any of the APIs, you might see a compiler error/warning indicating that the API is experimental. This behavior is expected, as the API is still in preview and the shape of this API is expected to change in the future. +The .NET Aspire Azure hosting integration now ships with a publisher. If you're using any of the `Aspire.Hosting.AzurePublisherExtensions.AddAzurePublisher*` APIs, you might see a compiler error/warning indicating that the API is experimental. This behavior is expected, as the API is still in preview and the shape of this API is expected to change in the future. ## Example diff --git a/docs/diagnostics/overview.md b/docs/diagnostics/overview.md index ca367180bd..081041a550 100644 --- a/docs/diagnostics/overview.md +++ b/docs/diagnostics/overview.md @@ -2,25 +2,156 @@ title: .NET Aspire diagnostics overview description: Learn about the diagnostics tools and features available in .NET Aspire. ms.topic: overview -ms.date: 04/15/2025 +ms.date: 07/22/2025 --- # .NET Aspire diagnostics list The following table lists the possible MSBuild and .NET Analyzer warnings and errors you might encounter with .NET Aspire: -| Diagnostic ID | Type | Description | -|---------------------------------------------------------|----------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| [`ASPIRE001`](aspire001.md) | Warning | The code language isn't fully supported by Aspire, some code generation targets will not run. | -| [`ASPIRE002`](aspire002.md) | Warning | Project is an Aspire AppHost project but necessary dependencies aren't present. Are you missing an Aspire.Hosting.AppHost PackageReference? | -| [`ASPIRE003`](aspire003.md) | Warning | 'Project' is a .NET Aspire AppHost project that requires Visual Studio version 17.10 or above to work correctly. | -| [`ASPIRE004`](aspire004.md) | Warning | 'Project' is referenced by an Aspire Host project, but it is not an executable. | -| [`ASPIRE006`](aspire006.md) | (Experimental) Error | Application model items must have valid names. | -| [`ASPIRE007`](aspire007.md) | Error | 'Project' requires a reference to "Aspire.AppHost.Sdk" with version "9.0.0" or greater to work correctly. | -| [`ASPIREACADOMAINS001`](aspireacadomains001.md) | (Experimental) Error | `ConfigureCustomDomain` is for evaluation purposes only and is subject to change or removal in future updates. | -| [`ASPIREAZURE001`](aspireazure001.md) | (Experimental) Error | Publishers are for evaluation purposes only and are subject to change or removal in future updates. | -| [`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. | -| [`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. | +| Diagnostic ID | Type | Description | +|--|--|--| +| [`ASPIRE001`](aspire001.md) | Warning | The code language isn't fully supported by Aspire, some code generation targets will not run. | +| [`ASPIRE002`](aspire002.md) | Warning | Project is an Aspire AppHost project but necessary dependencies aren't present. Are you missing an Aspire.Hosting.AppHost PackageReference? | +| [`ASPIRE003`](aspire003.md) | Warning | 'Project' is a .NET Aspire AppHost project that requires Visual Studio version 17.10 or above to work correctly. | +| [`ASPIRE004`](aspire004.md) | Warning | 'Project' is referenced by an Aspire Host project, but it is not an executable. | +| [`ASPIRE006`](aspire006.md) | (Experimental) Error | Application model items must have valid names. | +| [`ASPIRE007`](aspire007.md) | Error | 'Project' requires a reference to "Aspire.AppHost.Sdk" with version "9.0.0" or greater to work correctly. | +| [`ASPIRE008`](aspire008.md) | Error | The Aspire workload that this project depends on is now deprecated. | +| [`ASPIREACADOMAINS001`](aspireacadomains001.md) | (Experimental) Error | `ConfigureCustomDomain` is for evaluation purposes only and is subject to change or removal in future updates. | +| [`ASPIREAZURE001`](aspireazure001.md) | (Experimental) Error | Publishers are for evaluation purposes only and are subject to change or removal in future updates. | +| [`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. | +| [`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. | + +## Suppress diagnostic + +You can suppress any diagnostic in this document using one of the following methods: + +- Add the at the assembly, class, method, line, etc. +- Include the diagnostic ID in the `NoWarn` property of your project file. +- Use preprocessor directives in your code. +- Configure diagnostic severity in an _.editorconfig_ file. + +There are some common patterns for suppressing diagnostics in .NET projects. The best method depends on your context and the specific diagnostic. Here's a quick guide to help you choose: + +> [!IMPORTANT] +> The following sections provide examples for suppressing the `ASPIREE000` diagnostic, which is a placeholder for any diagnostic ID you might encounter. Replace `ASPIREE000` with the actual diagnostic ID you want to suppress. + +### Suppress with the suppress message attribute + +The is ideal when you need targeted, documented suppression tied directly to a specific code element like a class or method. It shines when you’re making a deliberate exception to a rule that is valid in most other places. This attribute keeps the suppression close to the code it affects, which helps reviewers and future maintainers understand the rationale. While it's a clean solution for isolated cases, it can clutter the code if overused, and it’s not the best choice for widespread or bulk suppressions. + +To suppress the `ASPIREE000` diagnostic with the , consider the following examples: + +**Assembly-level suppression:** + +```csharp +// Typically placed in AssemblyInfo.cs, GlobalUsings.cs, or any global file. +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage( + "Aspire.Example", // Category + "ASPIREE000:AspireExampleDiagnostic", // CheckId and optional rule title + Justification = "This warning is not applicable to our context.")] +``` + +**Class-level suppression:** + +```csharp +[System.Diagnostics.CodeAnalysis.SuppressMessage( + "Aspire.Example", + "ASPIREE000:AspireExampleDiagnostic", + Justification = "This warning is not applicable to our context.")] +public class ExampleClassThatConsumesTheOffendingAPI +{ + // Class implementation +} +``` + +**Method-level suppression:** + +```csharp +public class ExampleClass +{ + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Aspire.Example", + "ASPIREE000:AspireExampleDiagnostic", + Justification = "This warning is not applicable to our context.")] + public void ExampleMethodThatConsumesTheOffendingAPI() + { + // Method implementation + } +} +``` + +> [!IMPORTANT] +> If the diagnostic is triggered on a property, field, or parameter, you can also apply it directly there with the . + +### Suppress within the project file + +Using the `NoWarn` property in the project file is great when you need to suppress a diagnostic across the entire project. This is helpful in cases where a diagnostic is irrelevant to your scenario or when dealing with known false positives. It’s a simple, central way to silence a diagnostic without touching your code. However, it lacks visibility—developers won’t see that a diagnostic is suppressed unless they inspect the project file—so it can make it harder to track intentional exceptions or identify the reason behind suppressions. + +To suppress the `ASPIREE000` diagnostic in the project file, add the following code to your _.csproj_ file: + +```xml + + $(NoWarn);ASPIREE000 + +``` + +> [!TIP] +> The `$(NoWarn)` property in the preceding XML is used to append the diagnostic ID to the existing list of suppressed warnings. This ensures that you don't accidentally remove other suppressions already in place. + +### Suppress with preprocessor directives + +Preprocessor directives like `#pragma warning disable` offer fine-grained suppression within a specific scope, such as a method body or block of code. They're especially handy when you need a temporary suppression during refactoring, or when a rule incorrectly flags a particular line that you can't or don't want to change. The ability to tightly wrap just the affected code makes this approach powerful, but it can also make the code harder to read and maintain, especially if the directives are scattered or forgotten over time. + +To suppress the `ASPIREE000` diagnostic with preprocessor directives, consider the following examples: + +**File-level suppression:** + +```csharp +#pragma warning disable ASPIREE000 + +public class ExampleClassThatConsumesTheOffendingAPI +{ + // Class implementation +} +``` + +**Granular suppression:** + +```csharp +public class ExampleClass +{ + public void ExampleMethodThatConsumesTheOffendingAPI() + { + #pragma warning disable ASPIREE000 + // Code that triggers the diagnostic + #pragma warning restore ASPIREE000 + } +} +``` + +You can use preprocessor directives to suppress the diagnostic for a specific block of code. This is useful when you want to limit the scope of the suppression to a particular section of your code. + +For more information, see [C# preprocessor directives](/dotnet/csharp/language-reference/preprocessor-directives). + +### Suppress with editor configuration + +Suppressing diagnostics using an _.editorconfig_ file is ideal for enforcing or adjusting analyzer behavior at scale. It allows teams to standardize severity levels (or disable rules) across an entire solution or per directory/file pattern. This method keeps suppression cleanly out of the codebase, and it works well for communicating team-wide conventions. However, it can be a bit opaque—developers need to know to look in the config file—and it doesn't offer the precision of an attribute or pragma when dealing with one-off cases. + +To suppress the `ASPIREE000` diagnostic in an _.editorconfig_ file, add the following code: + +```ini +[*.cs] +dotnet_diagnostic.ASPIREE000.severity = none +``` + +This configuration applies to all C# files in the project. You can also scope it to specific files or directories by adjusting the section header. + +## See also + +- [.NET docs: How to suppress code analysis warnings](/dotnet/fundamentals/code-analysis/suppress-warnings) +- [Visual Studio docs: Suppress code analysis violations](/visualstudio/code-quality/in-source-suppression-overview) diff --git a/docs/extensibility/interaction-service.md b/docs/extensibility/interaction-service.md new file mode 100644 index 0000000000..9dc048a395 --- /dev/null +++ b/docs/extensibility/interaction-service.md @@ -0,0 +1,359 @@ +--- +title: Interaction Service (Preview) +description: Use the interaction service API to prompt users for input, request confirmation, and display messages in the Aspire dashboard or CLI during publish and deploy. +ms.date: 07/21/2025 +--- + +# Interaction Service (Preview) + +The interaction service (`Aspire.Hosting.IInteractionService`) allows you to prompt users for input, request confirmation, and display messages. The interaction service works in two different contexts: + +- **Aspire dashboard**: When running `aspire run` or launching the app host directly, interactions appear as dialogs and notifications in the dashboard UI. +- **Aspire CLI**: When running `aspire publish` or `aspire deploy`, interactions are prompted through the command-line interface. + +This is useful for scenarios where you need to gather information from the user or provide feedback on the status of operations, regardless of how the application is being launched or deployed. + +## The `IInteractionService` API + +The `IInteractionService` interface is retrieved from the dependency injection container. `IInteractionService` can be injected into types created from DI or resolved from , which is usually available on a context argument passed to events. + +When you request `IInteractionService`, be sure to check if it's available for usage. If you attempt to use the interaction service when it's not available (`IInteractionService.IsAvailable` returns `false`), an exception is thrown. + +```csharp +var interactionService = serviceProvider.GetRequiredService(); +if (interactionService.IsAvailable) +{ + var result = await interactionService.PromptConfirmationAsync( + title: "Delete confirmation", + message: "Are you sure you want to delete the data?"); + + if (result.Data) + { + // Run your resource/command logic. + } +} +``` + +The interaction service has several methods that you use to interact with users or display messages. The behavior of these methods depends on the execution context: + +- **Dashboard context** (`aspire run` or direct app host launch): Interactions appear as modal dialogs, notifications, and form inputs in the [Aspire dashboard web interface](../fundamentals/dashboard/overview.md). +- **CLI context** (`aspire publish` or `aspire deploy`): Interactions are prompted through the command-line interface with text-based prompts and responses. + +The following sections describe how to use these APIs effectively in both contexts: + +| Method | Description | Contexts supported | +|--|--|--| +| `PromptMessageBoxAsync` | Displays a modal dialog box with a message and buttons for user interaction. | Dashboard only | +| `PromptNotificationAsync` | Displays a non-modal notification in the dashboard as a message bar. | Dashboard only | +| `PromptConfirmationAsync` | Displays a confirmation dialog with options for the user to confirm or cancel an action. | Dashboard only | +| `PromptInputAsync` | Prompts the user for a single input value, such as text or secret. | Dashboard, CLI | +| `PromptInputsAsync` | Prompts the user for multiple input values in a single dialog (dashboard) or sequentially (CLI). | Dashboard, CLI | + +> [!IMPORTANT] +> During `aspire publish` and `aspire deploy` operations, only `PromptInputAsync` and `PromptInputsAsync` are available. Other interaction methods (`PromptMessageBoxAsync`, `PromptNotificationAsync`, and `PromptConfirmationAsync`) will throw an exception if called in CLI contexts. + +## Where to use the interaction service + +Any of the available callback-based extension methods of `IResourceBuilder` can use the interaction service to prompt users for input or confirmation. Use the interaction service in these scenarios: + +- **Custom resource types**: Gather input from users or confirm actions when you create custom resource types. + + Resource types are free to define dashboard interactions, such as prompting for user input or displaying messages. The interaction service allows you to create a more interactive experience for users when they manage resources in the Aspire dashboard or CLI. For more information, see [Create custom .NET Aspire hosting integrations](custom-hosting-integration.md). + +- **Custom resource commands**: Add commands to resources in the Aspire dashboard or CLI. Use the interaction service to prompt users for input or confirmation when these commands run. + + When you chain a call to on a target `IResourceBuilder`, for example, your callback can use the interaction service to gather input or confirm actions. For more information, see [Custom resource commands in .NET Aspire](../fundamentals/custom-resource-commands.md). + +- **Publish and deploy workflows**: During `aspire publish` or `aspire deploy` operations, use the interaction service to gather deployment-specific configuration and confirm destructive operations through the CLI. + +These approaches help you create interactive, user-friendly experiences for local development, dashboard interactions, and deployment workflows. + +> [!IMPORTANT] +> This article demonstrates the interaction service in the context of a `WithCommand` callback with a `FakeResource` type, but the same principles apply to other extension methods that support user interactions. +> +> For example: +> +> ```csharp +> var builder = DistributedApplication.CreateBuilder(args); +> +> builder.AddFakeResource("fake-resource") +> .WithCommand("msg-dialog", "Example Message Dialog", async context => +> { +> var interactionService = context.GetRequiredService(); +> +> // Use interaction service... +> +> return CommandResults.Success(); +> }); +> ``` +> +> For CLI specific contexts, the interaction service is retrieved from either the `PublishingContext` or `DeploymentContext` depending on the operation being performed. + +## Display messages + +There are several ways to display messages to the user: + +- **Dialog messages**: Show important information in a dialog box. +- **Notification messages**: Display less critical information in a notification. + +> [!NOTE] +> Message display methods (`PromptMessageBoxAsync` and `PromptNotificationAsync`) are only available in dashboard contexts. These methods throw an exception if called during `aspire publish` or `aspire deploy` operations. + +### Display a dialog message box + +Dialog messages provide important information that requires user attention. + + + +The `IInteractionService.PromptMessageBoxAsync` method displays a message with customizable response options. + +:::code source="snippets/InteractionService/AppHost.MessageBoxExample.cs" id="example"::: + +**Dashboard view:** + +:::image type="content" source="media/interaction-service-message-dialog.png" lightbox="media/interaction-service-message-dialog.png" alt-text="Aspire dashboard interface showing a message dialog with a title, message, and buttons."::: + +**CLI view:** + +The `PromptMessageBoxAsync` method only works in the dashboard context. If you call it during `aspire publish` or `aspire deploy`, the method throws an exception and doesn't display a message in the CLI. + +### Display a notification message + +Notification messages provide non-modal notifications. + +> [!TIP] +> In the dashboard, notification messages appear stacked at the top, so you can show several messages at once. You can display notifications one after another by awaiting each dismissal before showing the next. Or, you can display multiple notifications at the same time without waiting for each to be dismissed. + + + +The `IInteractionService.PromptNotificationAsync` method displays informational messages with optional action links in the dashboard context. You don't have to await the result of a notification message if you don't need to. This is especially useful for notifications, since you might want to display a notification and continue without waiting for user to dismiss it. + +:::code source="snippets/InteractionService/AppHost.NotificationExample.cs" id="example"::: + +The previous example demonstrates several ways to use the notification API. Each approach displays different types of notifications, all of which are invoked in parallel and displayed in the dashboard around the same time. + +**Dashboard view:** + +:::image type="content" source="media/interaction-service-message-bar.png" lightbox="media/interaction-service-message-bar.png" alt-text="Aspire dashboard interface showing multiple notification messages stacked near the top of the page. There are five notifications displayed, each with a different type of notification."::: + +**CLI view:** + +The `PromptNotificationAsync` method isn't available in CLI contexts. If you call it during `aspire publish` or `aspire deploy`, it throws an exception. + +## Prompt for user confirmation + + + +Use the interaction service when you need the user to confirm an action before proceeding. The `IInteractionService.PromptConfirmationAsync` method displays a confirmation prompt in the dashboard context. Confirmation prompts are essential for destructive operations or actions that have significant consequences. They help prevent accidental actions and give users a chance to reconsider their decisions. + +### Prompt for confirmation before destructive operations + +For operations that can't be undone, such as deleting resources, always prompt for confirmation: + +:::code source="snippets/InteractionService/AppHost.ConfirmationExample.cs" id="example"::: + +**Dashboard view:** + +:::image type="content" source="media/interaction-service-confirmation.png" lightbox="media/interaction-service-confirmation.png" alt-text="Aspire dashboard interface showing a confirmation dialog with a title, message, and buttons for confirming or canceling the operation."::: + +**CLI view:** + +The `PromptConfirmationAsync` method isn't available in CLI contexts. If you call it during `aspire publish` or `aspire deploy`, the method throws an exception. + +## Prompt for user input + +The interaction service API allows you to prompt users for input in various ways. You can collect single values or multiple values, with support for different input types including text, secrets, choices, booleans, and numbers. The presentation adapts automatically to the execution context. + +| Type | Dashboard | CLI prompt | +|--------------|---------------------------|-----------------------| +| `Text` | Textbox | Text prompt | +| `SecretText` | Textbox with masked input | Masked text prompt | +| `Choice` | Dropdown box of options | Choice prompt | +| `Boolean` | Checkbox | Boolean choice prompt | +| `Number` | Number textbox | Text prompt | + +### Prompt the user for input values + + + +You can prompt for a single value using the `IInteractionService.PromptInputAsync` method, or collect multiple pieces of information with `IInteractionService.PromptInputsAsync`. In the dashboard, multiple inputs appear together in a single dialog. In the CLI, each input is requested one after another. + +> [!IMPORTANT] +> It's possible to create wizard-like flows using the interaction service. By chaining multiple prompts together—handling the results from one prompt before moving to the next—you can guide users through a series of related questions, making it easier to collect all the necessary information. + +Consider the following example, which prompts the user for multiple input values: + +:::code source="snippets/InteractionService/AppHost.MultipleInputExample.cs" id="example"::: + +**Dashboard view:** + +This renders on the dashboard as shown in the following image: + +:::image type="content" source="media/interaction-service-multiple-input.png" lightbox="media/interaction-service-multiple-input.png" alt-text="Aspire dashboard interface showing a multiple input dialog with labels, input fields, and buttons for confirming or canceling the input."::: + +Imagine you fill out the dialog with the following values: + +:::image type="content" source="media/interaction-service-multiple-input-filled.png" lightbox="media/interaction-service-multiple-input-filled.png" alt-text="Aspire dashboard interface showing a multiple input dialog with filled input fields and buttons for confirming or canceling the input."::: + +After you select the **Ok** button, the resource logs display the following output: + +:::image type="content" source="media/interaction-service-multiple-input-logs.png" lightbox="media/interaction-service-multiple-input-logs.png" alt-text="Aspire dashboard interface showing logs with the input values entered in the multiple input dialog."::: + +**CLI view:** + +In the CLI context, the same inputs are requested sequentially: + +```Aspire +aspire deploy + +Step 1: Analyzing model. + + ✓ DONE: Analyzing the distributed application model for publishing and deployment capabilities. 00:00:00 + Found 1 resources that support deployment. (FakeResource) + +✅ COMPLETED: Analyzing model. completed successfully + +═══════════════════════════════════════════════════════════════════════════════════════════════════════════════ + +Configure your application deployment settings: +Application Name: example-app +Environment: Staging +Instance Count: 3 +Enable Monitoring: [y/n] (n): y +✓ DEPLOY COMPLETED: Deploying completed successfully +``` + +Depending on the input type, the CLI might display additional prompts. For example, the `Enable Monitoring` input is a boolean choice, so the CLI prompts for a yes/no response. If you enter `y`, it enables monitoring; if you enter `n`, it disables monitoring. For the environment input, the CLI displays a list of available environments for selection: + +```Aspire +Configure your application deployment settings: +Application Name: example-app +Environment: + +> Development + Staging + Testing + +(Type to search) +``` + +#### Input validation + + + +Basic input validation is available by configuring `InteractionInput`. It provides options for requiring a value, or the maximum text length of `Text` or `SecretText` fields. + +For complex scenarios, you can provide custom validation logic using the `InputsDialogInteractionOptions.ValidationCallback` property: + +```csharp +// Multiple inputs with custom validation +var databaseInputs = new List +{ + new() + { + Label = "Database Name", + InputType = InputType.Text, + Required = true, + Placeholder = "myapp-db" + }, + new() + { + Label = "Username", + InputType = InputType.Text, + Required = true, + Placeholder = "admin" + }, + new() + { + Label = "Password", + InputType = InputType.SecretText, + Required = true, + Placeholder = "Enter a strong password" + }, + new() + { + Label = "Confirm password", + InputType = InputType.SecretText, + Required = true, + Placeholder = "Confirm your password" + } +}; + +var validationOptions = new InputsDialogInteractionOptions +{ + ValidationCallback = async context => + { + var passwordInput = context.Inputs.FirstOrDefault(i => i.Label == "Password"); + var confirmPasswordInput = context.Inputs.FirstOrDefault(i => i.Label == "Confirm password"); + + // Validate password strength + if (passwordInput?.Value is { Length: < 8 }) + { + context.AddValidationError(passwordInput, "Password must be at least 8 characters long"); + } + + // Validate password confirmation + if (passwordInput?.Value != confirmPasswordInput?.Value) + { + context.AddValidationError(confirmPasswordInput!, "Passwords do not match"); + } + + await Task.CompletedTask; + } +}; + +var dbResult = await interactionService.PromptInputsAsync( + title: "Database configuration", + message: "Configure your PostgreSQL database connection:", + inputs: databaseInputs, + options: validationOptions); + +if (!dbResult.Canceled && dbResult.Data != null) +{ + // Use the validated configuration +} +``` + +Prompting the user for a password and confirming they match is referred to as "dual independent verification" input. This approach is common in scenarios where you want to ensure the user enters the same password twice to avoid typos or mismatches. + +### Best practices for user input + +When prompting for user input, consider these best practices: + +1. **Group related inputs**: Use multiple input prompts to collect related configuration values. In the dashboard, these appear in a single dialog; in the CLI, they're requested sequentially but grouped conceptually. +1. **Provide clear labels and placeholders**: Help users understand what information is expected, regardless of context. +1. **Use appropriate input types**: Choose the right input type for the data you're collecting (secret for passwords, choice for predefined options, etc.). Both contexts support these input types appropriately. +1. **Implement validation**: Validate user input and provide clear error messages when validation fails. Both contexts support validation feedback. +1. **Make required fields clear**: Mark required fields and provide appropriate defaults for optional ones. +1. **Handle cancellation**: Always check if the user canceled the input prompt and handle it gracefully. Users can cancel in both dashboard and CLI contexts. + +## Interaction contexts + +The interaction service behaves differently depending on how your Aspire solution is launched: + +### Dashboard context + +When you run your application using `aspire run` or by directly launching the app host project, interactions appear in the Aspire dashboard web interface: + +- **Modal dialogs**: Message boxes and input prompts appear as overlay dialogs that require user interaction. +- **Notification messages**: Informational messages appear as dismissible banners at the top of the dashboard. +- **Rich UI**: Full support for interactive form elements, validation, and visual feedback. +- **All methods available**: All interaction service methods are supported in dashboard contexts. + +### CLI context + +When you run `aspire publish` or `aspire deploy`, interactions are prompted through the command-line interface: + +- **Text prompts**: Only input prompts (`PromptInputAsync` and `PromptInputsAsync`) are available and appear as text-based prompts in the terminal. +- **Sequential input**: Multiple inputs are requested one at a time rather than in a single dialog. +- **Limited functionality**: Message boxes, notifications, and confirmation dialogs aren't available and throws exceptions if called. + +> [!IMPORTANT] +> The interaction service adapts automatically to dashboard and CLI contexts. In CLI mode, only input-related methods—`PromptInputAsync` and `PromptInputsAsync`—are supported. Calling `PromptMessageBoxAsync`, `PromptNotificationAsync`, or `PromptConfirmationAsync` in CLI operations like `aspire publish` or `aspire deploy` results in an exception. + +## See also + + + +- [Aspire.Hosting.IInteractionService](https://github.com/dotnet/aspire/blob/main/src/Aspire.Hosting/IInteractionService.cs) +- [.NET Aspire extensibility overview](../extensibility/custom-hosting-integration.md) diff --git a/docs/extensibility/media/interaction-service-confirmation.png b/docs/extensibility/media/interaction-service-confirmation.png new file mode 100644 index 0000000000..dc1cd87bbc Binary files /dev/null and b/docs/extensibility/media/interaction-service-confirmation.png differ diff --git a/docs/extensibility/media/interaction-service-message-bar.png b/docs/extensibility/media/interaction-service-message-bar.png new file mode 100644 index 0000000000..e67ddd8acd Binary files /dev/null and b/docs/extensibility/media/interaction-service-message-bar.png differ diff --git a/docs/extensibility/media/interaction-service-message-dialog.png b/docs/extensibility/media/interaction-service-message-dialog.png new file mode 100644 index 0000000000..8a12ff0035 Binary files /dev/null and b/docs/extensibility/media/interaction-service-message-dialog.png differ diff --git a/docs/extensibility/media/interaction-service-multiple-input-filled.png b/docs/extensibility/media/interaction-service-multiple-input-filled.png new file mode 100644 index 0000000000..59c1e83e2f Binary files /dev/null and b/docs/extensibility/media/interaction-service-multiple-input-filled.png differ diff --git a/docs/extensibility/media/interaction-service-multiple-input-logs.png b/docs/extensibility/media/interaction-service-multiple-input-logs.png new file mode 100644 index 0000000000..8594fcfbe0 Binary files /dev/null and b/docs/extensibility/media/interaction-service-multiple-input-logs.png differ diff --git a/docs/extensibility/media/interaction-service-multiple-input.png b/docs/extensibility/media/interaction-service-multiple-input.png new file mode 100644 index 0000000000..3cf1ed340a Binary files /dev/null and b/docs/extensibility/media/interaction-service-multiple-input.png differ diff --git a/docs/extensibility/snippets/InteractionService/AppHost.ConfirmationExample.cs b/docs/extensibility/snippets/InteractionService/AppHost.ConfirmationExample.cs new file mode 100644 index 0000000000..2dd97211b3 --- /dev/null +++ b/docs/extensibility/snippets/InteractionService/AppHost.ConfirmationExample.cs @@ -0,0 +1,35 @@ +using Microsoft.Extensions.DependencyInjection; + +partial class Program +{ + public static async Task ShowConfirmationExample(ExecuteCommandContext context) + { + // + var interactionService = context.ServiceProvider.GetRequiredService(); + + // Prompt for confirmation before resetting database + var resetConfirmation = await interactionService.PromptConfirmationAsync( + title: "Confirm Reset", + message: "Are you sure you want to reset the `development-database`? This action **cannot** be undone.", + options: new MessageBoxInteractionOptions + { + Intent = MessageIntent.Confirmation, + PrimaryButtonText = "Reset", + SecondaryButtonText = "Cancel", + ShowSecondaryButton = true, + EnableMessageMarkdown = true + }); + + if (resetConfirmation.Data is true) + { + // Perform the reset operation... + + return CommandResults.Success(); + } + else + { + return CommandResults.Failure("Database reset canceled by user."); + } + // + } +} diff --git a/docs/extensibility/snippets/InteractionService/AppHost.MessageBoxExample.cs b/docs/extensibility/snippets/InteractionService/AppHost.MessageBoxExample.cs new file mode 100644 index 0000000000..e95a1200e3 --- /dev/null +++ b/docs/extensibility/snippets/InteractionService/AppHost.MessageBoxExample.cs @@ -0,0 +1,46 @@ +using Microsoft.Extensions.DependencyInjection; + +partial class Program +{ + public static async Task ShowMessageBoxExample(ExecuteCommandContext context) + { + // + var interactionService = context.ServiceProvider.GetRequiredService(); + + var result = await interactionService.PromptMessageBoxAsync( + "Simple Message Box: Example", + """ + ##### 🤓 Nice! + + It's worth noting that **Markdown** is _supported_ + (and demonstrated here) in the message body. Simply + configure the options as: + + ```csharp + var options = new MessageBoxInteractionOptions + { + EnableMessageMarkdown = true, + // Other options... + }; + ``` + + Cool, [📖 learn more](https://learn.microsoft.com/dotnet/aspire/extensibility/interaction-service)... + """, + new MessageBoxInteractionOptions + { + EnableMessageMarkdown = true, + PrimaryButtonText = "Awesome" + } + ); + + if (result.Canceled) + { + return CommandResults.Failure("User cancelled."); + } + + return result.Data + ? CommandResults.Success() + : CommandResults.Failure("The user doesn't like the example"); + // + } +} diff --git a/docs/extensibility/snippets/InteractionService/AppHost.MultipleInputExample.cs b/docs/extensibility/snippets/InteractionService/AppHost.MultipleInputExample.cs new file mode 100644 index 0000000000..ca68b4fa7f --- /dev/null +++ b/docs/extensibility/snippets/InteractionService/AppHost.MultipleInputExample.cs @@ -0,0 +1,80 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +partial class Program +{ + public static async Task ShowMultipleInputExample( + ExecuteCommandContext context, FakeResource fakeResource) + { + // + var interactionService = context.ServiceProvider.GetRequiredService(); + var loggerService = context.ServiceProvider.GetRequiredService(); + var logger = loggerService.GetLogger(fakeResource); + + var inputs = new List + { + new() + { + Label = "Application Name", + InputType = InputType.Text, + Required = true, + Placeholder = "my-app" + }, + new() + { + Label = "Environment", + InputType = InputType.Choice, + Required = true, + Options = + [ + new("dev", "Development"), + new("staging", "Staging"), + new("test", "Testing") + ] + }, + new() + { + Label = "Instance Count", + InputType = InputType.Number, + Required = true, + Placeholder = "1" + }, + new() + { + Label = "Enable Monitoring", + InputType = InputType.Boolean, + Required = false + } + }; + + var appConfigurationInput = await interactionService.PromptInputsAsync( + title: "Application Configuration", + message: "Configure your application deployment settings:", + inputs: inputs); + + if (!appConfigurationInput.Canceled) + { + // Process the collected input values + var appName = appConfigurationInput.Data[0].Value; + var environment = appConfigurationInput.Data[1].Value; + var instanceCount = int.Parse(appConfigurationInput.Data[2].Value ?? "1"); + var enableMonitoring = bool.Parse(appConfigurationInput.Data[3].Value ?? "false"); + + logger.LogInformation(""" + Application Name: {AppName} + Environment: {Environment} + Instance Count: {InstanceCount} + Monitoring Enabled: {EnableMonitoring} + """, + appName, environment, instanceCount, enableMonitoring); + + // Use the collected values as needed + return CommandResults.Success(); + } + else + { + return CommandResults.Failure("User canceled application configuration input."); + } + // + } +} diff --git a/docs/extensibility/snippets/InteractionService/AppHost.NotificationExample.cs b/docs/extensibility/snippets/InteractionService/AppHost.NotificationExample.cs new file mode 100644 index 0000000000..77003071b8 --- /dev/null +++ b/docs/extensibility/snippets/InteractionService/AppHost.NotificationExample.cs @@ -0,0 +1,63 @@ +using Microsoft.Extensions.DependencyInjection; + +partial class Program +{ + public static async Task ShowNotificationExample(ExecuteCommandContext context) + { + // + var interactionService = context.ServiceProvider.GetRequiredService(); + + // Demonstrating various notification types with different intents + var tasks = new List + { + interactionService.PromptNotificationAsync( + title: "Confirmation", + message: "Are you sure you want to proceed?", + options: new NotificationInteractionOptions + { + Intent = MessageIntent.Confirmation + }), + interactionService.PromptNotificationAsync( + title: "Success", + message: "Your operation completed successfully.", + options: new NotificationInteractionOptions + { + Intent = MessageIntent.Success, + LinkText = "View Details", + LinkUrl = "https://learn.microsoft.com/dotnet/aspire/success" + }), + interactionService.PromptNotificationAsync( + title: "Warning", + message: "Your SSL certificate will expire soon.", + options: new NotificationInteractionOptions + { + Intent = MessageIntent.Warning, + LinkText = "Renew Certificate", + LinkUrl = "https://portal.azure.com/certificates" + }), + interactionService.PromptNotificationAsync( + title: "Information", + message: "There is an update available for your application.", + options: new NotificationInteractionOptions + { + Intent = MessageIntent.Information, + LinkText = "Update Now", + LinkUrl = "https://learn.microsoft.com/dotnet/aspire" + }), + interactionService.PromptNotificationAsync( + title: "Error", + message: "An error occurred while processing your request.", + options: new NotificationInteractionOptions + { + Intent = MessageIntent.Error, + LinkText = "Troubleshoot", + LinkUrl = "https://learn.microsoft.com/dotnet/aspire/troubleshoot" + }) + }; + + await Task.WhenAll(tasks); + + return CommandResults.Success(); + // + } +} diff --git a/docs/extensibility/snippets/InteractionService/AppHost.cs b/docs/extensibility/snippets/InteractionService/AppHost.cs new file mode 100644 index 0000000000..e29158f589 --- /dev/null +++ b/docs/extensibility/snippets/InteractionService/AppHost.cs @@ -0,0 +1,64 @@ +using Microsoft.Extensions.DependencyInjection; + +var builder = DistributedApplication.CreateBuilder(args); + +var fakeResource = builder.AddFakeResource("fake-resource-01") + .WithCommand("msg-dialog", "Example Message Dialog", ShowMessageBoxExample) + .WithCommand("msg-bar", "Example Message Bar", ShowNotificationExample) + .WithCommand("confirm", "Confirmation Example", ShowConfirmationExample); + +fakeResource.WithCommand( + "multi-input", + "Multi Input Example", + context => ShowMultipleInputExample(context, fakeResource.Resource)); + + +builder.AddFakeResource("fake-resource-02") + .WithDeployment(async context => + { + var interactionService = context.Services.GetRequiredService(); + var logger = context.Logger; + + var inputs = new List + { + new() + { + Label = "Application Name", + InputType = InputType.Text, + Required = true, + Placeholder = "my-app" + }, + new() + { + Label = "Environment", + InputType = InputType.Choice, + Required = true, + Options = + [ + new("dev", "Development"), + new("staging", "Staging"), + new("test", "Testing") + ] + }, + new() + { + Label = "Instance Count", + InputType = InputType.Number, + Required = true, + Placeholder = "1" + }, + new() + { + Label = "Enable Monitoring", + InputType = InputType.Boolean, + Required = false + } + }; + + var appConfigurationInput = await interactionService.PromptInputsAsync( + title: "Application Configuration", + message: "Configure your application deployment settings:", + inputs: inputs); + }); + +builder.Build().Run(); diff --git a/docs/extensibility/snippets/InteractionService/FakeResource.cs b/docs/extensibility/snippets/InteractionService/FakeResource.cs new file mode 100644 index 0000000000..569bec5e05 --- /dev/null +++ b/docs/extensibility/snippets/InteractionService/FakeResource.cs @@ -0,0 +1,6 @@ +public sealed class FakeResource(string name) : IResource +{ + string IResource.Name => name; + + ResourceAnnotationCollection IResource.Annotations { get; } = []; +} diff --git a/docs/extensibility/snippets/InteractionService/FakeResourceExtensions.cs b/docs/extensibility/snippets/InteractionService/FakeResourceExtensions.cs new file mode 100644 index 0000000000..c5e021f457 --- /dev/null +++ b/docs/extensibility/snippets/InteractionService/FakeResourceExtensions.cs @@ -0,0 +1,32 @@ +namespace Aspire.Hosting; + +public static class FakeResourceExtensions +{ + public static IResourceBuilder AddFakeResource( + this IDistributedApplicationBuilder builder, + [ResourceName] string name) + { + var fakeResource = new FakeResource(name); + + return builder.AddResource(fakeResource) + .WithInitialState(new() + { + ResourceType = "Fake Resource", + State = KnownResourceStates.Running, + Properties = + [ + new(CustomResourceKnownProperties.Source, "Fake") + ] + }) + .ExcludeFromManifest(); + } + + public static IResourceBuilder WithDeployment( + this IResourceBuilder builder, + Func callback) + { + builder.WithAnnotation(new DeployingCallbackAnnotation(callback)); + + return builder; + } +} \ No newline at end of file diff --git a/docs/extensibility/snippets/InteractionService/InteractionService.AppHost.csproj b/docs/extensibility/snippets/InteractionService/InteractionService.AppHost.csproj new file mode 100644 index 0000000000..f1fc924424 --- /dev/null +++ b/docs/extensibility/snippets/InteractionService/InteractionService.AppHost.csproj @@ -0,0 +1,17 @@ + + + + + + net9.0 + enable + enable + Exe + $(NoWarn);ASPIREINTERACTION001;ASPIREPUBLISHERS001 + + + + + + + diff --git a/docs/extensibility/snippets/InteractionService/InteractionService.slnx b/docs/extensibility/snippets/InteractionService/InteractionService.slnx new file mode 100644 index 0000000000..d364a11967 --- /dev/null +++ b/docs/extensibility/snippets/InteractionService/InteractionService.slnx @@ -0,0 +1,3 @@ + + + diff --git a/docs/extensibility/snippets/InteractionService/Properties/launchSettings.json b/docs/extensibility/snippets/InteractionService/Properties/launchSettings.json new file mode 100644 index 0000000000..701d4b730e --- /dev/null +++ b/docs/extensibility/snippets/InteractionService/Properties/launchSettings.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:17251;http://localhost:15199", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21161", + "ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22175" + } + }, + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:15199", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19277", + "ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20014" + } + } + } +} diff --git a/docs/extensibility/snippets/MailDevResource/MailDev.Hosting/MailDev.Hosting.csproj b/docs/extensibility/snippets/MailDevResource/MailDev.Hosting/MailDev.Hosting.csproj index 52e5f84b93..4e16d6f0e6 100644 --- a/docs/extensibility/snippets/MailDevResource/MailDev.Hosting/MailDev.Hosting.csproj +++ b/docs/extensibility/snippets/MailDevResource/MailDev.Hosting/MailDev.Hosting.csproj @@ -7,7 +7,7 @@ - + diff --git a/docs/extensibility/snippets/MailDevResource/MailDevResource.AppHost/MailDevResource.AppHost.csproj b/docs/extensibility/snippets/MailDevResource/MailDevResource.AppHost/MailDevResource.AppHost.csproj index d4dd7e799b..cca192e93a 100644 --- a/docs/extensibility/snippets/MailDevResource/MailDevResource.AppHost/MailDevResource.AppHost.csproj +++ b/docs/extensibility/snippets/MailDevResource/MailDevResource.AppHost/MailDevResource.AppHost.csproj @@ -1,5 +1,5 @@ - + Exe net9.0 @@ -8,7 +8,7 @@ 9c9bfb14-6706-4421-bc93-37cbaebe36d0 - + diff --git a/docs/extensibility/snippets/MailDevResource/MailDevResource.ServiceDefaults/MailDevResource.ServiceDefaults.csproj b/docs/extensibility/snippets/MailDevResource/MailDevResource.ServiceDefaults/MailDevResource.ServiceDefaults.csproj index ae37426e09..9183e963ee 100644 --- a/docs/extensibility/snippets/MailDevResource/MailDevResource.ServiceDefaults/MailDevResource.ServiceDefaults.csproj +++ b/docs/extensibility/snippets/MailDevResource/MailDevResource.ServiceDefaults/MailDevResource.ServiceDefaults.csproj @@ -11,7 +11,7 @@ - + diff --git a/docs/extensibility/snippets/MailDevResourceAndComponent/MailDev.Hosting/MailDev.Hosting.csproj b/docs/extensibility/snippets/MailDevResourceAndComponent/MailDev.Hosting/MailDev.Hosting.csproj index 52e5f84b93..4e16d6f0e6 100644 --- a/docs/extensibility/snippets/MailDevResourceAndComponent/MailDev.Hosting/MailDev.Hosting.csproj +++ b/docs/extensibility/snippets/MailDevResourceAndComponent/MailDev.Hosting/MailDev.Hosting.csproj @@ -7,7 +7,7 @@ - + diff --git a/docs/extensibility/snippets/MailDevResourceAndComponent/MailDevResource.AppHost/MailDevResource.AppHost.csproj b/docs/extensibility/snippets/MailDevResourceAndComponent/MailDevResource.AppHost/MailDevResource.AppHost.csproj index d4dd7e799b..cca192e93a 100644 --- a/docs/extensibility/snippets/MailDevResourceAndComponent/MailDevResource.AppHost/MailDevResource.AppHost.csproj +++ b/docs/extensibility/snippets/MailDevResourceAndComponent/MailDevResource.AppHost/MailDevResource.AppHost.csproj @@ -1,5 +1,5 @@ - + Exe net9.0 @@ -8,7 +8,7 @@ 9c9bfb14-6706-4421-bc93-37cbaebe36d0 - + diff --git a/docs/extensibility/snippets/MailDevResourceAndComponent/MailDevResource.ServiceDefaults/MailDevResource.ServiceDefaults.csproj b/docs/extensibility/snippets/MailDevResourceAndComponent/MailDevResource.ServiceDefaults/MailDevResource.ServiceDefaults.csproj index ae37426e09..9183e963ee 100644 --- a/docs/extensibility/snippets/MailDevResourceAndComponent/MailDevResource.ServiceDefaults/MailDevResource.ServiceDefaults.csproj +++ b/docs/extensibility/snippets/MailDevResourceAndComponent/MailDevResource.ServiceDefaults/MailDevResource.ServiceDefaults.csproj @@ -11,7 +11,7 @@ - + diff --git a/docs/extensibility/snippets/MailDevResourceWithCredentials/MailDev.Hosting/MailDev.Hosting.csproj b/docs/extensibility/snippets/MailDevResourceWithCredentials/MailDev.Hosting/MailDev.Hosting.csproj index 52e5f84b93..4e16d6f0e6 100644 --- a/docs/extensibility/snippets/MailDevResourceWithCredentials/MailDev.Hosting/MailDev.Hosting.csproj +++ b/docs/extensibility/snippets/MailDevResourceWithCredentials/MailDev.Hosting/MailDev.Hosting.csproj @@ -7,7 +7,7 @@ - + diff --git a/docs/extensibility/snippets/MailDevResourceWithCredentials/MailDevResource.AppHost/MailDevResource.AppHost.csproj b/docs/extensibility/snippets/MailDevResourceWithCredentials/MailDevResource.AppHost/MailDevResource.AppHost.csproj index d4dd7e799b..cca192e93a 100644 --- a/docs/extensibility/snippets/MailDevResourceWithCredentials/MailDevResource.AppHost/MailDevResource.AppHost.csproj +++ b/docs/extensibility/snippets/MailDevResourceWithCredentials/MailDevResource.AppHost/MailDevResource.AppHost.csproj @@ -1,5 +1,5 @@ - + Exe net9.0 @@ -8,7 +8,7 @@ 9c9bfb14-6706-4421-bc93-37cbaebe36d0 - + diff --git a/docs/extensibility/snippets/MailDevResourceWithCredentials/MailDevResource.ServiceDefaults/MailDevResource.ServiceDefaults.csproj b/docs/extensibility/snippets/MailDevResourceWithCredentials/MailDevResource.ServiceDefaults/MailDevResource.ServiceDefaults.csproj index ae37426e09..9183e963ee 100644 --- a/docs/extensibility/snippets/MailDevResourceWithCredentials/MailDevResource.ServiceDefaults/MailDevResource.ServiceDefaults.csproj +++ b/docs/extensibility/snippets/MailDevResourceWithCredentials/MailDevResource.ServiceDefaults/MailDevResource.ServiceDefaults.csproj @@ -11,7 +11,7 @@ - + diff --git a/docs/frameworks/snippets/Orleans/OrleansAppHost/OrleansAppHost.csproj b/docs/frameworks/snippets/Orleans/OrleansAppHost/OrleansAppHost.csproj index acecd09c93..e23ee66234 100644 --- a/docs/frameworks/snippets/Orleans/OrleansAppHost/OrleansAppHost.csproj +++ b/docs/frameworks/snippets/Orleans/OrleansAppHost/OrleansAppHost.csproj @@ -1,5 +1,5 @@ - + Exe net9.0 @@ -8,10 +8,10 @@ 88427062-d086-46c2-b35e-171d742a6fe0 - - - - + + + + diff --git a/docs/frameworks/snippets/Orleans/OrleansClient/OrleansClient.csproj b/docs/frameworks/snippets/Orleans/OrleansClient/OrleansClient.csproj index 3e885bf63a..2a7e42be7e 100644 --- a/docs/frameworks/snippets/Orleans/OrleansClient/OrleansClient.csproj +++ b/docs/frameworks/snippets/Orleans/OrleansClient/OrleansClient.csproj @@ -11,7 +11,7 @@ - + diff --git a/docs/frameworks/snippets/Orleans/OrleansServer/OrleansServer.csproj b/docs/frameworks/snippets/Orleans/OrleansServer/OrleansServer.csproj index 84705695e2..c73b4f12a8 100644 --- a/docs/frameworks/snippets/Orleans/OrleansServer/OrleansServer.csproj +++ b/docs/frameworks/snippets/Orleans/OrleansServer/OrleansServer.csproj @@ -14,8 +14,8 @@ - - + + diff --git a/docs/frameworks/snippets/Orleans/OrleansServiceDefaults/OrleansServiceDefaults.csproj b/docs/frameworks/snippets/Orleans/OrleansServiceDefaults/OrleansServiceDefaults.csproj index 7b2f75af5f..0915aa45d5 100644 --- a/docs/frameworks/snippets/Orleans/OrleansServiceDefaults/OrleansServiceDefaults.csproj +++ b/docs/frameworks/snippets/Orleans/OrleansServiceDefaults/OrleansServiceDefaults.csproj @@ -15,7 +15,7 @@ - + diff --git a/docs/fundamentals/annotations-overview.md b/docs/fundamentals/annotations-overview.md index 5e60324215..3c00f5482f 100644 --- a/docs/fundamentals/annotations-overview.md +++ b/docs/fundamentals/annotations-overview.md @@ -1,7 +1,7 @@ --- title: Resource annotations description: Learn about annotations in .NET Aspire, how they work, and how to create custom annotations for extending resource behavior. -ms.date: 07/16/2025 +ms.date: 07/25/2025 --- # Resource annotations in .NET Aspire @@ -43,7 +43,7 @@ In this example: ## Built-in annotation types -.NET Aspire includes many built-in annotation types for common scenarios. This section covers some of the more commonly used annotations, but there are many more available for specific use cases. +.NET Aspire includes many built-in annotation types for common scenarios. This section covers _some_ of the more commonly used annotations, but there are [_many more_](xref:Aspire.Hosting.ApplicationModel.IResourceAnnotation) available for specific use cases. ### `EndpointAnnotation` @@ -121,6 +121,8 @@ api.Resource.Annotations.Add( ); ``` +For more information about publishing and deploying Aspire apps, see [publishing and deploying](../deployment/overview.md). + ## Creating custom annotations Custom annotations in .NET Aspire are designed to capture resource-specific metadata and behavior that can be leveraged throughout the application lifecycle. They're commonly used by: diff --git a/docs/fundamentals/app-host-overview.md b/docs/fundamentals/app-host-overview.md index 59a7b3e99d..78242fa0de 100644 --- a/docs/fundamentals/app-host-overview.md +++ b/docs/fundamentals/app-host-overview.md @@ -48,7 +48,7 @@ The app host project handles running all of the projects that are part of the .N ```xml - + Exe @@ -57,7 +57,7 @@ The app host project handles running all of the projects that are part of the .N - + diff --git a/docs/fundamentals/build-container-images.md b/docs/fundamentals/build-container-images.md new file mode 100644 index 0000000000..1b0f74e904 --- /dev/null +++ b/docs/fundamentals/build-container-images.md @@ -0,0 +1,186 @@ +--- +title: Build container images +description: Learn how to build container images from your .NET Aspire resources. +ms.date: 07/25/2025 +ai-usage: ai-assisted +--- + +# Build container images (Preview) + +.NET Aspire provides powerful APIs for building container images from your resources during publishing and deployment operations. This article covers the key components that enable programmatic container image creation and progress reporting. + +## Overview + +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: + + + +- `IResourceContainerImageBuilder`: The service that turns resource definitions into runnable container images. +- `PublishingActivityProgressReporter`: 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. + +> [!IMPORTANT] +> These APIs are currently in preview and subject to change. They are designed for advanced scenarios where you need custom control over container image building and progress reporting. To suppress warnings for these APIs, see [Compiler Error ASPIREPUBLISHERS001](../diagnostics/aspirepublishers001.md). + +## When to use these APIs + +Consider using the container image building and progress reporting APIs in these scenarios: + +- **Custom deployment targets**: When you need to deploy to platforms that require specific image formats or build configurations. +- **Complex build pipelines**: When your publishing process involves multiple steps that users should see. +- **Enterprise scenarios**: When you need custom progress reporting for integration with CI/CD systems or dashboards. +- **Custom resource types**: When implementing custom resources that need to participate in the publishing and deployment process. + +> [!NOTE] +> For most standard Aspire applications, the built-in publishing process builds container images automatically without requiring these APIs. + +## Resource container image builder API + +The `IResourceContainerImageBuilder` is the core service in the layer that converts resource definitions into container images. It analyzes each resource in your distributed application model and determines whether to: + +- Reuse an existing image. +- Build from a .NET project using `dotnet publish /t:PublishContainer`. +- Build from a Dockerfile using the local container runtime. + +### Container build options + + + +The `ContainerBuildOptions` class provides strongly typed configuration for container builds. This class allows you to specify: + +- **Image format**: Docker or Open Container Initiative (OCI) format. +- **Target platform**: Linux x64, Windows, ARM64, etc. +- **Output path**: Where to save the built images. + +### Container runtime health checks + +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 + +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. + +### API overview and behavior + +The progress reporter uses a hierarchical model with guaranteed ordering and thread-safe operations: + +| 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. | +| **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. | + +### API structure and usage + +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`. +- **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. +- **Cancellation**: All APIs accept and propagate cancellation to the CLI. +- **Disposal contract**: Disposing steps automatically completes them if unfinished, preventing orphaned phases. + +## Example: Build container images and report progress + +To use these APIs, add a `PublishingCallbackAnnotation`, a `DeployingCallbackAnnotation`, or both to a resource in your app model. You can annotate custom (or built-in) resources by adding annotations to the collection. + +As a developer, you can choose to: + +- Use both annotations if your resource needs to do work in both publishing and deployment. For example, build images and generate manifests during publishing, then push images or configure deployment targets during deployment. Publishing always happens before deployment, so you can keep logic for each phase separate. + +- Use only `PublishingCallbackAnnotation` if your resource only needs to do something during publishing. This is common when you just need to build artifacts or images, but don't need to do anything during deployment. + +- Use only `DeployingCallbackAnnotation` if your resource only needs to do something during deployment. This fits cases where you use prebuilt images and just need to deploy or configure them. + +Choose one or more annotations that match your resource's responsibilities to keep your application model clear and maintainable. This separation lets you clearly define logic for each phase, but you can use both the activity reporter and the resource container image builder in either callback as needed. + +### Example resource with annotations + +For example, consider the `ComputeEnvironmentResource` constructor: + +:::code source="snippets/build-container-images/apphost/ComputeEnvironmentResource.cs" id="ctor"::: + +When instantiated, it defines both a publishing and deploying callback annotation. + +Given the example `ComputeEnvironmentResource` () type, imagine you have an extension method that you expose so consumers are able to add the compute environment: + +:::code source="snippets/build-container-images/apphost/ComputeEnvironmentResourceExtensions.cs"::: + +The preceding code: + +- Defines an extension method on the . +- Accepts a `name` for the compute environment resource, protected by the . +- Instantiates a `ComputeEnvironmentResource` given the `name` and adds it to the `builder`. + +### Example AppHost + +In your AppHost, you can add the `ComputeEnvironmentResource` to the application model like this: + +:::code source="snippets/build-container-images/apphost/AppHost.cs"::: + +The preceding code uses the `AddComputeEnvironment` extension method to add the `ComputeEnvironmentResource` to the application model. + +### Publishing callback annotation + +When you add the `ComputeEnvironmentResource`, it registers a `PublishingCallbackAnnotation`. The callback uses the `PublishAsync` method: + +:::code source="snippets/build-container-images/apphost/ComputeEnvironmentResource.cs" id="publish"::: + +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. + +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. + +### Deploying callback annotation + +Like the publishing callback, the deploying callback is registered using the `DeployingCallbackAnnotation` and calls the `DeployAsync` method: + +:::code source="snippets/build-container-images/apphost/ComputeEnvironmentResource.cs" id="deploy"::: + +The preceding code: + +- Simulates deploying workloads to a Kubernetes cluster. +- Uses the `PublishingActivityProgressReporter` 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`. + +## Best practices + +When using these APIs, follow these guidelines: + +### Image building + +- Always specify explicit `ContainerBuildOptions` for production scenarios. +- Consider target platform requirements when building for deployment. +- Use OCI format for maximum compatibility with container registries. +- Handle `InvalidOperationException` when container runtime health checks fail. + +### Progress reporting + +- Encapsulate long-running logical phases in steps rather than emitting raw tasks. +- Keep titles concise (under 60 characters) as the CLI truncates longer strings. +- Call `CompletePublishAsync` exactly once per publishing or deployment operation. +- Treat warnings as recoverable and allow subsequent steps to proceed. +- Treat errors as fatal and fail fast with clear diagnostics. +- Use asynchronous, cancellation-aware operations to avoid blocking event processing. + +### State management + +- Each step and task starts in *Running* state and transitions exactly once to *Completed*, *Warning*, or *Error*. +- Throw an exception when attempting multiple state transitions. +- Leverage the reporter to guarantee ordered events and prevent interleaving. +- Dispose of `IPublishingActivityStep` to automatically complete unfinished steps. + +## See also + +- [Publishing and deployment overview](../deployment/overview.md) +- [Container configuration](../app-host/configuration.md) +- [Dockerfile integration](../app-host/withdockerfile.md) diff --git a/docs/fundamentals/dashboard/automation/aspire-dashboard/Aspire.Dashboard.ScreenCapture/Aspire.Dashboard.ScreenCapture.csproj b/docs/fundamentals/dashboard/automation/aspire-dashboard/Aspire.Dashboard.ScreenCapture/Aspire.Dashboard.ScreenCapture.csproj index 7de49ad7ca..91dd33a031 100644 --- a/docs/fundamentals/dashboard/automation/aspire-dashboard/Aspire.Dashboard.ScreenCapture/Aspire.Dashboard.ScreenCapture.csproj +++ b/docs/fundamentals/dashboard/automation/aspire-dashboard/Aspire.Dashboard.ScreenCapture/Aspire.Dashboard.ScreenCapture.csproj @@ -12,7 +12,7 @@ - + diff --git a/docs/fundamentals/dashboard/automation/aspire-dashboard/AspireSample/AspireSample.AppHost/AspireSample.AppHost.csproj b/docs/fundamentals/dashboard/automation/aspire-dashboard/AspireSample/AspireSample.AppHost/AspireSample.AppHost.csproj index 6c1251ca58..3748cdb9ae 100644 --- a/docs/fundamentals/dashboard/automation/aspire-dashboard/AspireSample/AspireSample.AppHost/AspireSample.AppHost.csproj +++ b/docs/fundamentals/dashboard/automation/aspire-dashboard/AspireSample/AspireSample.AppHost/AspireSample.AppHost.csproj @@ -1,6 +1,6 @@ - + Exe @@ -16,8 +16,8 @@ - - + + diff --git a/docs/fundamentals/dashboard/automation/aspire-dashboard/AspireSample/AspireSample.ServiceDefaults/AspireSample.ServiceDefaults.csproj b/docs/fundamentals/dashboard/automation/aspire-dashboard/AspireSample/AspireSample.ServiceDefaults/AspireSample.ServiceDefaults.csproj index ae37426e09..9183e963ee 100644 --- a/docs/fundamentals/dashboard/automation/aspire-dashboard/AspireSample/AspireSample.ServiceDefaults/AspireSample.ServiceDefaults.csproj +++ b/docs/fundamentals/dashboard/automation/aspire-dashboard/AspireSample/AspireSample.ServiceDefaults/AspireSample.ServiceDefaults.csproj @@ -11,7 +11,7 @@ - + diff --git a/docs/fundamentals/dashboard/automation/aspire-dashboard/AspireSample/AspireSample.Web/AspireSample.Web.csproj b/docs/fundamentals/dashboard/automation/aspire-dashboard/AspireSample/AspireSample.Web/AspireSample.Web.csproj index 958c813289..b5ee8c0011 100644 --- a/docs/fundamentals/dashboard/automation/aspire-dashboard/AspireSample/AspireSample.Web/AspireSample.Web.csproj +++ b/docs/fundamentals/dashboard/automation/aspire-dashboard/AspireSample/AspireSample.Web/AspireSample.Web.csproj @@ -11,7 +11,7 @@ - + diff --git a/docs/fundamentals/dashboard/explore.md b/docs/fundamentals/dashboard/explore.md index 0139d26c3b..c903aa1b5d 100644 --- a/docs/fundamentals/dashboard/explore.md +++ b/docs/fundamentals/dashboard/explore.md @@ -1,8 +1,9 @@ --- title: Explore .NET Aspire dashboard description: Explore the .NET Aspire dashboard features through the .NET Aspire Starter app. -ms.date: 04/07/2025 +ms.date: 07/21/2025 ms.topic: reference +ai-usage: ai-assisted --- # Explore the .NET Aspire dashboard @@ -437,6 +438,22 @@ The following shortcuts are available: - ?: Got to **Help**. - Shift + s: Go to **Settings**. +## Interaction prompts + +Some resources or commands might prompt you for values when using the dashboard. This interactive functionality is powered by the [interaction service](../../extensibility/interaction-service.md), which allows integrations to display notifications or to request input from users when needed. + +For example, Azure resources that are missing required configuration might prompt you for configuration values when the dashboard starts or when you interact with those resources. These prompts help ensure that resources are properly configured and can function correctly within your .NET Aspire application. + +In the dashboard, interaction prompts appear as: + +- Input dialogs for missing configuration values. +- Confirmation dialogs for important actions. +- Notification messages with details about resource status. + +These prompts appear directly in the dashboard interface, making it easy to provide the necessary information without switching to external tools or configuration files. + +For detailed information about using the interaction service API, including examples and CLI support, see [Interaction Service](../../extensibility/interaction-service.md). + ## GitHub Copilot in the dashboard The .NET Aspire dashboard includes GitHub Copilot as your AI debugging assistant when you launch your app from VS Code or Visual Studio with a GitHub account that has a Copilot subscription. Copilot can help you: diff --git a/docs/fundamentals/dashboard/snippets/BrowserTelemetry/BrowserTelemetry.AppHost/BrowserTelemetry.AppHost.csproj b/docs/fundamentals/dashboard/snippets/BrowserTelemetry/BrowserTelemetry.AppHost/BrowserTelemetry.AppHost.csproj index 79dfe89ba4..175252c66e 100644 --- a/docs/fundamentals/dashboard/snippets/BrowserTelemetry/BrowserTelemetry.AppHost/BrowserTelemetry.AppHost.csproj +++ b/docs/fundamentals/dashboard/snippets/BrowserTelemetry/BrowserTelemetry.AppHost/BrowserTelemetry.AppHost.csproj @@ -9,7 +9,7 @@ - + diff --git a/docs/fundamentals/dashboard/snippets/BrowserTelemetry/BrowserTelemetry.ServiceDefaults/BrowserTelemetry.ServiceDefaults.csproj b/docs/fundamentals/dashboard/snippets/BrowserTelemetry/BrowserTelemetry.ServiceDefaults/BrowserTelemetry.ServiceDefaults.csproj index ae37426e09..9183e963ee 100644 --- a/docs/fundamentals/dashboard/snippets/BrowserTelemetry/BrowserTelemetry.ServiceDefaults/BrowserTelemetry.ServiceDefaults.csproj +++ b/docs/fundamentals/dashboard/snippets/BrowserTelemetry/BrowserTelemetry.ServiceDefaults/BrowserTelemetry.ServiceDefaults.csproj @@ -11,7 +11,7 @@ - + diff --git a/docs/fundamentals/dashboard/snippets/DisableAI/AspireApp/AspireApp.AppHost/AspireApp.AppHost.csproj b/docs/fundamentals/dashboard/snippets/DisableAI/AspireApp/AspireApp.AppHost/AspireApp.AppHost.csproj index 12b01b8214..aac2801d95 100644 --- a/docs/fundamentals/dashboard/snippets/DisableAI/AspireApp/AspireApp.AppHost/AspireApp.AppHost.csproj +++ b/docs/fundamentals/dashboard/snippets/DisableAI/AspireApp/AspireApp.AppHost/AspireApp.AppHost.csproj @@ -1,6 +1,6 @@ - + Exe @@ -16,8 +16,8 @@ - - + + diff --git a/docs/fundamentals/dashboard/snippets/DisableAI/AspireApp/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj b/docs/fundamentals/dashboard/snippets/DisableAI/AspireApp/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj index ae37426e09..9183e963ee 100644 --- a/docs/fundamentals/dashboard/snippets/DisableAI/AspireApp/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj +++ b/docs/fundamentals/dashboard/snippets/DisableAI/AspireApp/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj @@ -11,7 +11,7 @@ - + diff --git a/docs/fundamentals/dashboard/snippets/DisableAI/AspireApp/AspireApp.Web/AspireApp.Web.csproj b/docs/fundamentals/dashboard/snippets/DisableAI/AspireApp/AspireApp.Web/AspireApp.Web.csproj index 9349588100..64ac49c577 100644 --- a/docs/fundamentals/dashboard/snippets/DisableAI/AspireApp/AspireApp.Web/AspireApp.Web.csproj +++ b/docs/fundamentals/dashboard/snippets/DisableAI/AspireApp/AspireApp.Web/AspireApp.Web.csproj @@ -11,7 +11,7 @@ - + diff --git a/docs/fundamentals/dotnet-aspire-sdk.md b/docs/fundamentals/dotnet-aspire-sdk.md index 3149ec9694..82d9d7f31b 100644 --- a/docs/fundamentals/dotnet-aspire-sdk.md +++ b/docs/fundamentals/dotnet-aspire-sdk.md @@ -16,7 +16,7 @@ The [📦 Aspire.AppHost.Sdk](https://www.nuget.org/packages/Aspire.AppHost.Sdk) ```xml - + Exe @@ -25,7 +25,7 @@ The [📦 Aspire.AppHost.Sdk](https://www.nuget.org/packages/Aspire.AppHost.Sdk) - + diff --git a/docs/fundamentals/external-parameters.md b/docs/fundamentals/external-parameters.md index 6414cf24ca..d5ea8198ec 100644 --- a/docs/fundamentals/external-parameters.md +++ b/docs/fundamentals/external-parameters.md @@ -11,7 +11,7 @@ Environments provide context for the application to run in. Parameters express t ## Parameter values -Parameter values are read from the `Parameters` section of the app host's configuration and are used to provide values to the app while running locally. When you publish the app, if the value isn't configured you're prompted to provide it. +Parameter values are read from the `Parameters` section of the app host's configuration and are used to provide values to the app while running locally. When you run or publish the app, if the value isn't configured you're prompted to provide it. Consider the following example app host _:::no-loc text="Program.cs":::_ file: @@ -29,7 +29,7 @@ The preceding code adds a parameter named `example-parameter-name` to the app ho ### Configure parameter values -Adding parameters to the builder is only one aspect of the configuration. You must also provide the value for the parameter. The value can be provided in the app host configuration file, set as a user secret, or configured in any [other standard configuration](/dotnet/core/extensions/configuration). When parameter values aren't found, they're prompted for when publishing the app. +Adding parameters to the builder is only one aspect of the configuration. You must also provide the value for the parameter. The value can be provided in the app host configuration file, set as a user secret, or configured in any [other standard configuration](/dotnet/core/extensions/configuration). When parameter values aren't found, they're prompted for when you run or publish the app. Consider the following app host configuration file _:::no-loc text="appsettings.json":::_: @@ -41,7 +41,7 @@ Consider the following app host configuration file _:::no-loc text="appsettings. } ``` -The preceding JSON configures a parameter in the `Parameters` section of the app host configuration. In other words, that app host is able to find the parameter as its configured. For example, you could walk up to the and access the value using the `Parameters:example-parameter-name` key: +The preceding JSON configures a parameter in the `Parameters` section of the app host configuration. In other words, that app host is able to find the parameter as it's configured. For example, you could walk up to the and access the value using the `Parameters:example-parameter-name` key: ```csharp var builder = DistributedApplication.CreateBuilder(args); @@ -53,6 +53,31 @@ var value = builder.Configuration[key]; // value = "local-value" > [!IMPORTANT] > However, you don't need to access this configuration value yourself in the app host. Instead, the is used to pass the parameter value to dependent resources. Most often as an environment variable. +### Prompt for parameter values in the dashboard + +If your code adds parameters but doesn't set them, you'll see a prompt to configure their values in the .NET Aspire dashboard. The **Unresolved parameters** message appears, and you can select **Enter values** to resolve the problem: + +:::image type="content" source="./media/dashboard-unresolved-parameters-message.png" lightbox="./media/dashboard-unresolved-parameters-message.png" alt-text="Screenshot of the .NET Aspire dashboard warning that appears when there are unresolved parameters."::: + +When you select **Enter values**, .NET Aspire displays a form that you can use to configure values for each of the missing parameters. + +You can also control how the dashboard displays these parameters, by using these methods: + +- `WithDescription`: Use this method to provide a text description that helps users understand the purpose of the parameter. +- `WithMarkdownDescription`: Use this method to provide a formatted description in [Markdown](https://www.markdownguide.org/basic-syntax/) that explains the parameter's intent. +- `WithCustomInput`: Use this method to provide a callback method that customizes the parameter dialog. For example, in this callback you can customize the default value, input type, label, and placeholder text. + +This code shows how to set a description and use the callback: + +:::code language="csharp" source="snippets/unresolvedparameters/AppHost.cs" id="unresolvedparameters"::: + +The code renders this control in the dashboard: + +:::image type="content" source="./media/customized-parameter-ui.png" lightbox="./media/customized-parameter-ui.png" alt-text="Screenshot of the .NET Aspire dashboard parameter completion dialog with customizations."::: + +> [!NOTE] +> The dashboard parameter dialog includes a **Save to user secret** checkbox. Select this option to store sensitive values in your AppHost's user secrets for extra protection. For more information about secret parameter values, see [Secret values](#secret-values). + ### Parameter representation in the manifest .NET Aspire uses a [deployment manifest](../deployment/manifest-format.md) to represent the app's resources and their relationships. Parameters are represented in the manifest as a new primitive called `parameter.v0`: diff --git a/docs/fundamentals/media/customized-parameter-ui.png b/docs/fundamentals/media/customized-parameter-ui.png new file mode 100644 index 0000000000..9e72c27d7a Binary files /dev/null and b/docs/fundamentals/media/customized-parameter-ui.png differ diff --git a/docs/fundamentals/media/dashboard-unresolved-parameters-message.png b/docs/fundamentals/media/dashboard-unresolved-parameters-message.png new file mode 100644 index 0000000000..cd05680f8b Binary files /dev/null and b/docs/fundamentals/media/dashboard-unresolved-parameters-message.png differ diff --git a/docs/fundamentals/networking-overview.md b/docs/fundamentals/networking-overview.md index 8e9e579c82..b83cd8e138 100644 --- a/docs/fundamentals/networking-overview.md +++ b/docs/fundamentals/networking-overview.md @@ -1,7 +1,7 @@ --- title: .NET Aspire inner loop networking overview description: Learn how .NET Aspire handles networking and endpoints, and how you can use them in your app code. -ms.date: 10/29/2024 +ms.date: 07/11/2025 ms.topic: overview --- @@ -30,6 +30,29 @@ To help visualize how endpoints work, consider the .NET Aspire starter templates :::image type="content" source="media/networking/networking-proxies-1x.png" lightbox="media/networking/networking-proxies.png" alt-text=".NET Aspire Starter Application template inner loop networking diagram."::: +## How container networks are managed + +When you add one or more container resources, .NET Aspire creates a dedicated container bridge network to enable service discovery between containers. This bridge network is a virtual network that lets containers communicate with each other and provides a DNS server for container-to-container service discovery using DNS names. + +The network's lifetime depends on the container resources: + +- If all containers have a session lifetime, the network is also session-based and is cleaned up when the app host process ends. +- If any container has a persistent lifetime, the network is persistent and remains running after the app host process terminates. Aspire reuses this network on subsequent runs, allowing persistent containers to keep communicating even when the app host isn't running. + +For more information on container lifetimes, see [Container resource lifetime](orchestrate-resources.md#container-resource-lifetime). + +Here are the naming conventions for container networks: + +- **Session networks**: `aspire-session-network--` +- **Persistent networks**: `aspire-persistent-network--` + +Each app host instance gets its own network resources. The only differences are the network's lifetime and name; service discovery works the same way for both. + +Containers register themselves on the network using their resource name. Aspire uses this name for service discovery between containers. For example, a `pgadmin` container can connect to a database resource named `postgres` using `postgres:5432`. + +> [!NOTE] +> Host services, such as projects or other executables, don't use container networks. They rely on exposed container ports for service discovery and communication with containers. For more details on service discovery, see [service discovery overview](../service-discovery/overview.md). + ## Launch profiles When you call , the app host looks for _Properties/launchSettings.json_ to determine the default set of endpoints. The app host selects a specific launch profile using the following rules: diff --git a/docs/fundamentals/orchestrate-resources.md b/docs/fundamentals/orchestrate-resources.md index 7fd83cecee..5ddf7fc187 100644 --- a/docs/fundamentals/orchestrate-resources.md +++ b/docs/fundamentals/orchestrate-resources.md @@ -1,26 +1,27 @@ --- -title: Orchestrate resources in .NET Aspire -description: Learn techniques to control the behavior of .NET Aspire resources such as project, containers, and executable resources. -ms.date: 04/16/2025 +title: Orchestrate resources in Aspire +description: Learn techniques to control the behavior of Aspire resources such as project, containers, and executable resources. +ms.date: 07/25/2025 uid: dotnet/aspire/orchestrate-resources --- # Orchestrate resources in .NET Aspire -In this article, you learn how to customize the behavior of resources further by writing code in the app host project. In .NET Aspire, a **resource** is a dependent part of a cloud-native application. Resource types include: +In this article, you learn how to customize the behavior of resources further by writing code in the app host project. In Aspire, a **resource** is a dependent part of a cloud-native application. Resource types include: - **.NET Project**: A custom microservice, responsible for specific functionality in your cloud-native application, and often built by a separate team of developers. - **Executable**: If you need to build microservices with tools like Node.js or Orleans, they run as executable resources. -- **Container**: You can add Docker containers, based on specific images to your .NET Aspire solution. +- **Container**: You can add Docker containers, based on specific images to your Aspire solution. - **Integration resources**: Integrations often add resources such as databases, caches, and messaging services to your application. +- **External service**: Represents a third-party API or service that your application depends on but isn't managed by Aspire. Use this for resources like public APIs or SaaS endpoints. -For the fundamentals of .NET Aspire orchestration and how it manages resources, see [.NET Aspire orchestration overview](app-host-overview.md). +For the fundamentals of Aspire orchestration and how it manages resources, see [Aspire orchestration overview](app-host-overview.md). ## Resource naming conventions -Resources in .NET Aspire must follow naming restrictions set by .NET Aspire and the technology that resource represents. For example, a .NET Aspire resource has a maximum name length of 64 characters, but an Azure Container App has a maximum length of 32. When you publish the .NET Aspire container resource for Azure, the name must not exceed 32 characters in length. +Resources in Aspire must follow naming restrictions set by Aspire and the technology that resource represents. For example, a Aspire resource has a maximum name length of 64 characters, but an Azure Container App has a maximum length of 32. When you publish the Aspire container resource for Azure, the name must not exceed 32 characters in length. -.NET Aspire resource names must follow these basic rules: +Aspire resource names must follow these basic rules: - **Must** be between 1 and 64 characters in length. - **Must** start with an ASCII letter. @@ -45,7 +46,7 @@ builder.AddProject("dbmigration") In the preceding code the `"dbmigration"` resource is configured to not automatically start with the distributed application. -Resources with explicit start can be started from the .NET Aspire dashboard by clicking the "Start" command. For more information, see [.NET Aspire dashboard: Stop or Start a resource](dashboard/explore.md#stop-or-start-a-resource). +Resources with explicit start can be started from the Aspire dashboard by clicking the "Start" command. For more information, see [Aspire dashboard: Stop or Start a resource](dashboard/explore.md#stop-or-start-a-resource). ## Waiting for resources @@ -62,7 +63,7 @@ builder.AddProject("apiservice") .WaitFor(postgresdb); ``` -In the preceding code, the "apiservice" project resource waits for the "postgresdb" database resource to enter the state. The example code shows the [.NET Aspire PostgreSQL integration](../database/postgresql-integration.md), but the same pattern can be applied to other resources. +In the preceding code, the "apiservice" project resource waits for the "postgresdb" database resource to enter the state. The example code shows the [Aspire PostgreSQL integration](../database/postgresql-integration.md), but the same pattern can be applied to other resources. Other cases might warrant waiting for a resource to run to completion, either or before the dependent resource starts. To wait for a resource to run to completion, use the method: @@ -89,7 +90,7 @@ Waiting for a resource can be bypassed using the **Start** command in the dashbo ## APIs for adding and expressing resources -.NET Aspire [hosting integrations](integrations-overview.md#hosting-integrations) and [client integrations](integrations-overview.md#client-integrations) are both delivered as NuGet packages, but they serve different purposes. While _client integrations_ provide client library configuration for consuming apps outside the scope of the app host, _hosting integrations_ provide APIs for expressing resources and dependencies within the app host. For more information, see [.NET Aspire integrations overview: Integration responsibilities](integrations-overview.md#integration-responsibilities). +Aspire [hosting integrations](integrations-overview.md#hosting-integrations) and [client integrations](integrations-overview.md#client-integrations) are both delivered as NuGet packages, but they serve different purposes. While _client integrations_ provide client library configuration for consuming apps outside the scope of the app host, _hosting integrations_ provide APIs for expressing resources and dependencies within the app host. For more information, see [Aspire integrations overview: Integration responsibilities](integrations-overview.md#integration-responsibilities). ## Express container resources @@ -131,9 +132,9 @@ The preceding code adds a container resource named "ollama" with the image `olla ### Customize container resources -All subclasses can be customized to meet your specific requirements. This can be useful when using a [hosting integration](integrations-overview.md#hosting-integrations) that models a container resource, but requires modifications. When you have an `IResourceBuilder` you can chain calls to any of the available APIs to modify the container resource. .NET Aspire container resources typically point to pinned tags, but you might want to use the `latest` tag instead. +All subclasses can be customized to meet your specific requirements. This can be useful when using a [hosting integration](integrations-overview.md#hosting-integrations) that models a container resource, but requires modifications. When you have an `IResourceBuilder` you can chain calls to any of the available APIs to modify the container resource. Aspire container resources typically point to pinned tags, but you might want to use the `latest` tag instead. -To help exemplify this, imagine a scenario where you're using the [.NET Aspire Redis integration](../caching/stackexchange-redis-integration.md). If the Redis integration relies on the `7.4` tag and you want to use the `latest` tag instead, you can chain a call to the API: +To help exemplify this, imagine a scenario where you're using the [Aspire Redis integration](../caching/stackexchange-redis-integration.md). If the Redis integration relies on the `7.4` tag and you want to use the `latest` tag instead, you can chain a call to the API: ```csharp var builder = DistributedApplication.CreateBuilder(args); @@ -149,7 +150,7 @@ For more information and additional APIs available, see is used to determine what container image to create and start. Under the hood, .NET Aspire runs the container using the defined container image by delegating calls to the appropriate OCI-compliant container runtime, either Docker or Podman. The following commands are used: +When the app host is run, the is used to determine what container image to create and start. Under the hood, Aspire runs the container using the defined container image by delegating calls to the appropriate OCI-compliant container runtime, either Docker or Podman. The following commands are used: #### [Docker](#tab/docker) @@ -171,7 +172,7 @@ These commands are used instead of `podman run` to manage attached container net --- -Beyond the base resource types, , , and , .NET Aspire provides extension methods to add common resources to your app model. For more information, see [Hosting integrations](integrations-overview.md#hosting-integrations). +Beyond the base resource types, , , and , Aspire provides extension methods to add common resources to your app model. For more information, see [Hosting integrations](integrations-overview.md#hosting-integrations). ### Container resource lifetime @@ -186,6 +187,112 @@ var ollama = builder.AddContainer("ollama", "ollama/ollama") The preceding code adds a container resource named "ollama" with the image "ollama/ollama" and a persistent lifetime. +## Express external service resources + + + +External services are third-party APIs and services that your application depends on but that exist outside your Aspire solution. These services are already running elsewhere and aren't managed by Aspire. To express an `ExternalServiceResource` you add it to an instance by calling the `AddExternalService` method: + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +var nuget = builder.AddExternalService("nuget", "https://api.nuget.org/") + .WithHttpHealthCheck(path: "/v3/index.json"); + +var frontend = builder.AddProject("frontend") + .WithReference(nuget); +``` + +The preceding code adds an external service resource named "nuget" that points to the NuGet API. The external service is configured with an HTTP health check to monitor its availability. The frontend project can then reference this external service for service discovery. + +External services support several configuration approaches: + +### Static URL configuration + +You can configure an external service with a static URL using either a string or a URI: + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +// Using a string URL +var nuget = builder.AddExternalService("nuget", "https://api.nuget.org/"); + +// Using a URI +var uri = new Uri("https://api.example.com/"); +var api = builder.AddExternalService("external-api", uri); +``` + +### Parameter-based URL configuration + +For scenarios where the external service URL might vary between environments or needs to be configurable, you can use parameters: + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +var externalServiceUrl = builder.AddParameter("external-service-url"); +var externalService = builder.AddExternalService("external-service", externalServiceUrl); + +var frontend = builder.AddProject("frontend") + .WithReference(externalService); +``` + +When using parameter-based configuration, the URL value can be set through configuration, environment variables, or user secrets. During development, Aspire might prompt you to provide the URL value. For more information, see [External parameters](external-parameters.md). + +### External service URL requirements + +External service URLs must meet specific requirements: + +- **Must** be an absolute URI (include scheme, host, and optional port). +- **Must** have the absolute path set to "/" (no additional path segments). +- **Must not** contain query parameters or fragments. + +Valid examples: + +- `https://api.example.com/` +- `http://localhost:8080/` +- `https://service.example.com:9443/` + +Invalid examples: + +- `https://api.example.com/v1/api` (contains path) +- `https://api.example.com/?version=1` (contains query) +- `https://api.example.com/#section` (contains fragment) + +### Health checks for external services + +External services can be configured with HTTP health checks to monitor their availability: + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +var api = builder.AddExternalService("api", "https://api.example.com/") + .WithHttpHealthCheck(path: "/health", statusCode: 200); +``` + +The `WithHttpHealthCheck` method adds a health check that periodically polls the external service. You can specify: + +- **`path`**: The relative path for the health check endpoint (optional, defaults to no additional path). +- **`statusCode`**: The expected HTTP status code (optional, defaults to 200). + +### Service discovery with external services + +When you reference an external service from another resource, Aspire automatically configures service discovery by injecting environment variables in the standard format: + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +var api = builder.AddExternalService("api", "https://api.example.com/"); + +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. + +### External service lifecycle + +External services implement , meaning they're not managed by Aspire's lifecycle system. They're expected to be running independently. During development, external services appear in the Aspire dashboard with a "Running" state if they're reachable, or show health check failures if they're not available. + ## Resource relationships Resource relationships link resources together. Relationships are informational and don't impact an app's runtime behavior. Instead, they're used when displaying details about resources in the dashboard. For example, relationships are visible in the [dashboard's resource details](./dashboard/explore.md#resource-details), and `Parent` relationships control resource nesting on the resources page. @@ -219,5 +326,5 @@ The preceding example uses - - + + diff --git a/docs/fundamentals/snippets/build-container-images/apphost/AppHost.cs b/docs/fundamentals/snippets/build-container-images/apphost/AppHost.cs new file mode 100644 index 0000000000..af5ac627ec --- /dev/null +++ b/docs/fundamentals/snippets/build-container-images/apphost/AppHost.cs @@ -0,0 +1,10 @@ +var builder = DistributedApplication.CreateBuilder(args); + +var cache = builder.AddRedis("redis"); + +builder.AddProject("api") + .WithReference(cache); + +builder.AddComputeEnvironment("compute-env"); + +builder.Build().Run(); \ No newline at end of file diff --git a/docs/fundamentals/snippets/build-container-images/apphost/AppHost.csproj b/docs/fundamentals/snippets/build-container-images/apphost/AppHost.csproj new file mode 100644 index 0000000000..7ed3ba4b66 --- /dev/null +++ b/docs/fundamentals/snippets/build-container-images/apphost/AppHost.csproj @@ -0,0 +1,21 @@ + + + + + + Exe + net9.0 + enable + enable + + + + + + + + + + + + diff --git a/docs/fundamentals/snippets/build-container-images/apphost/ComputeEnvironmentResource.cs b/docs/fundamentals/snippets/build-container-images/apphost/ComputeEnvironmentResource.cs new file mode 100644 index 0000000000..2947227ea1 --- /dev/null +++ b/docs/fundamentals/snippets/build-container-images/apphost/ComputeEnvironmentResource.cs @@ -0,0 +1,111 @@ +using System.Diagnostics.CodeAnalysis; +using Aspire.Hosting.Publishing; +using Microsoft.Extensions.DependencyInjection; + +[Experimental("ASPIRECOMPUTE001")] +public sealed class ComputeEnvironmentResource : Resource +{ + // + public ComputeEnvironmentResource(string name) : base(name) + { + Annotations.Add(new PublishingCallbackAnnotation(PublishAsync)); + Annotations.Add(new DeployingCallbackAnnotation(DeployAsync)); + } + // + + // + private static async Task PublishAsync(PublishingContext context) + { + var reporter = context.ActivityReporter; + var imageBuilder = context.Services.GetRequiredService(); + + // Build container images for all project resources in the application + await using (var buildStep = await reporter.CreateStepAsync( + "Build container images", context.CancellationToken)) + { + // Find all resources that need container images + var projectResources = context.Model.Resources + .OfType() + .ToList(); + + if (projectResources.Count > 0) + { + // Configure how images should be built + var buildOptions = new ContainerBuildOptions + { + ImageFormat = ContainerImageFormat.Oci, + TargetPlatform = ContainerTargetPlatform.LinuxAmd64, + OutputPath = Path.Combine(context.OutputPath, "images") + }; + + var buildTask = await buildStep.CreateTaskAsync( + $"Building {projectResources.Count} container image(s)", context.CancellationToken); + + // Build all the container images + await imageBuilder.BuildImagesAsync( + projectResources, buildOptions, context.CancellationToken); + + await buildTask.SucceedAsync( + $"Built {projectResources.Count} image(s) successfully", context.CancellationToken); + } + else + { + var skipTask = await buildStep.CreateTaskAsync( + "No container images to build", context.CancellationToken); + + await skipTask.SucceedAsync("Skipped - no project resources found", context.CancellationToken); + } + + await buildStep.SucceedAsync("Container image build completed", context.CancellationToken); + } + + // Generate deployment manifests + await using (var manifestStep = await reporter.CreateStepAsync( + "Generate deployment manifests", context.CancellationToken)) + { + var bicepTask = await manifestStep.CreateTaskAsync( + "Write main.bicep", context.CancellationToken); + + // Write file to context.OutputPath … + await bicepTask.SucceedAsync( + $"main.bicep at {context.OutputPath}", context.CancellationToken); + + await manifestStep.SucceedAsync("Manifests ready", context.CancellationToken); + } + + // Complete the publishing operation + await reporter.CompletePublishAsync( + completionMessage: "Publishing pipeline completed successfully", + completionState: CompletionState.Completed, + cancellationToken: context.CancellationToken); + } + // + + // + private static async Task DeployAsync(DeployingContext context) + { + var reporter = context.ActivityReporter; + + await using (var deployStep = await reporter.CreateStepAsync( + "Deploy to target environment", context.CancellationToken)) + { + var applyTask = await deployStep.CreateTaskAsync( + "Apply Kubernetes manifests", context.CancellationToken); + + // Simulate deploying to Kubernetes cluster + await Task.Delay(1_000, context.CancellationToken); + + await applyTask.SucceedAsync("All workloads deployed", context.CancellationToken); + await deployStep.SucceedAsync("Deployment to cluster completed", context.CancellationToken); + } + + // Complete the deployment operation + await reporter.CompletePublishAsync( + completionMessage: "Deployment completed successfully", + completionState: CompletionState.Completed, + isDeploy: true, + cancellationToken: context.CancellationToken); + } + // +} + diff --git a/docs/fundamentals/snippets/build-container-images/apphost/ComputeEnvironmentResourceExtensions.cs b/docs/fundamentals/snippets/build-container-images/apphost/ComputeEnvironmentResourceExtensions.cs new file mode 100644 index 0000000000..69cc1d8b1d --- /dev/null +++ b/docs/fundamentals/snippets/build-container-images/apphost/ComputeEnvironmentResourceExtensions.cs @@ -0,0 +1,14 @@ +using System.Diagnostics.CodeAnalysis; + +[Experimental("ASPIRECOMPUTE001")] +public static class ComputeEnvironmentResourceExtensions +{ + public static IResourceBuilder AddComputeEnvironment( + this IDistributedApplicationBuilder builder, + [ResourceName] string name) + { + var resource = new ComputeEnvironmentResource(name); + + return builder.AddResource(resource); + } +} diff --git a/docs/fundamentals/snippets/build-container-images/build-container-images.sln b/docs/fundamentals/snippets/build-container-images/build-container-images.sln new file mode 100644 index 0000000000..a1f19d7e2d --- /dev/null +++ b/docs/fundamentals/snippets/build-container-images/build-container-images.sln @@ -0,0 +1,56 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "api", "api", "{D82C56D6-B2CD-F611-9E31-04638F7EC30E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Api", "api\Api.csproj", "{071A7C69-29C7-4AA2-AF07-237380B41DE1}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "apphost", "apphost", "{90DCE71B-4939-B80A-CE8C-85DE2210C7AC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AppHost", "apphost\AppHost.csproj", "{28328297-9072-4001-B418-6B3D3BF67E42}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {071A7C69-29C7-4AA2-AF07-237380B41DE1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {071A7C69-29C7-4AA2-AF07-237380B41DE1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {071A7C69-29C7-4AA2-AF07-237380B41DE1}.Debug|x64.ActiveCfg = Debug|Any CPU + {071A7C69-29C7-4AA2-AF07-237380B41DE1}.Debug|x64.Build.0 = Debug|Any CPU + {071A7C69-29C7-4AA2-AF07-237380B41DE1}.Debug|x86.ActiveCfg = Debug|Any CPU + {071A7C69-29C7-4AA2-AF07-237380B41DE1}.Debug|x86.Build.0 = Debug|Any CPU + {071A7C69-29C7-4AA2-AF07-237380B41DE1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {071A7C69-29C7-4AA2-AF07-237380B41DE1}.Release|Any CPU.Build.0 = Release|Any CPU + {071A7C69-29C7-4AA2-AF07-237380B41DE1}.Release|x64.ActiveCfg = Release|Any CPU + {071A7C69-29C7-4AA2-AF07-237380B41DE1}.Release|x64.Build.0 = Release|Any CPU + {071A7C69-29C7-4AA2-AF07-237380B41DE1}.Release|x86.ActiveCfg = Release|Any CPU + {071A7C69-29C7-4AA2-AF07-237380B41DE1}.Release|x86.Build.0 = Release|Any CPU + {28328297-9072-4001-B418-6B3D3BF67E42}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {28328297-9072-4001-B418-6B3D3BF67E42}.Debug|Any CPU.Build.0 = Debug|Any CPU + {28328297-9072-4001-B418-6B3D3BF67E42}.Debug|x64.ActiveCfg = Debug|Any CPU + {28328297-9072-4001-B418-6B3D3BF67E42}.Debug|x64.Build.0 = Debug|Any CPU + {28328297-9072-4001-B418-6B3D3BF67E42}.Debug|x86.ActiveCfg = Debug|Any CPU + {28328297-9072-4001-B418-6B3D3BF67E42}.Debug|x86.Build.0 = Debug|Any CPU + {28328297-9072-4001-B418-6B3D3BF67E42}.Release|Any CPU.ActiveCfg = Release|Any CPU + {28328297-9072-4001-B418-6B3D3BF67E42}.Release|Any CPU.Build.0 = Release|Any CPU + {28328297-9072-4001-B418-6B3D3BF67E42}.Release|x64.ActiveCfg = Release|Any CPU + {28328297-9072-4001-B418-6B3D3BF67E42}.Release|x64.Build.0 = Release|Any CPU + {28328297-9072-4001-B418-6B3D3BF67E42}.Release|x86.ActiveCfg = Release|Any CPU + {28328297-9072-4001-B418-6B3D3BF67E42}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {071A7C69-29C7-4AA2-AF07-237380B41DE1} = {D82C56D6-B2CD-F611-9E31-04638F7EC30E} + {28328297-9072-4001-B418-6B3D3BF67E42} = {90DCE71B-4939-B80A-CE8C-85DE2210C7AC} + EndGlobalSection +EndGlobal diff --git a/docs/fundamentals/snippets/custom-commands/AspireApp/AspireApp.AppHost/AspireApp.AppHost.csproj b/docs/fundamentals/snippets/custom-commands/AspireApp/AspireApp.AppHost/AspireApp.AppHost.csproj index 12b01b8214..aac2801d95 100644 --- a/docs/fundamentals/snippets/custom-commands/AspireApp/AspireApp.AppHost/AspireApp.AppHost.csproj +++ b/docs/fundamentals/snippets/custom-commands/AspireApp/AspireApp.AppHost/AspireApp.AppHost.csproj @@ -1,6 +1,6 @@ - + Exe @@ -16,8 +16,8 @@ - - + + diff --git a/docs/fundamentals/snippets/custom-commands/AspireApp/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj b/docs/fundamentals/snippets/custom-commands/AspireApp/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj index ae37426e09..9183e963ee 100644 --- a/docs/fundamentals/snippets/custom-commands/AspireApp/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj +++ b/docs/fundamentals/snippets/custom-commands/AspireApp/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj @@ -11,7 +11,7 @@ - + diff --git a/docs/fundamentals/snippets/custom-commands/AspireApp/AspireApp.Web/AspireApp.Web.csproj b/docs/fundamentals/snippets/custom-commands/AspireApp/AspireApp.Web/AspireApp.Web.csproj index 9349588100..64ac49c577 100644 --- a/docs/fundamentals/snippets/custom-commands/AspireApp/AspireApp.Web/AspireApp.Web.csproj +++ b/docs/fundamentals/snippets/custom-commands/AspireApp/AspireApp.Web/AspireApp.Web.csproj @@ -11,7 +11,7 @@ - + diff --git a/docs/fundamentals/snippets/custom-urls/AspireApp.AppHost/AspireApp.AppHost.csproj b/docs/fundamentals/snippets/custom-urls/AspireApp.AppHost/AspireApp.AppHost.csproj index 46bbe2bc29..f2c96ac124 100644 --- a/docs/fundamentals/snippets/custom-urls/AspireApp.AppHost/AspireApp.AppHost.csproj +++ b/docs/fundamentals/snippets/custom-urls/AspireApp.AppHost/AspireApp.AppHost.csproj @@ -1,6 +1,6 @@ - + Exe @@ -11,7 +11,7 @@ - + diff --git a/docs/fundamentals/snippets/healthz/Healthz.ServiceDefaults/Healthz.ServiceDefaults.csproj b/docs/fundamentals/snippets/healthz/Healthz.ServiceDefaults/Healthz.ServiceDefaults.csproj index ae37426e09..9183e963ee 100644 --- a/docs/fundamentals/snippets/healthz/Healthz.ServiceDefaults/Healthz.ServiceDefaults.csproj +++ b/docs/fundamentals/snippets/healthz/Healthz.ServiceDefaults/Healthz.ServiceDefaults.csproj @@ -11,7 +11,7 @@ - + diff --git a/docs/fundamentals/snippets/http-commands/AspireApp/AspireApp.Api/AspireApp.Api.csproj b/docs/fundamentals/snippets/http-commands/AspireApp/AspireApp.Api/AspireApp.Api.csproj index 4098180252..6da7575dee 100644 --- a/docs/fundamentals/snippets/http-commands/AspireApp/AspireApp.Api/AspireApp.Api.csproj +++ b/docs/fundamentals/snippets/http-commands/AspireApp/AspireApp.Api/AspireApp.Api.csproj @@ -10,7 +10,7 @@ - + diff --git a/docs/fundamentals/snippets/http-commands/AspireApp/AspireApp.AppHost/AspireApp.AppHost.csproj b/docs/fundamentals/snippets/http-commands/AspireApp/AspireApp.AppHost/AspireApp.AppHost.csproj index 85d9100f40..f5a7785db2 100644 --- a/docs/fundamentals/snippets/http-commands/AspireApp/AspireApp.AppHost/AspireApp.AppHost.csproj +++ b/docs/fundamentals/snippets/http-commands/AspireApp/AspireApp.AppHost/AspireApp.AppHost.csproj @@ -1,6 +1,6 @@ - + Exe @@ -12,9 +12,9 @@ - - - + + + diff --git a/docs/fundamentals/snippets/integrations/AspireApp/AspireApp.AppHost/AspireApp.AppHost.csproj b/docs/fundamentals/snippets/integrations/AspireApp/AspireApp.AppHost/AspireApp.AppHost.csproj index 8960c1e49c..21625fc30c 100644 --- a/docs/fundamentals/snippets/integrations/AspireApp/AspireApp.AppHost/AspireApp.AppHost.csproj +++ b/docs/fundamentals/snippets/integrations/AspireApp/AspireApp.AppHost/AspireApp.AppHost.csproj @@ -1,5 +1,5 @@ - + Exe net9.0 @@ -7,8 +7,8 @@ enable - - + + diff --git a/docs/fundamentals/snippets/integrations/AspireApp/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj b/docs/fundamentals/snippets/integrations/AspireApp/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj index ae37426e09..9183e963ee 100644 --- a/docs/fundamentals/snippets/integrations/AspireApp/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj +++ b/docs/fundamentals/snippets/integrations/AspireApp/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj @@ -11,7 +11,7 @@ - + diff --git a/docs/fundamentals/snippets/integrations/AspireApp/WorkerService/WorkerService.csproj b/docs/fundamentals/snippets/integrations/AspireApp/WorkerService/WorkerService.csproj index b9af7ab994..419f363ba9 100644 --- a/docs/fundamentals/snippets/integrations/AspireApp/WorkerService/WorkerService.csproj +++ b/docs/fundamentals/snippets/integrations/AspireApp/WorkerService/WorkerService.csproj @@ -8,7 +8,7 @@ - + diff --git a/docs/fundamentals/snippets/lifecycles/AspireApp/AspireApp.AppHost/Program.cs b/docs/fundamentals/snippets/lifecycles/AspireApp/AspireApp.AppHost/AppHost.cs similarity index 100% rename from docs/fundamentals/snippets/lifecycles/AspireApp/AspireApp.AppHost/Program.cs rename to docs/fundamentals/snippets/lifecycles/AspireApp/AspireApp.AppHost/AppHost.cs diff --git a/docs/fundamentals/snippets/lifecycles/AspireApp/AspireApp.AppHost/AspireApp.AppHost.csproj b/docs/fundamentals/snippets/lifecycles/AspireApp/AspireApp.AppHost/AspireApp.AppHost.csproj index e8fb36acd8..c78f043dc5 100644 --- a/docs/fundamentals/snippets/lifecycles/AspireApp/AspireApp.AppHost/AspireApp.AppHost.csproj +++ b/docs/fundamentals/snippets/lifecycles/AspireApp/AspireApp.AppHost/AspireApp.AppHost.csproj @@ -1,5 +1,5 @@ - + Exe net9.0 @@ -8,6 +8,6 @@ d1fafd31-bb63-479d-bc2b-a4067786068f - + \ No newline at end of file diff --git a/docs/fundamentals/snippets/lifecycles/AspireApp/AspireApp.AppHost/Console.txt b/docs/fundamentals/snippets/lifecycles/AspireApp/AspireApp.AppHost/Console.txt index 8e36e37c71..e0cb244a01 100644 --- a/docs/fundamentals/snippets/lifecycles/AspireApp/AspireApp.AppHost/Console.txt +++ b/docs/fundamentals/snippets/lifecycles/AspireApp/AspireApp.AppHost/Console.txt @@ -1,7 +1,7 @@ info: LifecycleLogger[0] 1. BeforeStartAsync info: Aspire.Hosting.DistributedApplication[0] - Aspire version: 9.3.0 + Aspire version: 9.4.0 info: Aspire.Hosting.DistributedApplication[0] Distributed application starting. info: Aspire.Hosting.DistributedApplication[0] diff --git a/docs/fundamentals/snippets/lifecycles/AspireApp/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj b/docs/fundamentals/snippets/lifecycles/AspireApp/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj index ae37426e09..9183e963ee 100644 --- a/docs/fundamentals/snippets/lifecycles/AspireApp/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj +++ b/docs/fundamentals/snippets/lifecycles/AspireApp/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj @@ -11,7 +11,7 @@ - + diff --git a/docs/fundamentals/snippets/networking/Networking.AppHost/Networking.AppHost.csproj b/docs/fundamentals/snippets/networking/Networking.AppHost/Networking.AppHost.csproj index 9446615816..1a9ea3499b 100644 --- a/docs/fundamentals/snippets/networking/Networking.AppHost/Networking.AppHost.csproj +++ b/docs/fundamentals/snippets/networking/Networking.AppHost/Networking.AppHost.csproj @@ -1,5 +1,5 @@ - + Exe net9.0 @@ -7,8 +7,8 @@ enable - - + + diff --git a/docs/fundamentals/snippets/networking/Networking.ServiceDefaults/Networking.ServiceDefaults.csproj b/docs/fundamentals/snippets/networking/Networking.ServiceDefaults/Networking.ServiceDefaults.csproj index ae37426e09..9183e963ee 100644 --- a/docs/fundamentals/snippets/networking/Networking.ServiceDefaults/Networking.ServiceDefaults.csproj +++ b/docs/fundamentals/snippets/networking/Networking.ServiceDefaults/Networking.ServiceDefaults.csproj @@ -11,7 +11,7 @@ - + diff --git a/docs/fundamentals/snippets/params/Parameters.ApiService/Parameters.ApiService.csproj b/docs/fundamentals/snippets/params/Parameters.ApiService/Parameters.ApiService.csproj index 14d7e087e8..847b78741b 100644 --- a/docs/fundamentals/snippets/params/Parameters.ApiService/Parameters.ApiService.csproj +++ b/docs/fundamentals/snippets/params/Parameters.ApiService/Parameters.ApiService.csproj @@ -7,7 +7,7 @@ - + diff --git a/docs/fundamentals/snippets/params/Parameters.AppHost/Parameters.AppHost.csproj b/docs/fundamentals/snippets/params/Parameters.AppHost/Parameters.AppHost.csproj index e6e7c2f8d2..1905272b70 100644 --- a/docs/fundamentals/snippets/params/Parameters.AppHost/Parameters.AppHost.csproj +++ b/docs/fundamentals/snippets/params/Parameters.AppHost/Parameters.AppHost.csproj @@ -1,5 +1,5 @@ - + Exe net9.0 @@ -7,8 +7,8 @@ enable - - + + diff --git a/docs/fundamentals/snippets/params/Parameters.ServiceDefaults/Parameters.ServiceDefaults.csproj b/docs/fundamentals/snippets/params/Parameters.ServiceDefaults/Parameters.ServiceDefaults.csproj index ebdd4d0d9f..499bebc980 100644 --- a/docs/fundamentals/snippets/params/Parameters.ServiceDefaults/Parameters.ServiceDefaults.csproj +++ b/docs/fundamentals/snippets/params/Parameters.ServiceDefaults/Parameters.ServiceDefaults.csproj @@ -12,7 +12,7 @@ - + diff --git a/docs/fundamentals/snippets/referenceexpressions/AspireReferenceExpressions.AppHost/AspireReferenceExpressions.AppHost.csproj b/docs/fundamentals/snippets/referenceexpressions/AspireReferenceExpressions.AppHost/AspireReferenceExpressions.AppHost.csproj index 8a89b6a77a..362ba6bf2f 100644 --- a/docs/fundamentals/snippets/referenceexpressions/AspireReferenceExpressions.AppHost/AspireReferenceExpressions.AppHost.csproj +++ b/docs/fundamentals/snippets/referenceexpressions/AspireReferenceExpressions.AppHost/AspireReferenceExpressions.AppHost.csproj @@ -1,6 +1,6 @@ - + Exe @@ -11,8 +11,8 @@ - - + + diff --git a/docs/fundamentals/snippets/referenceexpressions/AspireReferenceExpressions.ServiceDefaults/AspireReferenceExpressions.ServiceDefaults.csproj b/docs/fundamentals/snippets/referenceexpressions/AspireReferenceExpressions.ServiceDefaults/AspireReferenceExpressions.ServiceDefaults.csproj index ae37426e09..9183e963ee 100644 --- a/docs/fundamentals/snippets/referenceexpressions/AspireReferenceExpressions.ServiceDefaults/AspireReferenceExpressions.ServiceDefaults.csproj +++ b/docs/fundamentals/snippets/referenceexpressions/AspireReferenceExpressions.ServiceDefaults/AspireReferenceExpressions.ServiceDefaults.csproj @@ -11,7 +11,7 @@ - + diff --git a/docs/fundamentals/snippets/template/YourAppName/YourAppName.ServiceDefaults.csproj b/docs/fundamentals/snippets/template/YourAppName/YourAppName.ServiceDefaults.csproj index ae37426e09..9183e963ee 100644 --- a/docs/fundamentals/snippets/template/YourAppName/YourAppName.ServiceDefaults.csproj +++ b/docs/fundamentals/snippets/template/YourAppName/YourAppName.ServiceDefaults.csproj @@ -11,7 +11,7 @@ - + diff --git a/docs/fundamentals/snippets/unresolvedparameters/AppHost.cs b/docs/fundamentals/snippets/unresolvedparameters/AppHost.cs new file mode 100644 index 0000000000..7383f9c19d --- /dev/null +++ b/docs/fundamentals/snippets/unresolvedparameters/AppHost.cs @@ -0,0 +1,19 @@ +var builder = DistributedApplication.CreateBuilder(args); + +// +#pragma warning disable ASPIREINTERACTION001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. +var externalServiceUrl = builder.AddParameter("external-service-url") + .WithDescription("The URL of the external service.") + .WithCustomInput(p => new() + { + InputType = InputType.Text, +#pragma warning restore ASPIREINTERACTION001 + Value = "https://example.com", + Label = p.Name, + Placeholder = $"Enter value for {p.Name}", + Description = p.Description + }); +var externalService = builder.AddExternalService("external-service", externalServiceUrl); +// + +builder.Build().Run(); diff --git a/docs/fundamentals/snippets/unresolvedparameters/Properties/launchSettings.json b/docs/fundamentals/snippets/unresolvedparameters/Properties/launchSettings.json new file mode 100644 index 0000000000..3f0840e13f --- /dev/null +++ b/docs/fundamentals/snippets/unresolvedparameters/Properties/launchSettings.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:17126;http://localhost:15244", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21213", + "ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22016" + } + }, + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:15244", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19152", + "ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20003" + } + } + } +} diff --git a/docs/fundamentals/snippets/unresolvedparameters/appsettings.Development.json b/docs/fundamentals/snippets/unresolvedparameters/appsettings.Development.json new file mode 100644 index 0000000000..0c208ae918 --- /dev/null +++ b/docs/fundamentals/snippets/unresolvedparameters/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/docs/fundamentals/snippets/unresolvedparameters/appsettings.json b/docs/fundamentals/snippets/unresolvedparameters/appsettings.json new file mode 100644 index 0000000000..31c092aa45 --- /dev/null +++ b/docs/fundamentals/snippets/unresolvedparameters/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning", + "Aspire.Hosting.Dcp": "Warning" + } + } +} diff --git a/docs/fundamentals/snippets/unresolvedparameters/unresolvedparameters.csproj b/docs/fundamentals/snippets/unresolvedparameters/unresolvedparameters.csproj new file mode 100644 index 0000000000..9add842f66 --- /dev/null +++ b/docs/fundamentals/snippets/unresolvedparameters/unresolvedparameters.csproj @@ -0,0 +1,17 @@ + + + + + + Exe + net9.0 + enable + enable + 64d4f7ef-c15f-4ba3-907d-daed941b9260 + + + + + + + diff --git a/docs/fundamentals/snippets/unresolvedparameters/unresolvedparameters.sln b/docs/fundamentals/snippets/unresolvedparameters/unresolvedparameters.sln new file mode 100644 index 0000000000..e013527a37 --- /dev/null +++ b/docs/fundamentals/snippets/unresolvedparameters/unresolvedparameters.sln @@ -0,0 +1,24 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.2.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "unresolvedparameters", "unresolvedparameters.csproj", "{AE35B03C-01B2-A9C4-A64F-E3A9B5292A95}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {AE35B03C-01B2-A9C4-A64F-E3A9B5292A95}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AE35B03C-01B2-A9C4-A64F-E3A9B5292A95}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AE35B03C-01B2-A9C4-A64F-E3A9B5292A95}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AE35B03C-01B2-A9C4-A64F-E3A9B5292A95}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {CFA6711C-93E9-4BF3-86D8-94978115BBCF} + EndGlobalSection +EndGlobal diff --git a/docs/fundamentals/snippets/volumes/VolumeMounts.AppHost/VolumeMounts.AppHost.csproj b/docs/fundamentals/snippets/volumes/VolumeMounts.AppHost/VolumeMounts.AppHost.csproj index 99a804970f..0427a77969 100644 --- a/docs/fundamentals/snippets/volumes/VolumeMounts.AppHost/VolumeMounts.AppHost.csproj +++ b/docs/fundamentals/snippets/volumes/VolumeMounts.AppHost/VolumeMounts.AppHost.csproj @@ -1,5 +1,5 @@ - + Exe net9.0 @@ -12,10 +12,10 @@ - - - - - + + + + + \ No newline at end of file diff --git a/docs/fundamentals/snippets/volumes/VolumeMounts.ServiceDefaults/VolumeMounts.ServiceDefaults.csproj b/docs/fundamentals/snippets/volumes/VolumeMounts.ServiceDefaults/VolumeMounts.ServiceDefaults.csproj index ae37426e09..9183e963ee 100644 --- a/docs/fundamentals/snippets/volumes/VolumeMounts.ServiceDefaults/VolumeMounts.ServiceDefaults.csproj +++ b/docs/fundamentals/snippets/volumes/VolumeMounts.ServiceDefaults/VolumeMounts.ServiceDefaults.csproj @@ -11,7 +11,7 @@ - + diff --git a/docs/get-started/aspire-overview.md b/docs/get-started/aspire-overview.md index b328326109..7233c2111f 100644 --- a/docs/get-started/aspire-overview.md +++ b/docs/get-started/aspire-overview.md @@ -1,101 +1,130 @@ --- -title: .NET Aspire overview -description: Learn about .NET Aspire, an application stack designed to improve the experience of building distributed applications. -ms.date: 03/26/2025 +title: Aspire overview +description: Learn about Aspire, an application stack designed to improve the experience of building distributed applications. +ms.date: 07/21/2025 --- -# .NET Aspire overview +# Aspire overview -:::row::: -:::column::: +**Aspire** provides tools, templates, and packages for building **observable, production-ready distributed apps**. At the center is the **app model**—a **code-first, single source of truth** that defines your app's services, resources, and connections. -:::image type="icon" border="false" source="../../assets/dotnet-aspire-logo-128.svg"::: +Aspire gives you a **unified toolchain**: launch and debug your entire app locally with one command, then deploy anywhere—**Kubernetes, the cloud, or your own servers**—using the same composition. -:::column-end::: -:::column span="3"::: +Extensibility is a core focus. Aspire's APIs are designed so you can adapt the platform to your infrastructure, services, and workflows. -.NET Aspire provides tools, templates, and packages to help you build observable, production-ready apps. Delivered through NuGet packages, .NET Aspire simplifies common challenges in modern app development. Today's apps often rely on multiple services like databases, messaging, and caching, many supported by [.NET Aspire Integrations](../fundamentals/integrations-overview.md). For the official support information, see the [.NET Aspire Support Policy](https://dotnet.microsoft.com/platform/support/policy/aspire). +### Key capabilities -:::column-end::: -:::row-end::: +- **App host orchestration:** Define services, dependencies, and configuration in code. +- **Rich integrations:** NuGet packages for popular services with standardized interfaces. +- **Consistent tooling:** Project templates and experiences for **Visual Studio, VS Code, and the CLI.** -## Why .NET Aspire? +For the official support information, see the [Aspire Support Policy](https://dotnet.microsoft.com/platform/support/policy/aspire). -.NET Aspire improves the experience of building apps that have a variety of projects and resources. With dev-time productivity enhancements that emulate deployed scenarios, you can quickly develop interconnected apps. Designed for flexibility, .NET Aspire allows you to replace or extend parts with your preferred tools and workflows. Key features include: +## The AppHost -- [**Dev-Time Orchestration**](#dev-time-orchestration): .NET Aspire provides features for running and connecting multi-project applications, container resources, and other dependencies for [local development environments](../fundamentals/networking-overview.md). -- [**Integrations**](#net-aspire-integrations): .NET Aspire integrations are NuGet packages for commonly used services, such as Redis or Postgres, with standardized interfaces ensuring they connect consistently and seamlessly with your app. -- [**Tooling**](#project-templates-and-tooling): .NET Aspire comes with project templates and tooling experiences for Visual Studio, Visual Studio Code, and the [.NET CLI](/dotnet/core/tools/) to help you create and interact with .NET Aspire projects. +Aspire's app host is where you define your app's services and dependencies in code—no complex configuration files required. The app host provides orchestration for your local development environment by simplifying the management of service discovery, environment variables, and container configurations. -## Dev-time orchestration +Picture a common three-tier architecture: a frontend, which depends on an API, which connects to a database. In Aspire, this topology is represented in the app host as shown in the following code: -In .NET Aspire, "orchestration" primarily focuses on enhancing the _local development_ experience by simplifying the management of your app's configuration and interconnections. It's important to note that .NET Aspire's orchestration isn't intended to replace the robust systems used in production environments, such as [Kubernetes](../deployment/overview.md#deploy-to-kubernetes). Instead, it's a set of abstractions that streamline the setup of service discovery, environment variables, and container configurations, eliminating the need to deal with low-level implementation details. With .NET Aspire, your code has a consistent bootstrapping experience on any dev machine without the need for complex manual steps, making it easier to manage during the development phase. +```csharp +var builder = DistributedApplication.CreateBuilder(args); -.NET Aspire orchestration assists with the following concerns: +// Add database service +var postgres = builder.AddPostgres("db") + .AddDatabase("appdata") + .WithDataVolume(); -- **App composition**: Specify the .NET projects, containers, executables, and cloud resources that make up the application. -- **Service discovery and connection string management**: The app host injects the right connection strings, network configurations, and service discovery information to simplify the developer experience. +// Add API service and reference dependencies +var api = builder.AddProject("api") + .WithReference(postgres) + .WaitFor(postgres); -For example, using .NET Aspire, the following code creates a local Redis container resource, waits for it to become available, and then configures the appropriate connection string in the `"frontend"` project with a few helper method calls: +// Add frontend service and reference the API +var frontend = builder.AddProject("frontend") + .WithReference(api); -```csharp -// Create a distributed application builder given the command line arguments. -var builder = DistributedApplication.CreateBuilder(args); +builder.Build().Run(); +``` -// Add a Redis server to the application. -var cache = builder.AddRedis("cache"); +The app host assists with the following concerns: -// Add the frontend project to the application and configure it to use the -// Redis server, defined as a referenced dependency. -builder.AddProject("frontend") - .WithReference(cache) - .WaitFor(cache); -``` +- **App composition**: Specify the projects, containers, executables, and cloud resources that make up your application. +- **Service discovery and connection string management**: Automatically inject the right connection strings and network configurations. -For more information, see [.NET Aspire orchestration overview](../fundamentals/app-host-overview.md). +It's important to note that Aspire's orchestration focuses on enhancing the _local development_ experience. It's not intended to replace production systems like Kubernetes, but rather provides abstractions that eliminate low-level implementation details during development. -> [!IMPORTANT] -> The call to creates a new Redis container in your local dev environment. If you'd rather use an existing Redis instance, you can use the method to reference an existing connection string. For more information, see [Reference existing resources](../fundamentals/app-host-overview.md#reference-existing-resources). +For more information, see [Aspire orchestration overview](../fundamentals/app-host-overview.md). -## .NET Aspire integrations +## Aspire integrations -[.NET Aspire integrations](../fundamentals/integrations-overview.md) are NuGet packages designed to simplify connections to popular services and platforms, such as Redis or PostgreSQL. .NET Aspire integrations handle cloud resource setup and interaction for you through standardized patterns, such as adding health checks and telemetry. Integrations are two-fold - ["hosting" integrations](../fundamentals/integrations-overview.md#hosting-integrations) represents the service you're connecting to, and ["client" integrations](../fundamentals/integrations-overview.md#client-integrations) represents the client or consumer of that service. In other words, for many hosting packages there's a corresponding client package that handles the service connection within your code. +Aspire makes it easy to define everything your app needs using integrations—NuGet packages designed to simplify connections to popular services and platforms. Each integration handles cloud resource setup and provides standardized patterns for health checks, telemetry, and configuration. -Each integration is designed to work with the .NET Aspire app host, and their configurations are injected automatically by [referencing named resources](../fundamentals/app-host-overview.md#reference-resources). In other words, if _Example.ServiceFoo_ references _Example.ServiceBar_, _Example.ServiceFoo_ inherits the integration's required configurations to allow them to communicate with each other automatically. +Resources you can integrate include: -For example, consider the following code using the .NET Aspire Service Bus integration: +- **AI Services**: Large Language Models, AI endpoints, and cognitive services. +- **Caches**: Redis, in-memory caches, and distributed caching solutions. +- **Containers**: Docker containers for databases, message brokers, and other services. +- **Databases**: SQL Server, PostgreSQL, MySQL, MongoDB, and other data stores. +- **Executables**: Console applications, scripts, and background services. +- **Frameworks**: Web applications, APIs, and microservices built with various frameworks. +- **Messaging Services**: Azure Service Bus, RabbitMQ, Kafka, and other messaging systems. +- **Projects**: .NET projects, Node.js applications, Python services, and more. +- **Storage**: Blob storage, file systems, and cloud storage services. -```csharp -builder.AddAzureServiceBusClient("servicebus"); -``` +Integrations are two-fold: "hosting" integrations represent the service you're connecting to, while "client" integrations represent the consumer of that service. + +> [!TIP] +> Under the hood, a _hosting_ [integration](../fundamentals/integrations-overview.md) can represent a container, an executable, or even just C# code that configures resources without running a separate process. You can add any container image, codebase, script, or cloud resource to your app host. Creating reusable Aspire integrations is similar to building reusable components for your apps. -The method handles the following concerns: +## Monitor and troubleshoot with the Aspire dashboard -- Registers a as a singleton in the DI container for connecting to Azure Service Bus. -- Applies configurations either inline through code or through configuration. -- Enables corresponding health checks, logging, and telemetry specific to the Azure Service Bus usage. +Aspire includes a powerful developer dashboard that gives you real-time visibility into your distributed app. The dashboard lets you inspect resources, view logs, traces, and metrics, and manage your app's services—all from a single UI. -A full list of available integrations is detailed on the [.NET Aspire integrations](../fundamentals/integrations-overview.md) overview page. +When you run your Aspire app, the dashboard launches automatically. You can: -## Project templates and tooling +- See all your app's resources and their status. +- Drill into logs, traces, and metrics for any service. +- Start, stop, or restart resources directly from the dashboard. +- Visualize dependencies and troubleshoot issues faster. -.NET Aspire provides a set of project templates and tooling experiences for Visual Studio, Visual Studio Code, and the [.NET CLI](/dotnet/core/tools/). These templates are designed to help you create and interact with .NET Aspire projects, or add .NET Aspire into your existing codebase. The templates include a set of opinionated defaults to help you get started quickly - for example, it has boilerplate code for turning on health checks and logging in .NET apps. These defaults are fully customizable, so you can edit and adapt them to suit your needs. +The dashboard is available both as part of an Aspire solution or as a [standalone tool](../fundamentals/dashboard/standalone.md) for any app that emits OpenTelemetry data. -.NET Aspire templates also include boilerplate extension methods that handle common service configurations for you: +Learn more in the [dashboard overview](../fundamentals/dashboard/overview.md), or dive deeper into [dashboard features and usage](../fundamentals/dashboard/explore.md). + +## From development to deployment + +When you compose your distributed app in Aspire's app host, you're not just defining services for local development—you're setting up the foundation for deployment. The same composition you use to run and debug locally becomes the blueprint for production deployment, ensuring consistency from development through to production. + +Aspire provides project templates and tooling experiences for your favorite development environments. These [templates include opinionated defaults](../fundamentals/aspire-sdk-templates.md) with boilerplate code for health checks, logging, and telemetry. The templates also include service defaults that handle common configurations: ```csharp builder.AddServiceDefaults(); ``` -For more information on what `AddServiceDefaults` does, see [.NET Aspire service defaults](../fundamentals/service-defaults.md). +When added to your C# code, this method configures: + +- **OpenTelemetry**: Formatted logging, runtime metrics, and tracing for ASPCore, gRPC, and HTTP. +- **Health checks**: Default endpoints that tools can query to monitor your app. +- **Service discovery**: Enables service discovery and configures accordingly. + +For more information, see [Aspire service defaults](../fundamentals/service-defaults.md). + +Consider how the three-tier architecture example can be deployed across different environments: + +| **Resource** | **Local development** | **Azure** | **AWS** | +|----------|-------------------|-------|-----|---------| +| Frontend | `npm run` | Azure Container Apps | Amazon Elastic Container Service | +| API service | `dotnet run` | Azure Container Apps | AWS Lambda | +| Database | `docker.io/library/postgres` | Azure Database for PostgreSQL | Amazon Relational Database Service | + +> [!TIP] +> These are just a few examples of how you can deploy Aspire apps. -When added to your _:::no-loc text="Program.cs":::_ file, the preceding code handles the following concerns: +Aspire's deployment capabilities are flexible and don't interfere with your existing workflows. You can continue using your preferred tools and services while benefiting from the consistent app topology defined in your app host. -- **OpenTelemetry**: Sets up formatted logging, runtime metrics, built-in meters, and tracing for ASP.NET Core, gRPC, and HTTP. For more information, see [.NET Aspire telemetry](../fundamentals/telemetry.md). -- **Default health checks**: Adds default health check endpoints that tools can query to monitor your app. For more information, see [.NET app health checks in C#](/dotnet/core/diagnostics/diagnostic-health-checks). -- **Service discovery**: Enables [service discovery](../service-discovery/overview.md) for the app and configures accordingly. +For more information, see [Deploy Aspire apps](../deployment/overview.md). ## Next steps > [!div class="nextstepaction"] -> [Quickstart: Build your first .NET Aspire project](build-your-first-aspire-app.md) +> [Build your first Aspire solution](build-your-first-aspire-app.md) diff --git a/docs/get-started/build-your-first-aspire-app.md b/docs/get-started/build-your-first-aspire-app.md index 48a29a2a04..bd3cce6579 100644 --- a/docs/get-started/build-your-first-aspire-app.md +++ b/docs/get-started/build-your-first-aspire-app.md @@ -221,6 +221,12 @@ The preceding code: For more information, see [Make HTTP requests with the `HttpClient`](/dotnet/fundamentals/networking/http/httpclient) class. +## 🖥️ Aspire CLI + +🧪 The Aspire CLI is **still in preview** and under active development. Expect more features and polish in future releases. + +[!INCLUDE [install-aspire-cli](../includes/install-aspire-cli.md)] + ## See also - [.NET Aspire integrations overview](../fundamentals/integrations-overview.md) diff --git a/docs/get-started/snippets/PythonSample/PythonSample.AppHost/PythonSample.AppHost.csproj b/docs/get-started/snippets/PythonSample/PythonSample.AppHost/PythonSample.AppHost.csproj index 3c68895f8f..dc4d1a2920 100644 --- a/docs/get-started/snippets/PythonSample/PythonSample.AppHost/PythonSample.AppHost.csproj +++ b/docs/get-started/snippets/PythonSample/PythonSample.AppHost/PythonSample.AppHost.csproj @@ -1,6 +1,6 @@ - + Exe @@ -11,8 +11,8 @@ - - + + \ No newline at end of file diff --git a/docs/get-started/snippets/PythonSample/PythonSample.ServiceDefaults/PythonSample.ServiceDefaults.csproj b/docs/get-started/snippets/PythonSample/PythonSample.ServiceDefaults/PythonSample.ServiceDefaults.csproj index ae37426e09..9183e963ee 100644 --- a/docs/get-started/snippets/PythonSample/PythonSample.ServiceDefaults/PythonSample.ServiceDefaults.csproj +++ b/docs/get-started/snippets/PythonSample/PythonSample.ServiceDefaults/PythonSample.ServiceDefaults.csproj @@ -11,7 +11,7 @@ - + diff --git a/docs/get-started/snippets/quickstart/AspireSample/AspireSample.AppHost/AspireSample.AppHost.csproj b/docs/get-started/snippets/quickstart/AspireSample/AspireSample.AppHost/AspireSample.AppHost.csproj index c05eabda91..6a16cc8015 100644 --- a/docs/get-started/snippets/quickstart/AspireSample/AspireSample.AppHost/AspireSample.AppHost.csproj +++ b/docs/get-started/snippets/quickstart/AspireSample/AspireSample.AppHost/AspireSample.AppHost.csproj @@ -1,6 +1,6 @@ - + Exe @@ -16,8 +16,8 @@ - - + + diff --git a/docs/get-started/snippets/quickstart/AspireSample/AspireSample.ServiceDefaults/AspireSample.ServiceDefaults.csproj b/docs/get-started/snippets/quickstart/AspireSample/AspireSample.ServiceDefaults/AspireSample.ServiceDefaults.csproj index ae37426e09..9183e963ee 100644 --- a/docs/get-started/snippets/quickstart/AspireSample/AspireSample.ServiceDefaults/AspireSample.ServiceDefaults.csproj +++ b/docs/get-started/snippets/quickstart/AspireSample/AspireSample.ServiceDefaults/AspireSample.ServiceDefaults.csproj @@ -11,7 +11,7 @@ - + diff --git a/docs/get-started/snippets/quickstart/AspireSample/AspireSample.Web/AspireSample.Web.csproj b/docs/get-started/snippets/quickstart/AspireSample/AspireSample.Web/AspireSample.Web.csproj index 958c813289..b5ee8c0011 100644 --- a/docs/get-started/snippets/quickstart/AspireSample/AspireSample.Web/AspireSample.Web.csproj +++ b/docs/get-started/snippets/quickstart/AspireSample/AspireSample.Web/AspireSample.Web.csproj @@ -11,7 +11,7 @@ - + diff --git a/docs/github/github-models-integration.md b/docs/github/github-models-integration.md new file mode 100644 index 0000000000..860f8ca17b --- /dev/null +++ b/docs/github/github-models-integration.md @@ -0,0 +1,333 @@ +--- +title: GitHub Models integration +description: Learn how to integrate .NET Aspire with GitHub Models for AI model access and management. +ms.date: 07/25/2025 +ai-usage: ai-assisted +--- + +# .NET Aspire GitHub Models integration (Preview) + +[!INCLUDE [includes-hosting-and-client](../includes/includes-hosting-and-client.md)] + +[GitHub Models](https://docs.github.com/github-models) provides access to various AI models including OpenAI's GPT models, DeepSeek, Microsoft's Phi models, and other leading AI models, all accessible through GitHub's infrastructure. The .NET Aspire GitHub Models integration enables you to connect to GitHub Models from your .NET applications for prototyping and production scenarios. + +## Hosting integration + +The .NET Aspire [GitHub Models](https://docs.github.com/github-models) hosting integration models GitHub Models resources as `GitHubModelResource`. To access these types and APIs for expressing them within your [AppHost project](../fundamentals/app-host-overview.md), install the [📦 Aspire.Hosting.GitHub.Models](https://www.nuget.org/packages/Aspire.Hosting.GitHub.Models) NuGet package: + +### [.NET CLI](#tab/dotnet-cli) + +```dotnetcli +dotnet add package Aspire.Hosting.GitHub.Models +``` + +### [PackageReference](#tab/package-reference) + +```xml + +``` + +--- + +For more information, see [dotnet add package](/dotnet/core/tools/dotnet-add-package) or [Manage package dependencies in .NET applications](/dotnet/core/tools/dependencies). + +### Add a GitHub Model resource + +To add a `GitHubModelResource` to your app host project, call the `AddGitHubModel` method: + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +var chat = builder.AddGitHubModel("chat", "openai/gpt-4o-mini"); + +builder.AddProject() + .WithReference(chat); + +// 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. + +### Specify an organization + +For organization-specific requests, you can specify an organization parameter: + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +var organization = builder.AddParameter("github-org"); +var chat = builder.AddGitHubModel("chat", "openai/gpt-4o-mini", organization); + +builder.AddProject() + .WithReference(chat); + +// After adding all resources, run the app... +``` + +When an organization is specified, the token must be attributed to that organization in GitHub. + +### Configure API key authentication + +The GitHub Models integration supports multiple ways to configure authentication: + +#### Default API key parameter + +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"); +``` + +Then in user secrets: + +```json +{ + "Parameters": { + "chat-gh-apikey": "YOUR_GITHUB_TOKEN_HERE" + } +} +``` + +#### Custom API key parameter + +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") + .WithApiKey(apiKey); +``` + +Then in user secrets: + +```json +{ + "Parameters": { + "my-api-key": "YOUR_GITHUB_TOKEN_HERE" + } +} +``` + +### Health checks + +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") + .WithHealthCheck(); +``` + +> [!IMPORTANT] +> Because health checks are included in the rate limit of the GitHub Models API, use this health check sparingly, such as when debugging connectivity issues. The health check runs only once per application instance to minimize API usage. + +### Available models + +GitHub Models supports various AI models. Some popular options include: + +- `openai/gpt-4o-mini` +- `openai/gpt-4o` +- `deepseek/DeepSeek-V3-0324` +- `microsoft/Phi-4-mini-instruct` + +Check the [GitHub Models documentation](https://docs.github.com/github-models) for the most up-to-date list of available models. + +## Client integration + +To get started with the .NET Aspire GitHub Models client integration, you can use either the Azure AI Inference client or the OpenAI client, depending on your needs and model compatibility. + +### Using Azure AI Inference client + +Install the [📦 Aspire.Azure.AI.Inference](https://www.nuget.org/packages/Aspire.Azure.AI.Inference) NuGet package in the client-consuming project: + +### [.NET CLI](#tab/dotnet-cli) + +```dotnetcli +dotnet add package Aspire.Azure.AI.Inference +``` + +### [PackageReference](#tab/package-reference) + +```xml + +``` + +--- + +#### Add a ChatCompletionsClient + +In the _Program.cs_ file of your client-consuming project, use the `AddAzureChatCompletionsClient` method to register a `ChatCompletionsClient` for dependency injection: + +```csharp +builder.AddAzureChatCompletionsClient("chat"); +``` + +You can then retrieve the `ChatCompletionsClient` instance using dependency injection: + +```csharp +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) + }); + + return response.Value.Choices[0].Message.Content; + } +} +``` + +#### Add ChatCompletionsClient with registered IChatClient + +If you're using the Microsoft.Extensions.AI abstractions, you can register an `IChatClient`: + +```csharp +builder.AddAzureChatCompletionsClient("chat") + .AddChatClient(); +``` + +Then use it in your services: + +```csharp +public class StoryService(IChatClient chatClient) +{ + public async Task GenerateStoryAsync(string prompt) + { + var response = await chatClient.GetResponseAsync(prompt); + + return response.Text; + } +} +``` + +### Using OpenAI client + +For models compatible with the OpenAI API (such as `openai/gpt-4o-mini`), you can use the OpenAI client. Install the [📦 Aspire.OpenAI](https://www.nuget.org/packages/Aspire.OpenAI) NuGet package: + +### [.NET CLI](#tab/dotnet-cli) + +```dotnetcli +dotnet add package Aspire.OpenAI +``` + +### [PackageReference](#tab/package-reference) + +```xml + +``` + +--- + +#### Add an OpenAI client + +```csharp +builder.AddOpenAIClient("chat"); +``` + +You can then use the OpenAI client: + +```csharp +public class ChatService(OpenAIClient client) +{ + public async Task GetChatResponseAsync(string prompt) + { + var chatClient = client.GetChatClient("openai/gpt-4o-mini"); + + var response = await chatClient.CompleteChatAsync( + new[] + { + new UserChatMessage(prompt) + }); + + return response.Value.Content[0].Text; + } +} +``` + +#### Add OpenAI client with registered IChatClient + +```csharp +builder.AddOpenAIClient("chat") + .AddChatClient(); +``` + +### Configuration + +The GitHub Models integration supports configuration through user secrets, environment variables, or app settings. The integration automatically uses the `GITHUB_TOKEN` environment variable if available, or you can specify a custom API key parameter. + +#### Authentication + +The GitHub Models integration requires a GitHub personal access token with `models: read` permission. The token can be provided in several ways: + +##### Environment variables in Codespaces and GitHub Actions + +When running an app in GitHub Codespaces or GitHub Actions, the `GITHUB_TOKEN` environment variable is automatically available and can be used without additional configuration. This token has the necessary permissions to access GitHub Models for the repository context. + +```csharp +// No additional configuration needed in Codespaces/GitHub Actions +var chat = builder.AddGitHubModel("chat", "openai/gpt-4o-mini"); +``` + +##### Personal access tokens for local development + +For local development, you need to create a [fine-grained personal access token](https://github.com/settings/tokens) with the `models: read` scope and configure it in user secrets: + +```json +{ + "Parameters": { + "chat-gh-apikey": "github_pat_YOUR_TOKEN_HERE" + } +} +``` + +#### Connection string format + +The connection string follows this format: + +```Plaintext +Endpoint=https://models.github.ai/inference;Key={api_key};Model={model_name};DeploymentId={model_name} +``` + +For organization-specific requests: + +```Plaintext +Endpoint=https://models.github.ai/orgs/{organization}/inference;Key={api_key};Model={model_name};DeploymentId={model_name} +``` + +### Rate limits and costs + +> [!IMPORTANT] +> Each model has rate limits that vary by model and usage tier. Some models include costs if you exceed free tier limits. Check the [GitHub Models documentation](https://docs.github.com/github-models) for current rate limits and pricing information. + +> [!TIP] +> Use health checks sparingly to avoid consuming your rate limit allowance. The integration caches health check results to minimize API calls. + +### Sample application + +The `dotnet/aspire` repo contains an example application demonstrating the GitHub Models integration. You can find the sample in the [Aspire GitHub repository](https://github.com/dotnet/aspire/tree/main/playground/GitHubModelsEndToEnd). + +[!INCLUDE [integration-observability-and-telemetry](../includes/integration-observability-and-telemetry.md)] + +### Logging + +The GitHub Models integration uses standard HTTP client logging categories: + +- `System.Net.Http.HttpClient` +- `Microsoft.Extensions.Http` + +### Tracing + +HTTP requests to the GitHub Models API are automatically traced when using the Azure AI Inference or OpenAI clients. + +## See also + +- [GitHub Models](https://docs.github.com/github-models) +- [.NET Aspire integrations overview](../fundamentals/integrations-overview.md) +- [.NET Aspire GitHub repo](https://github.com/dotnet/aspire) +- [GitHub Models API documentation](https://docs.github.com/rest/models/inference) diff --git a/docs/includes/aspire-prereqs.md b/docs/includes/aspire-prereqs.md index 3e9b4703da..9b125a9bbe 100644 --- a/docs/includes/aspire-prereqs.md +++ b/docs/includes/aspire-prereqs.md @@ -2,7 +2,8 @@ To work with .NET Aspire, you need the following installed locally: -- [.NET 8.0](https://dotnet.microsoft.com/download/dotnet/8.0) or [.NET 9.0](https://dotnet.microsoft.com/download/dotnet/9.0) +- [.NET 8.0](https://dotnet.microsoft.com/download/dotnet/8.0) or [.NET 9.0](https://dotnet.microsoft.com/download/dotnet/9.0). + - Starting with .NET Aspire 9.4, [.NET 10 Preview 5 or later](https://dotnet.microsoft.com/download/dotnet/10.0) is supported. - An OCI compliant container runtime, such as: - [Docker Desktop](https://www.docker.com/products/docker-desktop) or [Podman](https://podman.io/). For more information, see [Container runtime](../fundamentals/setup-tooling.md#container-runtime). - An Integrated Developer Environment (IDE) or code editor, such as: diff --git a/docs/includes/install-aspire-cli.md b/docs/includes/install-aspire-cli.md new file mode 100644 index 0000000000..dac344239a --- /dev/null +++ b/docs/includes/install-aspire-cli.md @@ -0,0 +1,127 @@ +--- +title: Install the Aspire CLI +ms.date: 07/25/2025 +--- + +### [Unix](#tab/unix) + +To install the Aspire CLI, run the following command in your terminal: + +```sh +curl -sSL https://aspire.dev/install.sh | bash +``` + +#### Script options + +The Unix install script (`install.sh`) supports the following options: + +| Parameter | Short form | Description | Default value | +|-----------|------------|-------------|---------------| +| `--install-path` | `-i` | Directory to install the CLI | `$HOME/.aspire/bin` | +| `--version` | | Version of the Aspire CLI to download | `9.4` | +| `--quality` | `-q` | Quality to download (`dev`, `staging`, `release`) | `release` | +| `--os` | | Operating system (auto-detected if not specified) | auto-detect | +| `--arch` | | Architecture (auto-detected if not specified) | auto-detect | +| `--keep-archive` | `-k` | Keep downloaded archive files after installation | `false` | +| `--verbose` | `-v` | Enable verbose output | `false` | +| `--help` | `-h` | Show help message | | + +#### Quality options + +The `--quality` option determines which build of the Aspire CLI to install: + +- **`dev`**: Latest builds from the `main` branch (development builds). +- **`staging`**: Builds from the current release branch (pre-release builds). +- **`release`**: Latest generally available release (stable builds). + +#### Usage examples + +Install to a custom directory: + +```sh +curl -sSL https://aspire.dev/install.sh | bash -s -- --install-path "/usr/local/bin" +``` + +Install with verbose output: + +```sh +curl -sSL https://aspire.dev/install.sh | bash -s -- --verbose +``` + +Install a specific version: + +```sh +curl -sSL https://aspire.dev/install.sh | bash -s -- --version "9.4" +``` + +Install development builds: + +```sh +curl -sSL https://aspire.dev/install.sh | bash -s -- --quality "dev" +``` + +#### Default installation path + +The script installs the Aspire CLI to `$HOME/.aspire/bin` by default. + +### [Windows](#tab/windows) + +To install the Aspire CLI, run the following command in your terminal: + +```powershell +Invoke-Expression "& { $(Invoke-RestMethod https://aspire.dev/install.ps1) }" +``` + +#### Script options + +The Windows install script (`install.ps1`) supports the following options: + +| Parameter | Description | Default value | +|-----------|-------------|---------------| +| `-InstallPath` | Directory to install the CLI | `%USERPROFILE%\.aspire\bin` | +| `-Version` | Version of the Aspire CLI to download | `9.4` | +| `-Quality` | Quality to download (`dev`, `staging`, `release`) | `release` | +| `-OS` | Operating system (auto-detected if not specified) | auto-detect | +| `-Architecture` | Architecture (auto-detected if not specified) | auto-detect | +| `-KeepArchive` | Keep downloaded archive files after installation | `false` | +| `-Help` | Show help message | | + +#### Quality options + +The `-Quality` option determines which build of the Aspire CLI to install: + +- **`dev`**: Latest builds from the `main` branch (development builds). +- **`staging`**: Builds from the current release branch (pre-release builds). +- **`release`**: Latest generally available release (stable builds). + +#### Usage examples + +Install to a custom directory: + +```powershell +Invoke-Expression "& { $(Invoke-RestMethod https://aspire.dev/install.ps1) } -InstallPath 'C:\Tools\Aspire'" +``` + +Install with verbose output: + +```powershell +Invoke-Expression "& { $(Invoke-RestMethod https://aspire.dev/install.ps1) } -Verbose" +``` + +Install a specific version: + +```powershell +Invoke-Expression "& { $(Invoke-RestMethod https://aspire.dev/install.ps1) } -Version '9.4'" +``` + +Install development builds: + +```powershell +Invoke-Expression "& { $(Invoke-RestMethod https://aspire.dev/install.ps1) } -Quality 'dev'" +``` + +#### Default installation path + +The script installs the Aspire CLI to `%USERPROFILE%\.aspire\bin` by default. + +--- diff --git a/docs/messaging/azure-event-hubs-integration.md b/docs/messaging/azure-event-hubs-integration.md index 50ed63821e..a87a1dd84c 100644 --- a/docs/messaging/azure-event-hubs-integration.md +++ b/docs/messaging/azure-event-hubs-integration.md @@ -1,7 +1,7 @@ --- title: .NET Aspire Azure Event Hubs integration description: This article describes the .NET Aspire Azure Event Hubs integration features and capabilities. -ms.date: 04/03/2025 +ms.date: 07/22/2025 --- # .NET Aspire Azure Event Hubs integration @@ -63,11 +63,11 @@ When you add an Azure Event Hubs resource to the app host, it exposes other usef If you're new to [Bicep](/azure/azure-resource-manager/bicep/overview), it's a domain-specific language for defining Azure resources. With .NET Aspire, you don't need to write Bicep by-hand, instead the provisioning APIs generate Bicep for you. When you publish your app, the generated Bicep is output alongside the manifest file. When you add an Azure Event Hubs resource, the following Bicep is generated: -:::code language="bicep" source="../snippets/azure/AppHost/event-hubs.module.bicep"::: +:::code language="bicep" source="../snippets/azure/AppHost/event-hubs/event-hubs.bicep"::: The preceding Bicep is a module that provisions an Azure Event Hubs resource. Additionally, role assignments are created for the Azure resource in a separate module: -:::code language="bicep" source="../snippets/azure/AppHost/event-hubs-roles.module.bicep"::: +:::code language="bicep" source="../snippets/azure/AppHost/event-hubs-roles/event-hubs-roles.bicep"::: The generated Bicep is a starting point and is influenced by changes to the provisioning infrastructure in C#. Customizations to the Bicep file directly will be overwritten, so make changes through the C# provisioning APIs to ensure they are reflected in the generated files. diff --git a/docs/messaging/azure-service-bus-integration.md b/docs/messaging/azure-service-bus-integration.md index 0c490cfd2c..5099b8a9e3 100644 --- a/docs/messaging/azure-service-bus-integration.md +++ b/docs/messaging/azure-service-bus-integration.md @@ -1,7 +1,7 @@ --- title: .NET Aspire Azure Service Bus integration description: Learn how to install and configure the .NET Aspire Azure Service Bus integration to connect to Azure Service Bus instances from .NET applications. -ms.date: 04/01/2025 +ms.date: 07/22/2025 --- # .NET Aspire Azure Service Bus integration @@ -60,11 +60,11 @@ When you add an to the app h If you're new to Bicep, it's a domain-specific language for defining Azure resources. With .NET Aspire, you don't need to write Bicep by-hand, instead the provisioning APIs generate Bicep for you. When you publish your app, the generated Bicep is output alongside the manifest file. When you add an Azure Service Bus resource, the following Bicep is generated: -:::code language="bicep" source="../snippets/azure/AppHost/service-bus.module.bicep"::: +:::code language="bicep" source="../snippets/azure/AppHost/service-bus/service-bus.bicep"::: The preceding Bicep is a module that provisions an Azure Service Bus namespace resource. Additionally, role assignments are created for the Azure resource in a separate module: -:::code language="bicep" source="../snippets/azure/AppHost/service-bus-roles.module.bicep"::: +:::code language="bicep" source="../snippets/azure/AppHost/service-bus-roles/service-bus-roles.bicep"::: In addition to the Service Bus namespace, it also provisions an Azure role-based access control (Azure RBAC) built-in role of Azure Service Bus Data Owner. The role is assigned to the Service Bus namespace's resource group. For more information, see [Azure Service Bus Data Owner](/azure/role-based-access-control/built-in-roles/integration#azure-service-bus-data-owner). diff --git a/docs/messaging/azure-web-pubsub-integration.md b/docs/messaging/azure-web-pubsub-integration.md index 6f1a75dbff..ffa8618b82 100644 --- a/docs/messaging/azure-web-pubsub-integration.md +++ b/docs/messaging/azure-web-pubsub-integration.md @@ -2,7 +2,7 @@ title: .NET Aspire Azure Web PubSub integration description: This article describes the .NET Aspire Azure Web PubSub integration features and capabilities. ms.topic: how-to -ms.date: 04/09/2025 +ms.date: 07/22/2025 --- # .NET Aspire Azure Web PubSub integration @@ -99,11 +99,11 @@ When you publish your app, .NET Aspire provisioning APIs generate Bicep alongsid When you add an Azure Web PubSub resource, the following Bicep is generated: -:::code language="bicep" source="../snippets/azure/AppHost/web-pubsub.module.bicep"::: +:::code language="bicep" source="../snippets/azure/AppHost/web-pubsub/web-pubsub.bicep"::: The preceding Bicep is a module that provisions an Azure Web PubSub resource. Additionally, role assignments are created for the Azure resource in a separate module: -:::code language="bicep" source="../snippets/azure/AppHost/web-pubsub-roles.module.bicep"::: +:::code language="bicep" source="../snippets/azure/AppHost/web-pubsub-roles/web-pubsub-roles.bicep"::: The generated Bicep is a starting point and is influenced by changes to the provisioning infrastructure in C#. Customizations to the Bicep file directly will be overwritten, so make changes through the C# provisioning APIs to ensure they are reflected in the generated files. diff --git a/docs/messaging/messaging-integrations.md b/docs/messaging/messaging-integrations.md index a8d4a4498c..670e97ecdd 100644 --- a/docs/messaging/messaging-integrations.md +++ b/docs/messaging/messaging-integrations.md @@ -230,7 +230,7 @@ Add the [.NET Aspire Azure Service Bus](azure-service-bus-integration.md) integr ```xml + Version="*" /> ``` diff --git a/docs/proxies/yarp-integration.md b/docs/proxies/yarp-integration.md new file mode 100644 index 0000000000..b61f6a6f9e --- /dev/null +++ b/docs/proxies/yarp-integration.md @@ -0,0 +1,314 @@ +--- +title: YARP integration +description: Learn how to use the .NET Aspire YARP reverse proxy integration, which includes hosting integration for containerized YARP instances. +ms.date: 07/25/2025 +ai-usage: ai-assisted +--- + +# .NET Aspire YARP integration + +[!INCLUDE [includes-hosting](../includes/includes-hosting.md)] + +[YARP (Yet Another Reverse Proxy)](https://microsoft.github.io/reverse-proxy/) is a toolkit for developing high-performance reverse proxy applications. The .NET Aspire YARP integration enables you to create containerized YARP reverse proxy instances with programmatic configuration or external configuration files. + +## Hosting integration + +The YARP hosting integration models a containerized YARP reverse proxy as the `YarpResource` type . To access this type and its APIs add the [📦 Aspire.Hosting.Yarp](https://www.nuget.org/packages/Aspire.Hosting.Yarp) NuGet package in the [app host](xref:dotnet/aspire/app-host) project. + +### [.NET CLI](#tab/dotnet-cli) + +```dotnetcli +dotnet add package Aspire.Hosting.Yarp +``` + +### [PackageReference](#tab/package-reference) + +```xml + +``` + +--- + +For more information, see [dotnet add package](/dotnet/core/tools/dotnet-add-package) or [Manage package dependencies in .NET applications](/dotnet/core/tools/dependencies). + +### Add YARP resource + +In your app host project, call `AddYarp` on the `builder` instance to add a YARP resource: + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +var catalogService = builder.AddProject("catalogservice"); +var basketService = builder.AddProject("basketservice"); + +var gateway = builder.AddYarp("gateway") + .WithConfiguration(yarp => + { + // Configure routes programmatically + yarp.AddRoute(catalogService); + yarp.AddRoute("/api/{**catch-all}", basketService); + }); + +// After adding all resources, run the app... +``` + +When .NET Aspire adds a YARP resource to the app host, it creates a new containerized YARP instance using the [mcr.microsoft.com/dotnet/nightly/yarp](https://mcr.microsoft.com/product/dotnet/nightly/yarp/about) container image. This official Microsoft container image contains a preconfigured YARP reverse proxy server that can be dynamically configured through Aspire's hosting APIs. + +The container image provides: + +- A lightweight YARP reverse proxy server. +- Support for dynamic configuration through JSON files or programmatic APIs. +- Integration with .NET service discovery. +- Built-in health checks and monitoring capabilities. + +The YARP resource can be configured programmatically using the `WithConfiguration` method or through external configuration files. + +### Add YARP resource with external configuration + +To configure the YARP resource using an external JSON configuration file, use the `WithConfigFile` method: + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +var catalogService = builder.AddProject("catalogservice"); +var basketService = builder.AddProject("basketservice"); + +var gateway = builder.AddYarp("gateway") + .WithConfigFile("yarp.json") + .WithReference(catalogService) + .WithReference(basketService); + +// After adding all resources, run the app... +``` + +The `yarp.json` configuration file can reference services using their resource names: + +```json +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Information" + } + }, + "AllowedHosts": "*", + "ReverseProxy": { + "Routes": { + "catalog": { + "ClusterId": "catalog", + "Match": { + "Path": "/catalog/{**catch-all}" + }, + "Transforms": [ + { "PathRemovePrefix": "/catalog" } + ] + }, + "basket": { + "ClusterId": "basket", + "Match": { + "Path": "/basket/{**catch-all}" + }, + "Transforms": [ + { "PathRemovePrefix": "/basket" } + ] + } + }, + "Clusters": { + "catalog": { + "Destinations": { + "catalog": { + "Address": "https://catalogservice" + } + } + }, + "basket": { + "Destinations": { + "basket": { + "Address": "https://basketservice" + } + } + } + } + } +} +``` + +> [!NOTE] +> When you use the `WithConfigFile`, programmatic configuration via `WithConfiguration` isn't supported. You must choose one approach or the other. + +### Programmatic configuration + +The YARP integration provides a fluent API for configuring routes, clusters, and transforms programmatically using the `IYarpConfigurationBuilder` : + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +var catalogService = builder.AddProject("catalogservice"); +var basketService = builder.AddProject("basketservice"); + +var gateway = builder.AddYarp("gateway") + .WithConfiguration(yarp => + { + // Add catch-all route for frontend service + yarp.AddRoute(catalogService); + + // Add specific path route with transforms + yarp.AddRoute("/api/{**catch-all}", basketService) + .WithTransformPathRemovePrefix("/api"); + + // Configure route matching + yarp.AddRoute("/catalog/api/{**catch-all}", catalogService) + .WithMatchMethods("GET", "POST") + .WithTransformPathRemovePrefix("/catalog"); + }); + +// After adding all resources, run the app... +``` + +#### Route configuration + +Routes define how incoming requests are matched and forwarded to backend services. The YARP integration provides several methods for configuring routes: + +- `AddRoute(resource)` - Creates a catch-all route for the specified resource +- `AddRoute(path, resource)` - Creates a route with a specific path pattern +- `AddRoute(path, externalService)` - Creates a route targeting an external service + +#### Route matching + +You can configure route matching criteria using various `WithMatch*` methods: + +```csharp +yarp.AddRoute("/api/{**catch-all}", basketService) + .WithMatchMethods("GET", "POST") + .WithMatchHeaders(new RouteHeader("X-Version", "v1")); +``` + +#### Transforms + +Transforms modify requests and responses as they pass through the proxy. The YARP integration supports various transform extensions: + +```csharp +yarp.AddRoute("/api/{**catch-all}", basketService) + .WithTransformPathRemovePrefix("/api") + .WithTransformPathPrefix("/v1") + .WithTransformRequestHeader("X-Forwarded-Host", "gateway.example.com") + .WithTransformResponseHeader("X-Powered-By", "YARP"); +``` + +Common transform methods include: + +- **Path transforms**: `WithTransformPathRemovePrefix`, `WithTransformPathPrefix`, `WithTransformPathSet` +- **Header transforms**: `WithTransformRequestHeader`, `WithTransformResponseHeader` +- **Query transforms**: `WithTransformQueryParameter`, `WithTransformQueryRemoveParameter` +- **Custom transforms**: `WithTransform` for custom transformation logic + +#### Cluster configuration + +Clusters define backend destinations and can be configured with load balancing, health checks, and other policies: + +```csharp +yarp.AddRoute("/api/{**catch-all}", basketService) + .AddCluster(basketService) + .WithLoadBalancingPolicy("Random") + .WithHealthCheckConfig(new HealthCheckConfig + { + Active = new ActiveHealthCheckConfig + { + Enabled = true, + Interval = TimeSpan.FromSeconds(30), + Timeout = TimeSpan.FromSeconds(5) + } + }) + .WithSessionAffinityConfig(new SessionAffinityConfig + { + Enabled = true, + Policy = "Cookie" + }); +``` + +### Customize host port + +To configure the host port that the YARP resource is exposed on, use the `WithHostPort` method: + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +var gateway = builder.AddYarp("gateway") + .WithHostPort(8080) + .WithConfiguration(yarp => + { + // Configure routes... + }); + +// After adding all resources, run the app... +``` + +By default, YARP uses a randomly assigned port. Using `WithHostPort` allows you to specify a fixed port for consistent access during development. + +### Service discovery integration + + + +The YARP integration automatically works with .NET service discovery when targeting resources that implement `IResourceWithServiceDiscovery`. This enables dynamic endpoint resolution for backend services: + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +var catalogService = builder.AddProject("catalogservice"); + +var gateway = builder.AddYarp("gateway") + .WithConfiguration(yarp => + { + // Service discovery automatically resolves catalogservice endpoints + yarp.AddRoute("/catalog/{**catch-all}", catalogService); + }); +``` + + + +For external services, use `AddExternalService`: + +```csharp +var externalApi = builder.AddExternalService("external-api") + .WithHttpsEndpoint("https://api.example.com"); + +var gateway = builder.AddYarp("gateway") + .WithConfiguration(yarp => + { + yarp.AddRoute("/external/{**catch-all}", externalApi); + }); +``` + +## Client integration + +There's no client integration provided for YARP. YARP operates as a reverse proxy server that sits between clients and backend services. Applications interact with YARP through standard HTTP requests to the proxy endpoints, not through a dedicated client library. + +To consume services through a YARP proxy in your .NET applications, use standard HTTP client patterns: + +```csharp +// In your consuming application +var httpClient = new HttpClient(); +var response = await httpClient.GetAsync("https://gateway/api/catalog/products"); +``` + +For applications within the same .NET Aspire solution, you can reference the YARP resource directly: + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +var gateway = builder.AddYarp("gateway") + .WithConfiguration(/* ... */); + +// Reference the YARP gateway from other services +builder.AddProject("webapp") + .WithReference(gateway); +``` + +## See also + +- [YARP documentation](https://microsoft.github.io/reverse-proxy/) +- [.NET Aspire orchestration overview](../fundamentals/orchestrate-resources.md) +- [Tutorial: Add .NET Aspire to an existing .NET app](../get-started/add-aspire-existing-app.md) diff --git a/docs/real-time/azure-signalr-scenario.md b/docs/real-time/azure-signalr-scenario.md index e413acbc8a..f40c136dd2 100644 --- a/docs/real-time/azure-signalr-scenario.md +++ b/docs/real-time/azure-signalr-scenario.md @@ -1,7 +1,7 @@ --- title: .NET Aspire Azure SignalR Service integration description: Learn how to integrate Azure SignalR Service with .NET Aspire. -ms.date: 04/10/2025 +ms.date: 07/22/2025 --- # .NET Aspire Azure SignalR Service integration @@ -73,9 +73,13 @@ This architecture allows the `webapp` project to communicate with the `api` proj When you add an Azure SignalR Service resource, .NET Aspire generates provisioning infrastructure using [Bicep](/azure/azure-resource-manager/bicep/overview). The generated Bicep includes defaults for location, SKU, and role assignments: -:::code language="bicep" source="../snippets/azure/AppHost/signalr.module.bicep"::: +:::code language="bicep" source="../snippets/azure/AppHost/signalr/signalr.bicep"::: -The generated Bicep provides a starting point and can be customized further. +The preceding Bicep is a module that provisions an Azure SignalR Service resource. Additionally, role assignments are created for the Azure resource in a separate module: + +:::code language="bicep" source="../snippets/azure/AppHost/signalr-roles/signalr-roles.bicep"::: + +The generated Bicep is a starting point and is influenced by changes to the provisioning infrastructure in C#. Customizations to the Bicep file directly will be overwritten, so make changes through the C# provisioning APIs to ensure they are reflected in the generated files. ### Customize provisioning infrastructure diff --git a/docs/real-time/snippets/signalr/SignalR.AppHost/SignalR.AppHost.csproj b/docs/real-time/snippets/signalr/SignalR.AppHost/SignalR.AppHost.csproj index 11ef7cf04f..e8a20d45bf 100644 --- a/docs/real-time/snippets/signalr/SignalR.AppHost/SignalR.AppHost.csproj +++ b/docs/real-time/snippets/signalr/SignalR.AppHost/SignalR.AppHost.csproj @@ -1,5 +1,5 @@ - + Exe net9.0 @@ -12,8 +12,8 @@ - - - + + + \ No newline at end of file diff --git a/docs/real-time/snippets/signalr/SignalR.ServiceDefaults/SignalR.ServiceDefaults.csproj b/docs/real-time/snippets/signalr/SignalR.ServiceDefaults/SignalR.ServiceDefaults.csproj index ae37426e09..9183e963ee 100644 --- a/docs/real-time/snippets/signalr/SignalR.ServiceDefaults/SignalR.ServiceDefaults.csproj +++ b/docs/real-time/snippets/signalr/SignalR.ServiceDefaults/SignalR.ServiceDefaults.csproj @@ -11,7 +11,7 @@ - + diff --git a/docs/security/azure-security-key-vault-integration.md b/docs/security/azure-security-key-vault-integration.md index aeaacfb691..2d8c6b12bd 100644 --- a/docs/security/azure-security-key-vault-integration.md +++ b/docs/security/azure-security-key-vault-integration.md @@ -1,7 +1,7 @@ --- title: .NET Aspire Azure Key Vault integration description: Learn about the .NET Aspire Azure Key Vault integration. -ms.date: 02/07/2025 +ms.date: 07/22/2025 uid: security/azure-security-key-vault-integration --- @@ -59,11 +59,11 @@ The method conf If you're new to [Bicep](/azure/azure-resource-manager/bicep/overview), it's a domain-specific language for defining Azure resources. With .NET Aspire, you don't need to write Bicep by-hand, instead the provisioning APIs generate Bicep for you. When you publish your app, the generated Bicep is output alongside the manifest file. When you add an Azure Key Vault resource, the following Bicep is generated: -:::code language="bicep" source="../snippets/azure/AppHost/key-vault.module.bicep"::: +:::code language="bicep" source="../snippets/azure/AppHost/key-vault/key-vault.bicep"::: The preceding Bicep is a module that provisions an Azure Key Vault resource. Additionally, role assignments are created for the Azure resource in a separate module: -:::code language="bicep" source="../snippets/azure/AppHost/key-vault-roles.module.bicep"::: +:::code language="bicep" source="../snippets/azure/AppHost/key-vault-roles/key-vault-roles.bicep"::: The generated Bicep is a starting point and is influenced by changes to the provisioning infrastructure in C#. Customizations to the Bicep file directly will be overwritten, so make changes through the C# provisioning APIs to ensure they are reflected in the generated files. diff --git a/docs/snippets/azure/AppHost/AppHost.csproj b/docs/snippets/azure/AppHost/AppHost.csproj index 98967622a8..011b46a570 100644 --- a/docs/snippets/azure/AppHost/AppHost.csproj +++ b/docs/snippets/azure/AppHost/AppHost.csproj @@ -1,6 +1,6 @@ - + Exe @@ -12,24 +12,25 @@ - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + diff --git a/docs/snippets/azure/AppHost/Program.ConfigureAIFoundryInfra.cs b/docs/snippets/azure/AppHost/Program.ConfigureAIFoundryInfra.cs new file mode 100644 index 0000000000..b24627f28f --- /dev/null +++ b/docs/snippets/azure/AppHost/Program.ConfigureAIFoundryInfra.cs @@ -0,0 +1,23 @@ +using Azure.Provisioning.CognitiveServices; + +internal static partial class Program +{ + public static void ConfigureAIFoundryInfra(IDistributedApplicationBuilder builder) + { + // + builder.AddAzureAIFoundry("foundry") + .ConfigureInfrastructure(infra => + { + var resources = infra.GetProvisionableResources(); + var account = resources.OfType().Single(); + + account.Sku = new CognitiveServicesSku + { + Tier = CognitiveServicesSkuTier.Enterprise, + Name = "E0" + }; + account.Tags.Add("ExampleKey", "Example value"); + }); + // + } +} \ No newline at end of file diff --git a/docs/snippets/azure/AppHost/Program.cs b/docs/snippets/azure/AppHost/Program.cs index c832d15af8..10f1624e1a 100644 --- a/docs/snippets/azure/AppHost/Program.cs +++ b/docs/snippets/azure/AppHost/Program.cs @@ -2,6 +2,13 @@ AddAzureInfrastructure(builder); +var foundry = builder.AddAzureAIFoundry("ai-foundry"); +var foundryDeployment = foundry.AddDeployment( + name: "chat", + modelName: "Phi-4", + modelVersion: "1", + format: "Microsoft"); + builder.AddAzureAppConfiguration("config"); builder.AddAzureApplicationInsights("app-insights"); diff --git a/docs/snippets/azure/AppHost/acr.module.bicep b/docs/snippets/azure/AppHost/acr/acr.bicep similarity index 81% rename from docs/snippets/azure/AppHost/acr.module.bicep rename to docs/snippets/azure/AppHost/acr/acr.bicep index c7721189c5..f5ca4a1be0 100644 --- a/docs/snippets/azure/AppHost/acr.module.bicep +++ b/docs/snippets/azure/AppHost/acr/acr.bicep @@ -1,7 +1,7 @@ @description('The location for the resource(s) to be deployed.') param location string = resourceGroup().location -resource acr 'Microsoft.ContainerRegistry/registries@2023-07-01' = { +resource acr 'Microsoft.ContainerRegistry/registries@2025-04-01' = { name: take('acr${uniqueString(resourceGroup().id)}', 50) location: location sku: { diff --git a/docs/snippets/azure/AppHost/ai-foundry-roles/ai-foundry-roles.bicep b/docs/snippets/azure/AppHost/ai-foundry-roles/ai-foundry-roles.bicep new file mode 100644 index 0000000000..936f58b7ab --- /dev/null +++ b/docs/snippets/azure/AppHost/ai-foundry-roles/ai-foundry-roles.bicep @@ -0,0 +1,32 @@ +@description('The location for the resource(s) to be deployed.') +param location string = resourceGroup().location + +param ai_foundry_outputs_name string + +param principalType string + +param principalId string + +resource ai_foundry 'Microsoft.CognitiveServices/accounts@2024-10-01' existing = { + name: ai_foundry_outputs_name +} + +resource ai_foundry_CognitiveServicesUser 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(ai_foundry.id, principalId, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'a97b65f3-24c7-4388-baec-2e87135dc908')) + properties: { + principalId: principalId + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'a97b65f3-24c7-4388-baec-2e87135dc908') + principalType: principalType + } + scope: ai_foundry +} + +resource ai_foundry_CognitiveServicesOpenAIUser 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(ai_foundry.id, principalId, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd')) + properties: { + principalId: principalId + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd') + principalType: principalType + } + scope: ai_foundry +} \ No newline at end of file diff --git a/docs/snippets/azure/AppHost/ai-foundry/ai-foundry.bicep b/docs/snippets/azure/AppHost/ai-foundry/ai-foundry.bicep new file mode 100644 index 0000000000..291f69e606 --- /dev/null +++ b/docs/snippets/azure/AppHost/ai-foundry/ai-foundry.bicep @@ -0,0 +1,44 @@ +@description('The location for the resource(s) to be deployed.') +param location string = resourceGroup().location + +resource ai_foundry 'Microsoft.CognitiveServices/accounts@2024-10-01' = { + name: take('aifoundry-${uniqueString(resourceGroup().id)}', 64) + location: location + identity: { + type: 'SystemAssigned' + } + kind: 'AIServices' + properties: { + customSubDomainName: toLower(take(concat('ai-foundry', uniqueString(resourceGroup().id)), 24)) + publicNetworkAccess: 'Enabled' + disableLocalAuth: true + } + sku: { + name: 'S0' + } + tags: { + 'aspire-resource-name': 'ai-foundry' + } +} + +resource chat 'Microsoft.CognitiveServices/accounts/deployments@2024-10-01' = { + name: 'Phi-4' + properties: { + model: { + format: 'Microsoft' + name: 'Phi-4' + version: '1' + } + } + sku: { + name: 'GlobalStandard' + capacity: 1 + } + parent: ai_foundry +} + +output aiFoundryApiEndpoint string = ai_foundry.properties.endpoints['AI Foundry API'] + +output endpoint string = ai_foundry.properties.endpoint + +output name string = ai_foundry.name \ No newline at end of file diff --git a/docs/snippets/azure/AppHost/app-insights/app-insights.bicep b/docs/snippets/azure/AppHost/app-insights/app-insights.bicep new file mode 100644 index 0000000000..7f97725b57 --- /dev/null +++ b/docs/snippets/azure/AppHost/app-insights/app-insights.bicep @@ -0,0 +1,36 @@ +@description('The location for the resource(s) to be deployed.') +param location string = resourceGroup().location + +param applicationType string = 'web' + +param kind string = 'web' + +resource law_app_insights 'Microsoft.OperationalInsights/workspaces@2025-02-01' = { + name: take('lawappinsights-${uniqueString(resourceGroup().id)}', 63) + location: location + properties: { + sku: { + name: 'PerGB2018' + } + } + tags: { + 'aspire-resource-name': 'law_app_insights' + } +} + +resource app_insights 'Microsoft.Insights/components@2020-02-02' = { + name: take('app_insights-${uniqueString(resourceGroup().id)}', 260) + kind: kind + location: location + properties: { + Application_Type: applicationType + WorkspaceResourceId: law_app_insights.id + } + tags: { + 'aspire-resource-name': 'app-insights' + } +} + +output appInsightsConnectionString string = app_insights.properties.ConnectionString + +output name string = app_insights.name \ No newline at end of file diff --git a/docs/snippets/azure/AppHost/config-roles.module.bicep b/docs/snippets/azure/AppHost/config-roles/config-roles.bicep similarity index 96% rename from docs/snippets/azure/AppHost/config-roles.module.bicep rename to docs/snippets/azure/AppHost/config-roles/config-roles.bicep index cef7fe4efb..17e61f195a 100644 --- a/docs/snippets/azure/AppHost/config-roles.module.bicep +++ b/docs/snippets/azure/AppHost/config-roles/config-roles.bicep @@ -7,7 +7,7 @@ param principalType string param principalId string -resource config 'Microsoft.AppConfiguration/configurationStores@2024-05-01' existing = { +resource config 'Microsoft.AppConfiguration/configurationStores@2024-06-01' existing = { name: config_outputs_name } diff --git a/docs/snippets/azure/AppHost/config.module.bicep b/docs/snippets/azure/AppHost/config/config.bicep similarity index 96% rename from docs/snippets/azure/AppHost/config.module.bicep rename to docs/snippets/azure/AppHost/config/config.bicep index e02b677d3f..8d8d917bf0 100644 --- a/docs/snippets/azure/AppHost/config.module.bicep +++ b/docs/snippets/azure/AppHost/config/config.bicep @@ -1,7 +1,7 @@ @description('The location for the resource(s) to be deployed.') param location string = resourceGroup().location -resource config 'Microsoft.AppConfiguration/configurationStores@2024-05-01' = { +resource config 'Microsoft.AppConfiguration/configurationStores@2024-06-01' = { name: take('config-${uniqueString(resourceGroup().id)}', 50) location: location properties: { diff --git a/docs/snippets/azure/AppHost/cosmos-roles.module.bicep b/docs/snippets/azure/AppHost/cosmos-roles/cosmos-roles.bicep similarity index 100% rename from docs/snippets/azure/AppHost/cosmos-roles.module.bicep rename to docs/snippets/azure/AppHost/cosmos-roles/cosmos-roles.bicep diff --git a/docs/snippets/azure/AppHost/cosmos/cosmos.bicep b/docs/snippets/azure/AppHost/cosmos/cosmos.bicep new file mode 100644 index 0000000000..48ccbf42a0 --- /dev/null +++ b/docs/snippets/azure/AppHost/cosmos/cosmos.bicep @@ -0,0 +1,33 @@ +@description('The location for the resource(s) to be deployed.') +param location string = resourceGroup().location + +resource cosmos 'Microsoft.DocumentDB/databaseAccounts@2024-08-15' = { + name: take('cosmos-${uniqueString(resourceGroup().id)}', 44) + location: location + properties: { + locations: [ + { + locationName: location + failoverPriority: 0 + } + ] + capabilities: [ + { + name: 'EnableServerless' + } + ] + consistencyPolicy: { + defaultConsistencyLevel: 'Session' + } + databaseAccountOfferType: 'Standard' + disableLocalAuth: true + } + kind: 'GlobalDocumentDB' + tags: { + 'aspire-resource-name': 'cosmos' + } +} + +output connectionString string = cosmos.properties.documentEndpoint + +output name string = cosmos.name \ No newline at end of file diff --git a/docs/snippets/azure/AppHost/event-hubs-roles.module.bicep b/docs/snippets/azure/AppHost/event-hubs-roles/event-hubs-roles.bicep similarity index 100% rename from docs/snippets/azure/AppHost/event-hubs-roles.module.bicep rename to docs/snippets/azure/AppHost/event-hubs-roles/event-hubs-roles.bicep diff --git a/docs/snippets/azure/AppHost/event-hubs.module.bicep b/docs/snippets/azure/AppHost/event-hubs/event-hubs.bicep similarity index 92% rename from docs/snippets/azure/AppHost/event-hubs.module.bicep rename to docs/snippets/azure/AppHost/event-hubs/event-hubs.bicep index d1f6892896..ef9c8dc192 100644 --- a/docs/snippets/azure/AppHost/event-hubs.module.bicep +++ b/docs/snippets/azure/AppHost/event-hubs/event-hubs.bicep @@ -6,6 +6,9 @@ param sku string = 'Standard' resource event_hubs 'Microsoft.EventHub/namespaces@2024-01-01' = { name: take('event_hubs-${uniqueString(resourceGroup().id)}', 256) location: location + properties: { + disableLocalAuth: true + } sku: { name: sku } diff --git a/docs/snippets/azure/AppHost/key-vault-roles.module.bicep b/docs/snippets/azure/AppHost/key-vault-roles/key-vault-roles.bicep similarity index 91% rename from docs/snippets/azure/AppHost/key-vault-roles.module.bicep rename to docs/snippets/azure/AppHost/key-vault-roles/key-vault-roles.bicep index 13f5f6c1a1..fb2654d8e3 100644 --- a/docs/snippets/azure/AppHost/key-vault-roles.module.bicep +++ b/docs/snippets/azure/AppHost/key-vault-roles/key-vault-roles.bicep @@ -7,7 +7,7 @@ param principalType string param principalId string -resource key_vault 'Microsoft.KeyVault/vaults@2023-07-01' existing = { +resource key_vault 'Microsoft.KeyVault/vaults@2024-11-01' existing = { name: key_vault_outputs_name } diff --git a/docs/snippets/azure/AppHost/key-vault.module.bicep b/docs/snippets/azure/AppHost/key-vault/key-vault.bicep similarity index 88% rename from docs/snippets/azure/AppHost/key-vault.module.bicep rename to docs/snippets/azure/AppHost/key-vault/key-vault.bicep index bf7a3a5ec4..c217f6200a 100644 --- a/docs/snippets/azure/AppHost/key-vault.module.bicep +++ b/docs/snippets/azure/AppHost/key-vault/key-vault.bicep @@ -1,7 +1,7 @@ @description('The location for the resource(s) to be deployed.') param location string = resourceGroup().location -resource key_vault 'Microsoft.KeyVault/vaults@2023-07-01' = { +resource key_vault 'Microsoft.KeyVault/vaults@2024-11-01' = { name: take('keyvault-${uniqueString(resourceGroup().id)}', 24) location: location properties: { diff --git a/docs/snippets/azure/AppHost/log-analytics-workspace/log-analytics-workspace.bicep b/docs/snippets/azure/AppHost/log-analytics-workspace/log-analytics-workspace.bicep new file mode 100644 index 0000000000..8f84a3620d --- /dev/null +++ b/docs/snippets/azure/AppHost/log-analytics-workspace/log-analytics-workspace.bicep @@ -0,0 +1,19 @@ +@description('The location for the resource(s) to be deployed.') +param location string = resourceGroup().location + +resource log_analytics_workspace 'Microsoft.OperationalInsights/workspaces@2025-02-01' = { + name: take('loganalyticsworkspace-${uniqueString(resourceGroup().id)}', 63) + location: location + properties: { + sku: { + name: 'PerGB2018' + } + } + tags: { + 'aspire-resource-name': 'log-analytics-workspace' + } +} + +output logAnalyticsWorkspaceId string = log_analytics_workspace.id + +output name string = log_analytics_workspace.name \ No newline at end of file diff --git a/docs/snippets/azure/AppHost/main.bicep b/docs/snippets/azure/AppHost/main.bicep new file mode 100644 index 0000000000..b2387f50c2 --- /dev/null +++ b/docs/snippets/azure/AppHost/main.bicep @@ -0,0 +1,304 @@ +targetScope = 'subscription' + +param resourceGroupName string + +param location string + +param principalId string + +resource rg 'Microsoft.Resources/resourceGroups@2023-07-01' = { + name: resourceGroupName + location: location +} + +module acr 'acr/acr.bicep' = { + name: 'acr' + scope: rg + params: { + location: location + } +} + +module ai_foundry 'ai-foundry/ai-foundry.bicep' = { + name: 'ai-foundry' + scope: rg + params: { + location: location + } +} + +module config 'config/config.bicep' = { + name: 'config' + scope: rg + params: { + location: location + } +} + +module app_insights 'app-insights/app-insights.bicep' = { + name: 'app-insights' + scope: rg + params: { + location: location + } +} + +module cosmos 'cosmos/cosmos.bicep' = { + name: 'cosmos' + scope: rg + params: { + location: location + } +} + +module event_hubs 'event-hubs/event-hubs.bicep' = { + name: 'event-hubs' + scope: rg + params: { + location: location + } +} + +module key_vault 'key-vault/key-vault.bicep' = { + name: 'key-vault' + scope: rg + params: { + location: location + } +} + +module log_analytics_workspace 'log-analytics-workspace/log-analytics-workspace.bicep' = { + name: 'log-analytics-workspace' + scope: rg + params: { + location: location + } +} + +module openai 'openai/openai.bicep' = { + name: 'openai' + scope: rg + params: { + location: location + } +} + +module postgres_flexible 'postgres-flexible/postgres-flexible.bicep' = { + name: 'postgres-flexible' + scope: rg + params: { + location: location + } +} + +module redis 'redis/redis.bicep' = { + name: 'redis' + scope: rg + params: { + location: location + } +} + +module search 'search/search.bicep' = { + name: 'search' + scope: rg + params: { + location: location + } +} + +module service_bus 'service-bus/service-bus.bicep' = { + name: 'service-bus' + scope: rg + params: { + location: location + } +} + +module signalr 'signalr/signalr.bicep' = { + name: 'signalr' + scope: rg + params: { + location: location + } +} + +module sql 'sql/sql.bicep' = { + name: 'sql' + scope: rg + params: { + location: location + } +} + +module storage 'storage/storage.bicep' = { + name: 'storage' + scope: rg + params: { + location: location + } +} + +module web_pubsub 'web-pubsub/web-pubsub.bicep' = { + name: 'web-pubsub' + scope: rg + params: { + location: location + messages_url_0: '/eventhandler/' + } +} + +module ai_foundry_roles 'ai-foundry-roles/ai-foundry-roles.bicep' = { + name: 'ai-foundry-roles' + scope: rg + params: { + location: location + ai_foundry_outputs_name: ai_foundry.outputs.name + principalType: '' + principalId: '' + } +} + +module config_roles 'config-roles/config-roles.bicep' = { + name: 'config-roles' + scope: rg + params: { + location: location + config_outputs_name: config.outputs.name + principalType: '' + principalId: '' + } +} + +module cosmos_roles 'cosmos-roles/cosmos-roles.bicep' = { + name: 'cosmos-roles' + scope: rg + params: { + location: location + cosmos_outputs_name: cosmos.outputs.name + principalId: '' + } +} + +module event_hubs_roles 'event-hubs-roles/event-hubs-roles.bicep' = { + name: 'event-hubs-roles' + scope: rg + params: { + location: location + event_hubs_outputs_name: event_hubs.outputs.name + principalType: '' + principalId: '' + } +} + +module key_vault_roles 'key-vault-roles/key-vault-roles.bicep' = { + name: 'key-vault-roles' + scope: rg + params: { + location: location + key_vault_outputs_name: key_vault.outputs.name + principalType: '' + principalId: '' + } +} + +module openai_roles 'openai-roles/openai-roles.bicep' = { + name: 'openai-roles' + scope: rg + params: { + location: location + openai_outputs_name: openai.outputs.name + principalType: '' + principalId: '' + } +} + +module postgres_flexible_roles 'postgres-flexible-roles/postgres-flexible-roles.bicep' = { + name: 'postgres-flexible-roles' + scope: rg + params: { + location: location + postgres_flexible_outputs_name: postgres_flexible.outputs.name + principalType: '' + principalId: '' + principalName: '' + } +} + +module redis_roles 'redis-roles/redis-roles.bicep' = { + name: 'redis-roles' + scope: rg + params: { + location: location + redis_outputs_name: redis.outputs.name + principalId: '' + principalName: '' + } +} + +module search_roles 'search-roles/search-roles.bicep' = { + name: 'search-roles' + scope: rg + params: { + location: location + search_outputs_name: search.outputs.name + principalType: '' + principalId: '' + } +} + +module service_bus_roles 'service-bus-roles/service-bus-roles.bicep' = { + name: 'service-bus-roles' + scope: rg + params: { + location: location + service_bus_outputs_name: service_bus.outputs.name + principalType: '' + principalId: '' + } +} + +module signalr_roles 'signalr-roles/signalr-roles.bicep' = { + name: 'signalr-roles' + scope: rg + params: { + location: location + signalr_outputs_name: signalr.outputs.name + principalType: '' + principalId: '' + } +} + +module sql_roles 'sql-roles/sql-roles.bicep' = { + name: 'sql-roles' + scope: rg + params: { + location: location + sql_outputs_name: sql.outputs.name + sql_outputs_sqlserveradminname: sql.outputs.sqlServerAdminName + principalId: '' + principalName: '' + } +} + +module storage_roles 'storage-roles/storage-roles.bicep' = { + name: 'storage-roles' + scope: rg + params: { + location: location + storage_outputs_name: storage.outputs.name + principalType: '' + principalId: '' + } +} + +module web_pubsub_roles 'web-pubsub-roles/web-pubsub-roles.bicep' = { + name: 'web-pubsub-roles' + scope: rg + params: { + location: location + web_pubsub_outputs_name: web_pubsub.outputs.name + principalType: '' + principalId: '' + } +} \ No newline at end of file diff --git a/docs/snippets/azure/AppHost/openai-roles.module.bicep b/docs/snippets/azure/AppHost/openai-roles/openai-roles.bicep similarity index 68% rename from docs/snippets/azure/AppHost/openai-roles.module.bicep rename to docs/snippets/azure/AppHost/openai-roles/openai-roles.bicep index 1200e172dd..e4916f0fd3 100644 --- a/docs/snippets/azure/AppHost/openai-roles.module.bicep +++ b/docs/snippets/azure/AppHost/openai-roles/openai-roles.bicep @@ -11,11 +11,11 @@ resource openai 'Microsoft.CognitiveServices/accounts@2024-10-01' existing = { name: openai_outputs_name } -resource openai_CognitiveServicesOpenAIContributor 'Microsoft.Authorization/roleAssignments@2022-04-01' = { - name: guid(openai.id, principalId, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'a001fd3d-188f-4b5d-821b-7da978bf7442')) +resource openai_CognitiveServicesOpenAIUser 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(openai.id, principalId, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd')) properties: { principalId: principalId - roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'a001fd3d-188f-4b5d-821b-7da978bf7442') + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd') principalType: principalType } scope: openai diff --git a/docs/snippets/azure/AppHost/openai.module.bicep b/docs/snippets/azure/AppHost/openai/openai.bicep similarity index 100% rename from docs/snippets/azure/AppHost/openai.module.bicep rename to docs/snippets/azure/AppHost/openai/openai.bicep diff --git a/docs/snippets/azure/AppHost/postgres-flexible-roles.module.bicep b/docs/snippets/azure/AppHost/postgres-flexible-roles/postgres-flexible-roles.bicep similarity index 100% rename from docs/snippets/azure/AppHost/postgres-flexible-roles.module.bicep rename to docs/snippets/azure/AppHost/postgres-flexible-roles/postgres-flexible-roles.bicep diff --git a/docs/snippets/azure/AppHost/postgres-flexible.module.bicep b/docs/snippets/azure/AppHost/postgres-flexible/postgres-flexible.bicep similarity index 100% rename from docs/snippets/azure/AppHost/postgres-flexible.module.bicep rename to docs/snippets/azure/AppHost/postgres-flexible/postgres-flexible.bicep diff --git a/docs/snippets/azure/AppHost/redis-roles.module.bicep b/docs/snippets/azure/AppHost/redis-roles/redis-roles.bicep similarity index 84% rename from docs/snippets/azure/AppHost/redis-roles.module.bicep rename to docs/snippets/azure/AppHost/redis-roles/redis-roles.bicep index 64ec0c1d91..4e0fdd9838 100644 --- a/docs/snippets/azure/AppHost/redis-roles.module.bicep +++ b/docs/snippets/azure/AppHost/redis-roles/redis-roles.bicep @@ -7,11 +7,11 @@ param principalId string param principalName string -resource redis 'Microsoft.Cache/redis@2024-03-01' existing = { +resource redis 'Microsoft.Cache/redis@2024-11-01' existing = { name: redis_outputs_name } -resource redis_contributor 'Microsoft.Cache/redis/accessPolicyAssignments@2024-03-01' = { +resource redis_contributor 'Microsoft.Cache/redis/accessPolicyAssignments@2024-11-01' = { name: guid(redis.id, principalId, 'Data Contributor') properties: { accessPolicyName: 'Data Contributor' diff --git a/docs/snippets/azure/AppHost/redis.module.bicep b/docs/snippets/azure/AppHost/redis/redis.bicep similarity index 91% rename from docs/snippets/azure/AppHost/redis.module.bicep rename to docs/snippets/azure/AppHost/redis/redis.bicep index cb8039d262..649529894a 100644 --- a/docs/snippets/azure/AppHost/redis.module.bicep +++ b/docs/snippets/azure/AppHost/redis/redis.bicep @@ -1,7 +1,7 @@ @description('The location for the resource(s) to be deployed.') param location string = resourceGroup().location -resource redis 'Microsoft.Cache/redis@2024-03-01' = { +resource redis 'Microsoft.Cache/redis@2024-11-01' = { name: take('redis-${uniqueString(resourceGroup().id)}', 63) location: location properties: { diff --git a/docs/snippets/azure/AppHost/search-roles.module.bicep b/docs/snippets/azure/AppHost/search-roles/search-roles.bicep similarity index 100% rename from docs/snippets/azure/AppHost/search-roles.module.bicep rename to docs/snippets/azure/AppHost/search-roles/search-roles.bicep diff --git a/docs/snippets/azure/AppHost/search.module.bicep b/docs/snippets/azure/AppHost/search/search.bicep similarity index 100% rename from docs/snippets/azure/AppHost/search.module.bicep rename to docs/snippets/azure/AppHost/search/search.bicep diff --git a/docs/snippets/azure/AppHost/service-bus-roles.module.bicep b/docs/snippets/azure/AppHost/service-bus-roles/service-bus-roles.bicep similarity index 100% rename from docs/snippets/azure/AppHost/service-bus-roles.module.bicep rename to docs/snippets/azure/AppHost/service-bus-roles/service-bus-roles.bicep diff --git a/docs/snippets/azure/AppHost/service-bus.module.bicep b/docs/snippets/azure/AppHost/service-bus/service-bus.bicep similarity index 100% rename from docs/snippets/azure/AppHost/service-bus.module.bicep rename to docs/snippets/azure/AppHost/service-bus/service-bus.bicep diff --git a/docs/snippets/azure/AppHost/signalr-roles.module.bicep b/docs/snippets/azure/AppHost/signalr-roles/signalr-roles.bicep similarity index 100% rename from docs/snippets/azure/AppHost/signalr-roles.module.bicep rename to docs/snippets/azure/AppHost/signalr-roles/signalr-roles.bicep diff --git a/docs/snippets/azure/AppHost/signalr.module.bicep b/docs/snippets/azure/AppHost/signalr/signalr.bicep similarity index 96% rename from docs/snippets/azure/AppHost/signalr.module.bicep rename to docs/snippets/azure/AppHost/signalr/signalr.bicep index e55d9ec39c..5e75b94dbc 100644 --- a/docs/snippets/azure/AppHost/signalr.module.bicep +++ b/docs/snippets/azure/AppHost/signalr/signalr.bicep @@ -10,6 +10,7 @@ resource signalr 'Microsoft.SignalRService/signalR@2024-03-01' = { '*' ] } + disableLocalAuth: true features: [ { flag: 'ServiceMode' diff --git a/docs/snippets/azure/AppHost/sql-roles.module.bicep b/docs/snippets/azure/AppHost/sql-roles.module.bicep deleted file mode 100644 index b51780ea7c..0000000000 --- a/docs/snippets/azure/AppHost/sql-roles.module.bicep +++ /dev/null @@ -1,21 +0,0 @@ -@description('The location for the resource(s) to be deployed.') -param location string = resourceGroup().location - -param sql_outputs_name string - -param principalId string - -param principalName string - -resource sql 'Microsoft.Sql/servers@2021-11-01' existing = { - name: sql_outputs_name -} - -resource sql_admin 'Microsoft.Sql/servers/administrators@2021-11-01' = { - name: 'ActiveDirectory' - properties: { - login: principalName - sid: principalId - } - parent: sql -} \ No newline at end of file diff --git a/docs/snippets/azure/AppHost/sql-roles/sql-roles.bicep b/docs/snippets/azure/AppHost/sql-roles/sql-roles.bicep new file mode 100644 index 0000000000..e054ea5291 --- /dev/null +++ b/docs/snippets/azure/AppHost/sql-roles/sql-roles.bicep @@ -0,0 +1,22 @@ +@description('The location for the resource(s) to be deployed.') +param location string = resourceGroup().location + +param sql_outputs_name string + +param sql_outputs_sqlserveradminname string + +param principalId string + +param principalName string + +resource sql 'Microsoft.Sql/servers@2021-11-01' existing = { + name: sql_outputs_name +} + +resource sqlServerAdmin 'Microsoft.ManagedIdentity/userAssignedIdentities@2024-11-30' existing = { + name: sql_outputs_sqlserveradminname +} + +resource mi 'Microsoft.ManagedIdentity/userAssignedIdentities@2024-11-30' existing = { + name: principalName +} \ No newline at end of file diff --git a/docs/snippets/azure/AppHost/sql.module.bicep b/docs/snippets/azure/AppHost/sql/sql.bicep similarity index 67% rename from docs/snippets/azure/AppHost/sql.module.bicep rename to docs/snippets/azure/AppHost/sql/sql.bicep index 705533e475..f8f0f75542 100644 --- a/docs/snippets/azure/AppHost/sql.module.bicep +++ b/docs/snippets/azure/AppHost/sql/sql.bicep @@ -1,9 +1,10 @@ @description('The location for the resource(s) to be deployed.') param location string = resourceGroup().location -param principalId string - -param principalName string +resource sqlServerAdminManagedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2024-11-30' = { + name: take('sql-admin-${uniqueString(resourceGroup().id)}', 63) + location: location +} resource sql 'Microsoft.Sql/servers@2021-11-01' = { name: take('sql-${uniqueString(resourceGroup().id)}', 63) @@ -11,8 +12,8 @@ resource sql 'Microsoft.Sql/servers@2021-11-01' = { properties: { administrators: { administratorType: 'ActiveDirectory' - login: principalName - sid: principalId + login: sqlServerAdminManagedIdentity.name + sid: sqlServerAdminManagedIdentity.properties.principalId tenantId: subscription().tenantId azureADOnlyAuthentication: true } @@ -36,4 +37,6 @@ resource sqlFirewallRule_AllowAllAzureIps 'Microsoft.Sql/servers/firewallRules@2 output sqlServerFqdn string = sql.properties.fullyQualifiedDomainName -output name string = sql.name \ No newline at end of file +output name string = sql.name + +output sqlServerAdminName string = sqlServerAdminManagedIdentity.name \ No newline at end of file diff --git a/docs/snippets/azure/AppHost/storage-roles.module.bicep b/docs/snippets/azure/AppHost/storage-roles/storage-roles.bicep similarity index 100% rename from docs/snippets/azure/AppHost/storage-roles.module.bicep rename to docs/snippets/azure/AppHost/storage-roles/storage-roles.bicep diff --git a/docs/snippets/azure/AppHost/storage.module.bicep b/docs/snippets/azure/AppHost/storage/storage.bicep similarity index 87% rename from docs/snippets/azure/AppHost/storage.module.bicep rename to docs/snippets/azure/AppHost/storage/storage.bicep index 5a375968ca..18c0a53042 100644 --- a/docs/snippets/azure/AppHost/storage.module.bicep +++ b/docs/snippets/azure/AppHost/storage/storage.bicep @@ -21,11 +21,6 @@ resource storage 'Microsoft.Storage/storageAccounts@2024-01-01' = { } } -resource blobs 'Microsoft.Storage/storageAccounts/blobServices@2024-01-01' = { - name: 'default' - parent: storage -} - output blobEndpoint string = storage.properties.primaryEndpoints.blob output queueEndpoint string = storage.properties.primaryEndpoints.queue diff --git a/docs/snippets/azure/AppHost/web-pubsub-roles.module.bicep b/docs/snippets/azure/AppHost/web-pubsub-roles/web-pubsub-roles.bicep similarity index 100% rename from docs/snippets/azure/AppHost/web-pubsub-roles.module.bicep rename to docs/snippets/azure/AppHost/web-pubsub-roles/web-pubsub-roles.bicep diff --git a/docs/snippets/azure/AppHost/web-pubsub.module.bicep b/docs/snippets/azure/AppHost/web-pubsub/web-pubsub.bicep similarity index 94% rename from docs/snippets/azure/AppHost/web-pubsub.module.bicep rename to docs/snippets/azure/AppHost/web-pubsub/web-pubsub.bicep index b3caad1f72..71d08ff0cf 100644 --- a/docs/snippets/azure/AppHost/web-pubsub.module.bicep +++ b/docs/snippets/azure/AppHost/web-pubsub/web-pubsub.bicep @@ -10,6 +10,9 @@ param messages_url_0 string resource web_pubsub 'Microsoft.SignalRService/webPubSub@2024-03-01' = { name: take('webpubsub-${uniqueString(resourceGroup().id)}', 63) location: location + properties: { + disableLocalAuth: true + } sku: { name: sku capacity: capacity diff --git a/docs/storage/includes/storage-app-host.md b/docs/storage/includes/storage-app-host.md index 8cbd4d7efb..17c31e7c5e 100644 --- a/docs/storage/includes/storage-app-host.md +++ b/docs/storage/includes/storage-app-host.md @@ -52,53 +52,7 @@ When you add an `AzureStorageResource` to the app host, it exposes other useful > [!IMPORTANT] > When you call , it implicitly calls —which adds support for generating Azure resources dynamically during app startup. The app must configure the appropriate subscription and location. For more information, see [Local provisioning: Configuration](../../azure/local-provisioning.md#configuration). -#### Provisioning-generated Bicep - -If you're new to [Bicep](/azure/azure-resource-manager/bicep/overview), it's a domain-specific language for defining Azure resources. With .NET Aspire, you don't need to write Bicep by-hand, instead the provisioning APIs generate Bicep for you. When you publish your app, the generated Bicep is output alongside the manifest file. When you add an Azure Storage resource, the following Bicep is generated: - -:::code language="bicep" source="../../snippets/azure/AppHost/storage.module.bicep"::: - -The preceding Bicep is a module that provisions an Azure Storage account with the following defaults: - -- `kind`: The kind of storage account. The default is `StorageV2`. -- `sku`: The SKU of the storage account. The default is `Standard_GRS`. -- `properties`: The properties of the storage account: - - `accessTier`: The access tier of the storage account. The default is `Hot`. - - `allowSharedKeyAccess`: A boolean value that indicates whether the storage account permits requests to be authorized with the account access key. The default is `false`. - - `minimumTlsVersion`: The minimum supported TLS version for the storage account. The default is `TLS1_2`. - - `networkAcls`: The network ACLs for the storage account. The default is `{ defaultAction: 'Allow' }`. - -In addition to the storage account, it also provisions a blob container. - -The following role assignments are added to the storage account to grant your application access. See the [built-in Azure role-based access control (Azure RBAC) roles](/azure/role-based-access-control/built-in-roles#storage) for more information: - -| Role / ID | Description | -|------|-------------| -| Storage Blob Data Contributor
`ba92f5b4-2d11-453d-a403-e96b0029c9fe` | Read, write, and delete Azure Storage containers and blobs. | -| Storage Table Data Contributor
`0a9a7e1f-b9d0-4cc4-a60d-0319b160aaa3` | Read, write, and delete Azure Storage tables and entities. | -| Storage Queue Data Contributor
`974c5e8b-45b9-4653-ba55-5f855dd0fb88` | Read, write, and delete Azure Storage queues and queue messages. | - -The generated Bicep is a starting point and is influenced by changes to the provisioning infrastructure in C#. Customizations to the Bicep file directly will be overwritten, so make changes through the C# provisioning APIs to ensure they are reflected in the generated files. - -#### Customize provisioning infrastructure - -All .NET Aspire Azure resources are subclasses of the type. This type enables the customization of the generated Bicep by providing a fluent API to configure the Azure resources—using the API. For example, you can configure the `kind`, `sku`, `properties`, and more. The following example demonstrates how to customize the Azure Storage resource: - -:::code language="csharp" source="../../snippets/azure/AppHost/Program.ConfigureStorageInfra.cs" id="configure"::: - -The preceding code: - -- Chains a call to the API: - - The `infra` parameter is an instance of the type. - - The provisionable resources are retrieved by calling the method. - - The single is retrieved. - - The is assigned to . - - The is assigned to a new with a `Name` of . - - A tag is added to the storage account with a key of `ExampleKey` and a value of `Example value`. - -There are many more configuration options available to customize the Azure Storage resource. For more information, see . - - +[!INCLUDE [storage-bicep](storage-bicep.md)] ### Connect to an existing Azure Storage account diff --git a/docs/storage/includes/storage-bicep.md b/docs/storage/includes/storage-bicep.md index f428a4f382..76b6b85f3d 100644 --- a/docs/storage/includes/storage-bicep.md +++ b/docs/storage/includes/storage-bicep.md @@ -4,17 +4,23 @@ ms.topic: include #### Provisioning-generated Bicep -If you're new to [Bicep](/azure/azure-resource-manager/bicep/overview), it's a domain-specific language for defining Azure resources. With .NET Aspire, you don't need to write Bicep by hand; instead, the provisioning APIs generate Bicep for you. When you publish your app, the generated Bicep is output alongside the manifest file. When you add an Azure Storage resource, the following Bicep is generated: +If you're new to [Bicep](/azure/azure-resource-manager/bicep/overview), it's a domain-specific language for defining Azure resources. With .NET Aspire, you don't need to write Bicep by-hand, instead the provisioning APIs generate Bicep for you. When you publish your app, the generated Bicep is output alongside the manifest file. When you add an Azure Storage resource, the following Bicep is generated: -:::code language="bicep" source="../../snippets/azure/AppHost/storage.module.bicep"::: +:::code language="bicep" source="../../snippets/azure/AppHost/storage/storage.bicep"::: -The preceding Bicep is a module that provisions an Azure Storage account resource. Additionally, role assignments are created for the Azure resource in a separate module: +The preceding Bicep is a module that provisions an Azure Storage account with the following defaults: -:::code language="bicep" source="../../snippets/azure/AppHost/storage-roles.module.bicep"::: +- `kind`: The kind of storage account. The default is `StorageV2`. +- `sku`: The SKU of the storage account. The default is `Standard_GRS`. +- `properties`: The properties of the storage account: + - `accessTier`: The access tier of the storage account. The default is `Hot`. + - `allowSharedKeyAccess`: A boolean value that indicates whether the storage account permits requests to be authorized with the account access key. The default is `false`. + - `minimumTlsVersion`: The minimum supported TLS version for the storage account. The default is `TLS1_2`. + - `networkAcls`: The network ACLs for the storage account. The default is `{ defaultAction: 'Allow' }`. In addition to the storage account, it also provisions a blob container. -The following role assignments are added to the storage account to grant your application access. For more information, see the [built-in Azure role-based access control (Azure RBAC) roles](/azure/role-based-access-control/built-in-roles#storage). +The following role assignments are added to the storage account to grant your application access. See the [built-in Azure role-based access control (Azure RBAC) roles](/azure/role-based-access-control/built-in-roles#storage) for more information: | Role / ID | Description | |------|-------------| @@ -22,7 +28,11 @@ The following role assignments are added to the storage account to grant your ap | Storage Table Data Contributor
`0a9a7e1f-b9d0-4cc4-a60d-0319b160aaa3` | Read, write, and delete Azure Storage tables and entities. | | Storage Queue Data Contributor
`974c5e8b-45b9-4653-ba55-5f855dd0fb88` | Read, write, and delete Azure Storage queues and queue messages. | -The generated Bicep is a starting point and is influenced by changes to the provisioning infrastructure in C#. If you make customizations directly to the Bicep file, they'll be overwritten, so make changes through the C# provisioning APIs to ensure they're reflected in the generated files. +Additionally, role assignments are created for the Azure resource in a separate module: + +:::code language="bicep" source="../../snippets/azure/AppHost/storage-roles/storage-roles.bicep"::: + +The generated Bicep is a starting point and is influenced by changes to the provisioning infrastructure in C#. Customizations to the Bicep file directly will be overwritten, so make changes through the C# provisioning APIs to ensure they are reflected in the generated files. #### Customize provisioning infrastructure diff --git a/docs/storage/snippets/tutorial/AspireStorage/AspireStorage.AppHost/AspireStorage.AppHost.csproj b/docs/storage/snippets/tutorial/AspireStorage/AspireStorage.AppHost/AspireStorage.AppHost.csproj index 48364adffa..470fcc9176 100644 --- a/docs/storage/snippets/tutorial/AspireStorage/AspireStorage.AppHost/AspireStorage.AppHost.csproj +++ b/docs/storage/snippets/tutorial/AspireStorage/AspireStorage.AppHost/AspireStorage.AppHost.csproj @@ -1,5 +1,5 @@ - + Exe net9.0 @@ -13,7 +13,7 @@
- - + +
\ No newline at end of file diff --git a/docs/storage/snippets/tutorial/AspireStorage/AspireStorage.ServiceDefaults/AspireStorage.ServiceDefaults.csproj b/docs/storage/snippets/tutorial/AspireStorage/AspireStorage.ServiceDefaults/AspireStorage.ServiceDefaults.csproj index ae37426e09..9183e963ee 100644 --- a/docs/storage/snippets/tutorial/AspireStorage/AspireStorage.ServiceDefaults/AspireStorage.ServiceDefaults.csproj +++ b/docs/storage/snippets/tutorial/AspireStorage/AspireStorage.ServiceDefaults/AspireStorage.ServiceDefaults.csproj @@ -11,7 +11,7 @@ - + diff --git a/docs/storage/snippets/tutorial/AspireStorage/AspireStorage.Web/AspireStorage.Web.csproj b/docs/storage/snippets/tutorial/AspireStorage/AspireStorage.Web/AspireStorage.Web.csproj index c1e68b32d6..e7dd8d8172 100644 --- a/docs/storage/snippets/tutorial/AspireStorage/AspireStorage.Web/AspireStorage.Web.csproj +++ b/docs/storage/snippets/tutorial/AspireStorage/AspireStorage.Web/AspireStorage.Web.csproj @@ -11,8 +11,8 @@
- - + +
diff --git a/docs/storage/snippets/tutorial/AspireStorage/AspireStorage.WorkerService/AspireStorage.WorkerService.csproj b/docs/storage/snippets/tutorial/AspireStorage/AspireStorage.WorkerService/AspireStorage.WorkerService.csproj index b562f31442..84376a9c39 100644 --- a/docs/storage/snippets/tutorial/AspireStorage/AspireStorage.WorkerService/AspireStorage.WorkerService.csproj +++ b/docs/storage/snippets/tutorial/AspireStorage/AspireStorage.WorkerService/AspireStorage.WorkerService.csproj @@ -8,7 +8,7 @@ - + diff --git a/docs/testing/snippets/testing/mstest/AspireApp.AppHost/AspireApp.AppHost.csproj b/docs/testing/snippets/testing/mstest/AspireApp.AppHost/AspireApp.AppHost.csproj index 2a0a680828..bbcfa232d1 100644 --- a/docs/testing/snippets/testing/mstest/AspireApp.AppHost/AspireApp.AppHost.csproj +++ b/docs/testing/snippets/testing/mstest/AspireApp.AppHost/AspireApp.AppHost.csproj @@ -1,5 +1,5 @@ - + Exe net9.0 @@ -12,6 +12,6 @@
- +
\ No newline at end of file diff --git a/docs/testing/snippets/testing/mstest/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj b/docs/testing/snippets/testing/mstest/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj index ae37426e09..9183e963ee 100644 --- a/docs/testing/snippets/testing/mstest/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj +++ b/docs/testing/snippets/testing/mstest/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj @@ -11,7 +11,7 @@ - + diff --git a/docs/testing/snippets/testing/mstest/AspireApp.Tests/AspireApp.Tests.csproj b/docs/testing/snippets/testing/mstest/AspireApp.Tests/AspireApp.Tests.csproj index 3f71086f49..30be6a5e2d 100644 --- a/docs/testing/snippets/testing/mstest/AspireApp.Tests/AspireApp.Tests.csproj +++ b/docs/testing/snippets/testing/mstest/AspireApp.Tests/AspireApp.Tests.csproj @@ -14,7 +14,7 @@ - + diff --git a/docs/testing/snippets/testing/nunit/AspireApp.AppHost/AspireApp.AppHost.csproj b/docs/testing/snippets/testing/nunit/AspireApp.AppHost/AspireApp.AppHost.csproj index ee0de5364f..35e2cffe3a 100644 --- a/docs/testing/snippets/testing/nunit/AspireApp.AppHost/AspireApp.AppHost.csproj +++ b/docs/testing/snippets/testing/nunit/AspireApp.AppHost/AspireApp.AppHost.csproj @@ -1,5 +1,5 @@ - + Exe net9.0 @@ -12,6 +12,6 @@ - + \ No newline at end of file diff --git a/docs/testing/snippets/testing/nunit/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj b/docs/testing/snippets/testing/nunit/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj index ae37426e09..9183e963ee 100644 --- a/docs/testing/snippets/testing/nunit/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj +++ b/docs/testing/snippets/testing/nunit/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj @@ -11,7 +11,7 @@ - + diff --git a/docs/testing/snippets/testing/nunit/AspireApp.Tests/AspireApp.Tests.csproj b/docs/testing/snippets/testing/nunit/AspireApp.Tests/AspireApp.Tests.csproj index a18f696038..cc0facf8e2 100644 --- a/docs/testing/snippets/testing/nunit/AspireApp.Tests/AspireApp.Tests.csproj +++ b/docs/testing/snippets/testing/nunit/AspireApp.Tests/AspireApp.Tests.csproj @@ -10,7 +10,7 @@ - + diff --git a/docs/testing/snippets/testing/xunit/AspireApp.AppHost/AspireApp.AppHost.csproj b/docs/testing/snippets/testing/xunit/AspireApp.AppHost/AspireApp.AppHost.csproj index b3651c97a8..d72e7b4f85 100644 --- a/docs/testing/snippets/testing/xunit/AspireApp.AppHost/AspireApp.AppHost.csproj +++ b/docs/testing/snippets/testing/xunit/AspireApp.AppHost/AspireApp.AppHost.csproj @@ -1,5 +1,5 @@ - + Exe net9.0 @@ -12,6 +12,6 @@ - +
\ No newline at end of file diff --git a/docs/testing/snippets/testing/xunit/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj b/docs/testing/snippets/testing/xunit/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj index ae37426e09..9183e963ee 100644 --- a/docs/testing/snippets/testing/xunit/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj +++ b/docs/testing/snippets/testing/xunit/AspireApp.ServiceDefaults/AspireApp.ServiceDefaults.csproj @@ -11,7 +11,7 @@ - + diff --git a/docs/testing/snippets/testing/xunit/AspireApp.Tests/AspireApp.Tests.csproj b/docs/testing/snippets/testing/xunit/AspireApp.Tests/AspireApp.Tests.csproj index 9e221dde5c..4b27d60313 100644 --- a/docs/testing/snippets/testing/xunit/AspireApp.Tests/AspireApp.Tests.csproj +++ b/docs/testing/snippets/testing/xunit/AspireApp.Tests/AspireApp.Tests.csproj @@ -10,7 +10,7 @@ - + diff --git a/docs/toc.yml b/docs/toc.yml index 3580eef6b0..b101ae4cc4 100644 --- a/docs/toc.yml +++ b/docs/toc.yml @@ -31,8 +31,8 @@ items: href: get-started/github-codespaces.md - name: Dev Containers href: get-started/dev-containers.md - - name: What's new in .NET Aspire 9.3 - href: whats-new/dotnet-aspire-9.3.md + - name: What's new in .NET Aspire 9.4 + href: whats-new/dotnet-aspire-9.4.md - name: Upgrade to .NET Aspire 9.0 href: get-started/upgrade-to-aspire-9.md @@ -79,6 +79,8 @@ items: href: fundamentals/networking-overview.md - name: Eventing in .NET Aspire href: app-host/eventing.md + - name: Prompt user from CLI or Dashboard + href: extensibility/interaction-service.md - name: Use external parameters displayName: external parameters,configuration href: fundamentals/external-parameters.md @@ -113,6 +115,42 @@ items: - name: Microsoft-collected dashboard telemetry href: fundamentals/dashboard/microsoft-collected-dashboard-telemetry.md +- name: Aspire CLI + displayName: aspire cli,aspire command line interface,aspire commands,cli + items: + - name: Overview + href: cli/overview.md + - name: Install + href: cli/install.md + - name: aspire command reference + items: + - name: aspire + href: cli-reference/aspire.md + - name: aspire add + href: cli-reference/aspire-add.md + - name: aspire deploy + href: cli-reference/aspire-deploy.md + - name: aspire config + items: + - name: aspire config [command] + href: cli-reference/aspire-config.md + - name: aspire config list + href: cli-reference/aspire-config-list.md + - name: aspire config get + href: cli-reference/aspire-config-get.md + - name: aspire config set + href: cli-reference/aspire-config-set.md + - name: aspire config delete + href: cli-reference/aspire-config-delete.md + - name: aspire exec + href: cli-reference/aspire-exec.md + - name: aspire new + href: cli-reference/aspire-new.md + - name: aspire publish + href: cli-reference/aspire-publish.md + - name: aspire run + href: cli-reference/aspire-run.md + - name: Testing items: - name: Overview @@ -184,6 +222,9 @@ items: - name: Azure App Configuration displayName: app configuration,configuration href: azure/azure-app-configuration-integration.md + - name: Azure AI Foundry (Preview) + displayName: azure ai,foundry,foundation models,ai foundry + href: azureai/azureai-foundry-integration.md - name: Azure AI Inference (Preview) displayName: azure ai,inference href: azureai/azureai-inference-integration.md @@ -280,6 +321,9 @@ items: - name: SQL Server displayName: sql server,ef core href: database/sql-server-entity-framework-integration.md + - name: GitHub Models + displayName: github models,github ai,github copilot,github copilot chat,deepseek,phi,gpt + href: github/github-models-integration.md - name: Keycloak (Preview) displayName: security,openid connect,single sign-on,sso,identity,federation,account management href: authentication/keycloak-integration.md @@ -337,6 +381,9 @@ items: - name: SQL Server displayName: sql server,sql database href: database/sql-server-integration.md + - name: YARP (Yet Another Reverse Proxy) + displayName: yarp,reverse proxy + href: proxies/yarp-integration.md - name: Community Toolkit items: - name: Overview @@ -401,7 +448,7 @@ items: - name: Secure communication between integrations href: extensibility/secure-communication-between-integrations.md -- name: Deploy +- name: Publish and Deploy items: - name: Overview displayName: azure deployment,deployment,Kubernetes,k8s @@ -432,6 +479,8 @@ items: href: database/sql-server-integration-deployment.md - name: Deploy .NET Aspire + Redis href: caching/caching-integrations-deployment.md + - name: Build container images + href: fundamentals/build-container-images.md - name: Tool-builder manifest schemas href: deployment/manifest-format.md @@ -481,6 +530,8 @@ items: href: diagnostics/aspire006.md - name: ASPIRE007 href: diagnostics/aspire007.md + - name: ASPIRE008 + href: diagnostics/aspire008.md - name: ASPIREACADOMAINS001 href: diagnostics/aspireacadomains001.md - name: ASPIRECOMPUTE001 @@ -509,6 +560,8 @@ items: href: diagnostics/aspire004.md - name: Aspire SDK reference is out of date or missing href: diagnostics/aspire007.md + - name: Aspire workload is deprecated + href: diagnostics/aspire008.md - name: Resources items: - name: Items must have valid names diff --git a/docs/whats-new/dotnet-aspire-9.4.md b/docs/whats-new/dotnet-aspire-9.4.md new file mode 100644 index 0000000000..ed7587f0fa --- /dev/null +++ b/docs/whats-new/dotnet-aspire-9.4.md @@ -0,0 +1,1869 @@ +--- +title: What's new in .NET Aspire 9.4 +description: Learn what's new in the official general availability release of .NET Aspire 9.4. +ms.date: 07/29/2025 +--- + +# What's new in .NET Aspire 9.4 + +📢 .NET Aspire 9.4 is the next minor version release of .NET Aspire. It supports: + +- .NET 8.0 Long Term Support (LTS) +- .NET 9.0 Standard Term Support (STS) +- .NET 10.0 Preview 6 + +If you have feedback, questions, or want to contribute to .NET Aspire, collaborate with us on [:::image type="icon" source="../media/github-mark.svg" border="false"::: GitHub](https://github.com/dotnet/aspire) or join us on our new [:::image type="icon" source="../media/discord-icon.svg" border="false"::: Discord](https://aka.ms/aspire-discord) to chat with the team and other community members. + +It's important to note that .NET Aspire releases out-of-band from .NET releases. While major versions of Aspire align with major .NET versions, minor versions are released more frequently. For more information on .NET and .NET Aspire version support, see: + +- [.NET support policy](https://dotnet.microsoft.com/platform/support/policy): Definitions for LTS and STS. +- [.NET Aspire support policy](https://dotnet.microsoft.com/platform/support/policy/aspire): Important unique product lifecycle details. + +## ⬆️ Upgrade to .NET Aspire 9.4 + +Moving between minor releases of Aspire is simple: + +1. In your AppHost project file (that is, _MyApp.AppHost.csproj_), update the [📦 Aspire.AppHost.Sdk](https://www.nuget.org/packages/Aspire.AppHost.Sdk) NuGet package to version `9.4.0`: + + ```xml + + ``` + + For more information, see [.NET Aspire SDK](xref:dotnet/aspire/sdk). + +1. Check for any NuGet package updates, either using the NuGet Package Manager in Visual Studio or the **Update NuGet Package** command from C# Dev Kit in VS Code. +1. Update to the latest [.NET Aspire templates](../fundamentals/aspire-sdk-templates.md) by running the following .NET command line: + + ```dotnetcli + dotnet new update + ``` + + > The `dotnet new update` command updates all of your templates to the latest version. + +If your AppHost project file doesn't have the `Aspire.AppHost.Sdk` reference, you might still be using .NET Aspire 8. To upgrade to 9.0, follow [the upgrade guide](../get-started/upgrade-to-aspire-9.md). + +## 🛠️ Aspire CLI is generally available + +With the release of Aspire 9.4, the Aspire CLI is generally available. To install the Aspire CLI as an AOT compiled binary, use the following helper scripts: + +```bash +# Bash +curl -sSL https://aspire.dev/install.sh | bash + +# PowerShell +iex "& { $(irm https://aspire.dev/install.ps1) }" +``` + +This will install the CLI and put it on your PATH (the binaries are placed in the `$HOME/.aspire/bin` path). If you choose you can also install the CLI as a non-AOT .NET global tool using: + +```dotnetcli +dotnet tool install -g Aspire.Cli +``` + +> [!NOTE] +> ⚠️ **The Aspire 9.4 CLI is not compatible with Aspire 9.3 projects.** +> You must upgrade your project to Aspire 9.4+ in order to use the latest CLI features. + +### 🎯 CLI Commands + +The Aspire CLI has the following [commands](../cli-reference/aspire.md): + +- `aspire new`: Creates a new Aspire project from templates. +- `aspire run`: Finds and runs the existing apphost from anywhere in the repo. +- `aspire add`: Adds a hosting integration package to the apphost. +- `aspire config [get|set|delete|list]`: Configures Aspire settings and feature flags. +- `aspire publish` (Preview): Generates deployment artifacts based on the apphost. + +In addition to these core commands, we have two beta commands behind [feature flags](../cli-reference/aspire-config.md): + +- `aspire exec`: Invokes an arbitrary command in the context of an executable resource defined in the apphost (ie, inheriting its environment variables). +- `aspire deploy`: Extends the capabiltiies of `aspire publish` to actively deploy to a target environment. + +#### `aspire exec` + +The new `exec` command allows you to execute commands within the context of your Aspire application environment: + +```bash +# Execute commands, like migrataions, with environment variables from your app model +aspire exec --resource my-api -- dotnet ef database update + +# Run scripts with access to application context +aspire exec --start-resource my-worker -- npm run build + +# The exec command automatically provides environment variables +# from your Aspire application resources to the executed command +``` + +**Key capabilities**: + +- **Environment variable injection** from your app model resources +- **Resource targeting** with `--resource` or `--start-resource` options +- **Command execution** in the context of your Aspirified application + +> [!IMPORTANT] +> 🧪 **Feature Flag**: The `aspire exec` command is behind a feature flag and **disabled by default** in this release. It must be explicitly enabled for use with `aspire config set features.execCommandEnabled true`. + +#### `aspire deploy` + +The `aspire deploy` command supports extensible deployment workflows through the new [`DeployingCallbackAnnotation`](../fundamentals/annotations-overview.md), enabling custom pre/post-deploy logic and richer integration with external systems during deployment operations. + +**Key capabilities:** + +- **Custom deployment hooks** using `Aspire.Hosting.Publishing.DeployingCallbackAnnotation` to execute custom logic during the `aspire deploy` command +- **Workflow activity reporting** via the to support progress notifications and prompting in commmands +- **Integration with publish** - `aspire deploy` runs `Aspire.Hosting.Publishing.PublishingCallbackAnnotations` to support deploying artifacts emitted by publish steps, if applicable + +The example below demonstrates using the `DeployingCallbackAnnotation` to register custom deployment behavior and showcases [CLI-based prompting](#-enhanced-publish-and-deploy-output) and progress notifications. + +```csharp +#pragma warning disable ASPIREPUBLISHERS001 +#pragma warning disable ASPIREINTERACTION001 + +using Aspire.Hosting.Publishing; +using Microsoft.Extensions.DependencyInjection; + +var builder = DistributedApplication.CreateBuilder(args); + +// Custom deployment step defined below +builder.AddDataSeedJob("SeedInitialData", seedDataPath: "data/seeds"); + +builder.Build().Run(); + +internal class DataSeedJobResource(string name, string seedDataPath) + : Resource(name) +{ + public string SeedDataPath { get; } = seedDataPath; +} + +internal static class DataSeedJobBuilderExtensions +{ + public static IResourceBuilder AddDataSeedJob( + this IDistributedApplicationBuilder builder, + string name, + string seedDataPath = "data/seeds") + { + var job = new DataSeedJobResource(name, seedDataPath); + var resourceBuilder = builder.AddResource(job); + + // Attach a DeployingCallbackAnnotation that will be invoked on `aspire deploy` + job.Annotations.Add(new DeployingCallbackAnnotation(async ctx => + { + CancellationToken ct = ctx.CancellationToken; + + // Prompt the user for a confirmation using the interaction service + var interactionService = ctx.Services.GetRequiredService(); + + var envResult = await interactionService.PromptInputAsync( + "Environment Configuration", + "Please enter the target environment name:", + new InteractionInput + { + Label = "Environment Name", + InputType = InputType.Text, + Required = true, + Placeholder = "dev, staging, prod" + }, + cancellationToken: ct); + + + // Use the ActivityReporter to report progress on the seeding process + var reporter = ctx.ActivityReporter; + + var step = await reporter.CreateStepAsync("Seeding data", ct); + var task = await step.CreateTaskAsync($"Loading seed data from {seedDataPath}", ct); + + try + { + // Do some work here + await Task.Delay(3000); + + await task.SucceedAsync("Seed data loaded", ct); + await step.SucceedAsync("Data seeding completed", ct); + } + catch (Exception ex) + { + await task.FailAsync(ex.Message, ct); + await step.FailAsync("Data seeding failed", ct); + throw; + } + })); + + return resourceBuilder; + } +} +``` + +This custom deployment logic executes as follows from the `aspire deploy` command. + +![aspire-deploy-whats-new](https://github.com/user-attachments/assets/15c6730d-8154-496a-be70-c67257ce5523) + +Now, integration owners can create sophisticated `aspire deploy` workflows. This work also provides a foundation for advanced deployment automation scenarios. + +> [!NOTE] +> While the `Aspire.Hosting.Publishing.DeployingCallbackAnnotation` API is available in .NET Aspire 9.4, there are currently no built-in resources that natively support deployment callbacks. Built-in resource support for deployment callbacks will be added in the next version of .NET Aspire. + +> [!IMPORTANT] +> 🧪 **Feature Flag**: The `aspire deploy` command is behind a feature flag and **disabled by default** in this release. It must be explicitly enabled for use with `aspire config set features.deployCommandEnabled true` + +### 📃 Enhanced publish and deploy output + +.NET Aspire 9.4 significantly improves the feedback and progress reporting during publish and deploy operations, providing clearer visibility into what's happening during deployment processes. + +**Key improvements:** + +- **Enhanced progress reporting** with detailed step-by-step feedback during publishing +- **Cleaner output formatting** that makes it easier to follow deployment progress +- **Better error messaging** with more descriptive information when deployments fail +- **Improved publishing context** that tracks and reports on resource deployment status +- **Container build logs** provide clear status updates during container operations + +These improvements make it much easier to understand what's happening during `aspire deploy` and `aspire publish` operations, helping developers debug issues more effectively and gain confidence in their deployment processes. + +The enhanced output is particularly valuable for: + +- **CI/CD pipelines** where clear logging is essential for troubleshooting +- **Complex deployments** with multiple resources and dependencies +- **Container-based deployments** where build and push operations need clear status reporting +- **Team environments** where deployment logs need to be easily interpreted by different team members + +For more information about publishing and deploying Aspire apps, see [aspire deploy](../cli-reference/aspire-deploy.md). + +## 🖥️ App model enhancements + +### 🎛️ Interaction service + +.NET Aspire 9.4 introduces the [interaction service](../extensibility/interaction-service.md), a general service that allows developers to build rich experiences at runtime by extending the dashboard UX and at publish and deploy time using the Aspire CLI. It allows you to build complex interactions where input is required from the user. + +> [!IMPORTANT] +> 🧪 This feature is experimental and may change in future releases. + +:::image type="content" source="media/dashboard-interaction-service.gif" lightbox="media/dashboard-interaction-service.gif" alt-text="Recording of using the interaction service in the dashboard."::: + +The interaction system supports: + +- Confirmation prompts for destructive operations +- Input collection with validation +- Multi-step workflows +- Dashboard interactions during run mode +- CLI interactions during deploy and publish operations + +```csharp +// Example usage of IInteractionService APIs +public class DeploymentService +{ + private readonly IInteractionService _interactionService; + + public DeploymentService(IInteractionService interactionService) + { + _interactionService = interactionService; + } + + public async Task DeployAsync() + { + // Prompt for confirmation before destructive operations + var confirmResult = await _interactionService.PromptConfirmationAsync( + "Confirm Deployment", + "This will overwrite the existing deployment. Continue?"); + + if (confirmResult.Canceled || !confirmResult.Data) + { + return; + } + + // Collect multiple inputs with validation + var regionInput = new InteractionInput { Label = "Region", InputType = InputType.Text, Required = true }; + var instanceCountInput = new InteractionInput { Label = "Instance Count", InputType = InputType.Number, Required = true }; + var enableMonitoringInput = new InteractionInput { Label = "Enable Monitoring", InputType = InputType.Boolean }; + + var multiInputResult = await _interactionService.PromptInputsAsync( + "Advanced Configuration", + "Configure deployment settings:", + [regionInput, instanceCountInput, enableMonitoringInput], + new InputsDialogInteractionOptions + { + ValidationCallback = async context => + { + if (!IsValidRegion(regionInput.Value)) + { + context.AddValidationError(regionInput, "Invalid region specified"); + } + } + }); + + if (multiInputResult.Canceled) + { + return; + } + + await RunDeploymentAsync( + region: regionInput.Value, + instanceCount: instanceCountInput.Value, + enableMonitoring: enableMonitoringInput.Value); + + // Show progress notifications + await _interactionService.PromptNotificationAsync( + "Deployment Status", + "Deployment completed successfully!", + new NotificationInteractionOptions + { + Intent = MessageIntent.Success, + LinkText = "View Dashboard", + LinkUrl = "https://portal.azure.com" + }); + } + + private bool IsValidRegion(string? region) + { + // Validation logic here + return !string.IsNullOrEmpty(region); + } +} +``` + +**Input types supported:** + +- `Text` - Standard text input +- `SecretText` - Password/secret input (masked) +- `Choice` - Dropdown selection +- `Boolean` - Checkbox input +- `Number` - Numeric input + +**Advanced features:** + +- **Validation callbacks** for complex input validation +- **Markdown support** for rich text descriptions +- **Custom button text** and dialog options +- **Intent-based styling** for different message types +- **Link support** in notifications + +These interactions work seamlessly whether you're running your application through the [Aspire dashboard](#-dashboard-improvements) or deploying via the CLI with `aspire deploy` and `aspire publish` commands. + +### 🔄 Interactive parameter prompting during run mode + +.NET Aspire 9.4 introduces interactive parameter prompting, automatically collecting missing parameter values in the dashboard during application startup through the new [interaction service](#-interaction-service). + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +// Parameters without default values will trigger prompts +var apiKey = builder.AddParameter("api-key", secret: true); +var dbPassword = builder.AddParameter("db-password", secret: true); + +// This also works for values that could be defined in appsettings.json +var environment = builder.AddParameterFromConfiguration("environment", "ENVIRONMENT_VARIABLE"); + +// Application will prompt for these values if not provided +var database = builder.AddPostgres("postgres", password: dbPassword); +var api = builder.AddProject("api") + .WithEnvironment("API_KEY", apiKey) + .WithEnvironment("ENVIRONMENT", environment) + .WithReference(database); + +builder.Build().Run(); +``` + +**Interactive experience:** + +- **Automatically detects parameters** that are missing so there aren't startup failures +- **Dashboard prompts** with interactive forms and Markdown-enabled parameter descriptions +- **Validation support** for enforcing rules (required, length, casing, etc) +- **Secret masking** so sensitive input isn't shown while being entered +- **Save to user secrets** for persistent per-project value storage outside of source control + +This feature eliminates the need to pre-configure all parameters in appsettings.json or .env files before running your Aspirified application, so you can clone, run, and be guided through what values are needed to run the full stack. + +#### 📝 Enhanced parameter descriptions and custom input rendering + +Building on the interactive parameter prompting capabilities and the new [interaction service](#-interaction-service), Aspire 9.4 introduces rich parameter descriptions and custom input rendering to provide better user guidance and specialized input controls during parameter collection. + +- **Aspire.Hosting.ParameterResourceBuilderExtensions.WithDescription** - Add helpful descriptions to guide users during parameter input +- **Markdown support** - Rich text descriptions with links, formatting, and lists using `enableMarkdown: true` +- **Aspire.Hosting.ParameterResourceBuilderExtensions.WithCustomInput** - Create specialized input controls for specific parameter types + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +// Parameters with descriptions provide better user guidance +var apiKey = builder.AddParameter("api-key", secret: true) + .WithDescription("API key for external service authentication"); + +var environment = builder.AddParameter("environment") + .WithDescription("Target deployment environment (dev, staging, prod)"); + +// Parameters with rich markdown descriptions +var configValue = builder.AddParameter("config-value") + .WithDescription(""" + Configuration value with detailed instructions: + + - Use **development** for local testing + - Use **staging** for pre-production validation + - Use **production** for live deployments + + See [configuration guide](https://docs.company.com/config) for details. + """, enableMarkdown: true); + +// Custom input rendering for specialized scenarios +var workerCount = builder.AddParameter("worker-count") + .WithDescription("Number of background worker processes") + .WithCustomInput(p => new InteractionInput + { + InputType = InputType.Number, + Label = "Worker Count", + Placeholder = "Enter number (1-10)", + Description = p.Description + }); + +var deploymentRegion = builder.AddParameter("region") + .WithDescription("Azure region for deployment") + .WithCustomInput(p => new InteractionInput + { + InputType = InputType.Choice, + Label = "Deployment Region", + Description = p.Description, + Options = new[] + { + KeyValuePair.Create("eastus", "East US"), + KeyValuePair.Create("westus", "West US"), + KeyValuePair.Create("northeurope", "North Europe"), + KeyValuePair.Create("southeastasia", "Southeast Asia") + } + }); + +var api = builder.AddProject("api") + .WithEnvironment("API_KEY", apiKey) + .WithEnvironment("ENVIRONMENT", environment) + .WithEnvironment("CONFIG_VALUE", configValue) + .WithEnvironment("WORKER_COUNT", workerCount) + .WithEnvironment("REGION", deploymentRegion); + +builder.Build().Run(); +``` + +For more information, including supported input types, see the [Interaction Service section](#-interaction-service) below or the full [interaction service docs](../extensibility/interaction-service.md). + +### 🌐 External service modeling + +Modern applications frequently need to integrate with external APIs, third-party services, or existing infrastructure that isn't managed by Aspire. .NET Aspire 9.4 introduces first-class support for [modeling external services](../fundamentals/orchestrate-resources.md#express-external-service-resources) as resources in your application graph. + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +// Reference an external service by URL +var externalApi = builder.AddExternalService("external-api", "https://api.company.com"); + +// Or use a parameter for dynamic configuration +var apiUrl = builder.AddParameter("api-url"); +var externalDb = builder.AddExternalService("external-db", apiUrl) + .WithHttpHealthCheck("/health"); + +var myService = builder.AddProject("my-service") + .WithReference(externalApi) + .WithReference(externalDb); + +builder.Build().Run(); +``` + +External services appear in the Aspire dashboard with health status, can be referenced like any other resource, and support the same configuration patterns as internal resources. + +### 🔗 Enhanced endpoint URL support + +.NET Aspire 9.4 introduces support for [non-localhost URLs](../fundamentals/networking-overview.md), making it easier to work with custom domains and network configurations. This includes support for `*.localhost` subdomains and automatic generation of multiple URL variants for endpoints listening on multiple addresses. + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +// Endpoints targeting all addresses automatically get multiple URL variants +var api = builder.AddProject("api") + .WithEndpoint("https", e => e.TargetHost = "0.0.0.0"); + +// Machine name URLs for external access +var publicService = builder.AddProject("public") + .WithEndpoint("https", e => e.TargetHost = "0.0.0.0"); + +builder.Build().Run(); +``` + +**Key capabilities:** + +- **Custom `*.localhost` subdomain support** that maintains localhost behavior +- **Automatic endpoint URL generation** for endpoints listening on multiple addresses, with both localhost and machine name URLs (such as Codespaces) +- **All URL variants** appear in the Aspire dashboard for easy access +- **Network flexibility** for development scenarios requiring specific network configurations +- **Launch profile configuration support** so custom URLs can also be configured via launch profiles in `launchSettings.json`: + +```json +{ + "profiles": { + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://*:7001;http://*:5001", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} +``` + +This simplifies development workflows where custom domains or external network access is needed while maintaining the familiar localhost development experience. A popular example includes SaaS solutions which use custom domains per-tenant. + +### 🐳 Enhanced persistent container support + +.NET Aspire 9.4 improves support for [persistent containers](../app-host/persistent-containers.md) with better lifecycle management and networking capabilities, ensuring containers can persist across application restarts while maintaining proper connectivity. + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +// Persistent containers with improved lifecycle support +var database = builder.AddPostgres("postgres") + .WithLifetime(ContainerLifetime.Persistent) + .WithExplicitStart(); // Better support for explicit start with persistent containers + +// Persistent containers automatically also get persistent networking +var redis = builder.AddRedis("redis") + .WithLifetime(ContainerLifetime.Persistent); + +var api = builder.AddProject("api") + .WithReference(database) + .WithReference(redis); + +builder.Build().Run(); +``` + +**Enhanced capabilities:** + +- **Improved lifecycle coordination** between `Aspire.Hosting.ResourceBuilderExtensions.WithExplicitStart` and `ContainerLifetime.Persistent` +- **Automatic persistent networking** spun up when persistent containers are detected +- **Container delay start** for more reliable startup sequencing +- **Network isolation** between persistent and session-scoped containers, which now use separate networks for better resource management + +This will greatly improve your experience while building stateful services that persist beyond individual application runs. + +### 🎛️ Resource command service + +.NET Aspire 9.4 introduces `Aspire.Hosting.ApplicationModel.ResourceCommandService`, an API for executing commands against resources. You can now easily execute the commands that appear in the dashboard programmatically. For example, when writing unit tests for commands, or having other integrations in Aspire execute commands. + +The example below uses `ResourceCommandService` to have a command execute other commands. + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +var database = builder.AddPostgres("postgres") + .WithHttpCommand("admin-restart", "Restart Database", + commandName: "db-restart", + commandOptions: new HttpCommandOptions + { + Method = HttpMethod.Post, + Description = "Restart the PostgreSQL database" + }); + +var cache = builder.AddRedis("cache") + .WithHttpCommand("admin-flush", "Flush Cache", + commandName: "cache-flush", + commandOptions: new HttpCommandOptions + { + Method = HttpMethod.Delete, + Description = "Clear all cached data" + }); + +// Add a composite command that coordinates multiple operations +var api = builder.AddProject("api") + .WithReference(database) + .WithReference(cache) + .WithCommand("reset-all", "Reset Everything", async (context, ct) => + { + var logger = context.ServiceProvider.GetRequiredService>(); + var commandService = context.ServiceProvider.GetRequiredService(); + + logger.LogInformation("Starting full system reset..."); + + try + { + var flushResult = await commandService.ExecuteCommandAsync(cache.Resource, "cache-flush", ct); + var restartResult = await commandService.ExecuteCommandAsync(database.Resource, "db-restart", ct); + if (!restartResult.Success || !flushResult.Success) + { + return CommandResults.Failure($"System reset failed"); + } + + logger.LogInformation("System reset completed successfully"); + return CommandResults.Success(); + } + catch (Exception ex) + { + logger.LogError(ex, "System reset failed"); + return CommandResults.Failure(ex); + } + }, + displayDescription: "Reset cache and restart database in coordinated sequence", + iconName: "ArrowClockwise"); + +builder.Build().Run(); +``` + +`ResourceCommandService` can also be used in unit tests: + +```csharp +[Fact] +public async Task Should_ResetCache_WhenTestStarts() +{ + var builder = DistributedApplication.CreateBuilder(); + + // Add cache with reset command for testing + var cache = builder.AddRedis("test-cache") + .WithHttpCommand("reset", "Reset Cache", + commandName: "reset-cache", + commandOptions: new HttpCommandOptions + { + Method = HttpMethod.Delete, + Description = "Clear all cached test data" + }); + + var api = builder.AddProject("test-api") + .WithReference(cache); + + await using var app = builder.Build(); + await app.StartAsync(); + + // Reset cache before running test + var result = await app.ResourceCommands.ExecuteCommandAsync( + cache.Resource, + "reset-cache", + CancellationToken.None); + + Assert.True(result.Success, $"Failed to reset cache: {result.ErrorMessage}"); +} +``` + +### 🔄 Resource lifecycle events + +.NET Aspire 9.4 introduces convenient extension methods on `Aspire.Hosting.ApplicationModel.IResourceBuilder` that make it much easier to subscribe to [lifecycle events](../app-host/eventing.md#app-host-life-cycle-events) directly on resources, providing a cleaner and more intuitive API. + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +var database = builder.AddPostgres("postgres") + .AddDatabase("mydb") + .OnConnectionStringAvailable(async (resource, evt, cancellationToken) => + { + // Log when connection strings are resolved + var logger = evt.Services.GetRequiredService>(); + logger.LogInformation("Connection string available for {Name}", resource.Name); + }); + +var api = builder.AddProject("api") + .WithReference(database) + .OnInitializeResource(async (resource, evt, cancellationToken) => + { + // Early resource initialization + var logger = evt.Services.GetRequiredService>(); + logger.LogInformation("Initializing resource {Name}", resource.Name); + }) + .OnBeforeResourceStarted(async (resource, evt, cancellationToken) => + { + // Pre-startup validation or configuration + var serviceProvider = evt.Services; + // Additional validation logic here + }) + .OnResourceEndpointsAllocated(async (resource, evt, cancellationToken) => + { + // React to endpoint allocation + var logger = evt.Services.GetRequiredService>(); + logger.LogInformation("Endpoints allocated for {Name}", resource.Name); + }) + .OnResourceReady(async (resource, evt, cancellationToken) => + { + // Resource is fully ready + var logger = evt.Services.GetRequiredService>(); + logger.LogInformation("Resource {Name} is ready", resource.Name); + }); + +// Example: Database seeding using OnResourceReady +var db = builder.AddMongoDB("mongo") + .WithMongoExpress() + .AddDatabase("db") + .OnResourceReady(async (db, evt, ct) => + { + // Seed the database with initial data + var connectionString = await db.ConnectionStringExpression.GetValueAsync(ct); + using var client = new MongoClient(connectionString); + + var myDb = client.GetDatabase("db"); + await myDb.CreateCollectionAsync("entries", cancellationToken: ct); + + // Insert sample data + for (int i = 0; i < 10; i++) + { + await myDb.GetCollection("entries").InsertOneAsync(new Entry(), cancellationToken: ct); + } + }); + +builder.Build().Run(); +``` + +**Available lifecycle events:** + +- `OnInitializeResource` - Called during early resource initialization +- `OnBeforeResourceStarted` - Called before the resource starts +- `OnConnectionStringAvailable` - Called when connection strings are resolved (requires `IResourceWithConnectionString`) +- `OnResourceEndpointsAllocated` - Called when resource endpoints are allocated (requires `IResourceWithEndpoints`) +- `OnResourceReady` - Called when the resource is fully ready + +The new chainable fluent API, strongly-typed callbacks, and simplified syntax make it intuitive to hook into your resource lifecycles for interactions, commands, custom scripts, and more. + +**Migration from manual eventing:** + +```csharp +// ❌ Before (manual eventing subscription): +builder.Eventing.Subscribe(db.Resource, async (evt, ct) => +{ + // Manual event handling with no type safety + var cs = await db.Resource.ConnectionStringExpression.GetValueAsync(ct); + // Process event... +}); + +// ✅ After (fluent extension methods): +var db = builder.AddMongoDB("mongo") + .AddDatabase("db") + .OnResourceReady(async (db, evt, ct) => + { + // Direct access to strongly-typed resource + var cs = await db.ConnectionStringExpression.GetValueAsync(ct); + // Process event... + }); +``` + +The new extension methods make it much easier to implement common patterns like database seeding, configuration validation, and resource health checks. Note that the old mechanism is not being deprecated, the new methods simply provide a more natural programming model when using the builder pattern. + +### 📁 Enhanced container file mounting + +Configuring container file systems often requires understanding complex Docker volume syntax and managing file permissions manually. .NET Aspire 9.4 introduces enhanced file mounting APIs that handle common scenarios with sensible defaults. + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +// Simple file copying from local source to container +var myContainer = builder.AddContainer("myapp", "myapp:latest") + .WithContainerFiles("/app/config", "./config-files") + .WithContainerFiles("/app/data", "./data", defaultOwner: 1000, defaultGroup: 1000) + .WithContainerFiles("/app/scripts", "./scripts", umask: UnixFileMode.UserRead | UnixFileMode.UserWrite); + +// You can also use the callback approach for dynamic file generation +var dynamicContainer = builder.AddContainer("worker", "worker:latest") + .WithContainerFiles("/app/runtime-config", async (context, ct) => + { + // Generate configuration files dynamically + var configFile = new ContainerFileSystemItem + { + Name = "app.json", + Contents = JsonSerializer.SerializeToUtf8Bytes(new { Environment = "Production" }) + }; + + return new[] { configFile }; + }); + +builder.Build().Run(); +``` + +The [enhanced APIs](../fundamentals/persist-data-volumes.md) handle file permissions, ownership, and provide both static and dynamic file mounting capabilities while maintaining the flexibility to customize when needed. + +### ✨ Advanced YARP routing with transform APIs (Preview) + +> [!NOTE] +> The [YARP integration](../proxies/yarp-integration.md) is currently in preview and APIs may change in future releases. + +Building sophisticated reverse proxy configurations has traditionally required deep knowledge of YARP's transform system and manual JSON configuration. .NET Aspire 9.4 introduces a comprehensive set of fluent APIs that make advanced routing transformations accessible through strongly-typed C# code. + +**Breaking change in 9.4:** The `WithConfigFile()` method has been removed and replaced with a code-based configuration model. This new approach works seamlessly with deployment scenarios as the strongly-typed configuration methods translate directly into the appropriate environment variables. + +You can now programmatically configure request/response transformations, header manipulation, path rewriting, and query string handling directly from your app model—no more wrestling with complex configuration files. + +**Example 1: Simple path-based routing with path prefix removal** + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +var apiV1 = builder.AddProject("api-v1"); +var apiV2 = builder.AddProject("api-v2"); + +var yarp = builder.AddYarp("gateway") + .WithConfiguration(yarpBuilder => + { + // Route /v1/* requests to api-v1, removing the /v1 prefix + yarpBuilder.AddRoute("/v1/{**catch-all}", apiV1) + .WithTransformPathRemovePrefix("/v1"); + + // Route /v2/* requests to api-v2, removing the /v2 prefix + yarpBuilder.AddRoute("/v2/{**catch-all}", apiV2) + .WithTransformPathRemovePrefix("/v2"); + }); + +builder.Build().Run(); +``` + +**Example 2: Host-based routing** + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +var adminApi = builder.AddProject("admin-api"); +var publicApi = builder.AddProject("public-api"); + +var yarp = builder.AddYarp("gateway") + .WithConfiguration(yarpBuilder => + { + // Route admin.example.com to admin API + yarpBuilder.AddRoute(adminApi) + .WithMatchHosts("admin.example.com"); + + // Route api.example.com to public API + yarpBuilder.AddRoute(publicApi) + .WithMatchHosts("api.example.com"); + + // Default route for any other host + yarpBuilder.AddRoute("/{**catch-all}", publicApi); + }); + +builder.Build().Run(); +``` + +**Example 3: Advanced routing with comprehensive transforms** + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +var backendApi = builder.AddProject("backend-api"); +var identityService = builder.AddProject("identity-service"); + +var yarp = builder.AddYarp("gateway") + .WithConfiguration(yarpBuilder => + { + // Configure sophisticated routing with transforms + yarpBuilder.AddRoute("/api/v1/{**catch-all}", backendApi) + .WithTransformPathPrefix("/v2") // Rewrite /api/v1/* to /v2/* + .WithTransformRequestHeader("X-API-Version", "2.0") + .WithTransformForwarded(useHost: true, useProto: true) + .WithTransformResponseHeader("X-Powered-By", "Aspire Gateway"); + + // Advanced header and query manipulation + yarpBuilder.AddRoute("/auth/{**catch-all}", identityService) + .WithTransformClientCertHeader("X-Client-Cert") + .WithTransformQueryValue("client_id", "aspire-app") + .WithTransformRequestHeadersAllowed("Authorization", "Content-Type") + .WithTransformUseOriginalHostHeader(false); + }); + +builder.Build().Run(); +``` + +#### Migration from YARP 9.3 to 9.4 + +If you were using `WithConfigFile()` in .NET Aspire 9.3, you'll need to migrate to the new code-based configuration model shown above. The strongly-typed APIs provide better IntelliSense support and work seamlessly with deployment scenarios. + +> [!NOTE] +> We are working on a more general-purpose solution for file-based configuration during deployment. File-based configuration support will return in a future version of .NET Aspire. + +This eliminates the need for complex YARP configuration files while providing complete access to YARP's powerful transformation pipeline through a fluent API. + +### 🔒 Enhanced Docker Compose deployment security + +.NET Aspire 9.4 improves [Docker Compose publish](../deployment/overview.md) security with smart port mapping - only external endpoints are exposed to the host while internal services use Docker's internal networking. + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +var compose = builder.AddDockerComposeEnvironment("production"); + +// Add a service with both internal and external endpoints +var webService = builder.AddContainer("webservice", "nginx") + .WithEndpoint(scheme: "http", port: 8080, name: "internal") // Internal endpoint + .WithEndpoint(scheme: "http", port: 8081, name: "api", isExternal: true); // External endpoint + +builder.Build().Run(); +``` + +**Generated Docker Compose output:** + +```yaml +services: + webservice: + image: "nginx:latest" + ports: + - "8081:8001" # Only external endpoints get port mappings (host:container) + expose: + - "8000" # Internal endpoints use expose (container port only) + networks: + - "aspire" +``` + +Now, only `isExternal: true` endpoints are exposed to host, and internal endpoints use Docker's `expose` for container-to-container communication. + +## 🎨 Dashboard improvements + +> [!TIP] +> For a bite sized look at many of the 9.4 dashboard changes, James Newton-King has kept up his tradition of posting one new dashboard feature a day leading up to an Aspire release on his [BlueSky](https://bsky.app/profile/james.newtonking.com)! + +### 🔔 Automatic upgrade check notifications + +.NET Aspire 9.4 includes an update notification system that automatically checks for newer versions and notifies developers when updates are available, making sure you stay current with the latest improvements and security updates. + +When a newer version is detected, a friendly notification appears in the Aspire dashboard: + +:::image type="content" source="media/dashboard-update-notification.png" lightbox="media/dashboard-update-notification.png" alt-text="Screenshot of dashboard showing an update notification."::: + +Aspire only shows notifications when a newer version is available, and the checks happen in the background without impacting application startup or performance. The upgrade check system can be disable by setting the `ASPIRE_VERSION_CHECK_DISABLED` environment variable to `true`. For more information, see [App host configuration](/dotnet/aspire/app-host/configuration). + +### 📋 Parameters and connection strings visible in dashboard + +.NET Aspire 9.4 makes parameters and connection strings visible in the Aspire dashboard, providing better visibility into your application's configuration and connectivity status during development. + +Connection strings: + +- Appear in the **resource details** panel for any resource that implements `IResourceWithConnectionString` +- Values are marked as **sensitive** and can be toggled for visibility in the dashboard +- Supports all resource types including databases, message brokers, and custom resources + +:::image type="content" source="media/dashboard-connection-strings.png" lightbox="media/dashboard-connection-strings.png" alt-text="Screenshot of dashboard showing connection string."::: + +External parameters are no longer hidden. The parameter state and value is visible in the dashboard. + +:::image type="content" source="media/dashboard-parameters.png" lightbox="media/dashboard-parameters.png" alt-text="Screenshot of dashboard showing parameters."::: + +For more information, see [external parameters](/dotnet/aspire/fundamentals/external-parameters). + +### 🔗 Enhanced dashboard peer visualization for uninstrumented resources + +.NET Aspire 9.4 lets you observe connections between resources even when they aren't instrumented with telemetry. + +For example, the screenshot below shows a call to a GitHub model resolving to the model resource in Aspire: + +:::image type="content" source="media/dashboard-tracing-peers.png" lightbox="media/dashboard-tracing-peers.png" alt-text="Screenshot of a span linked to a GitHub model resource defined in Aspire."::: + +OpenTelemetry spans can now resolve to peers that are defined by parameters, connection strings, GitHub Models, and external services: + +- **Connection string parsing** supports SQL Server, PostgreSQL, MySQL, MongoDB, Redis, and many other connection string formats +- **Visualize parameters** with URLs or connection strings and how they connect to services +- **GitHub Models integration** for GitHub-hosted AI models with proper state management +- **External service mapping** between your services and external dependencies + +### 📋 Console logs text wrapping control + +.NET Aspire 9.4 introduces a new toggle option in the dashboard console logs to control text wrapping behavior, giving you better control over how long log lines are displayed. + +:::image type="content" source="media/dashboard-console-logs-wrapping.gif" lightbox="media/dashboard-console-logs-wrapping.gif" alt-text="Recording of toggling line wrapping on console logs page."::: + +Some Aspire users have run into trouble with viewing large console logs, which is tracked in this GitHub issue: [Console logs not showing, plus browser window size affecting displayed logs #7969](https://github.com/dotnet/aspire/issues/7969). If you're having trouble with logs please try experimenting with disabling wrapping and see whether it improves your user experience. Feedback on this issue would be very helpful. + +### 👁️ Show/hide hidden resources in dashboard + +.NET Aspire 9.4 introduces the ability to show or hide hidden resources in the dashboard, giving you complete visibility into your application's infrastructure components and internal resources that are normally hidden from view. + +:::image type="content" source="media/dashboard-hidden-resources.png" lightbox="media/dashboard-hidden-resources.png" alt-text="Dashboard resources page with the show/hide hidden resources UI visible."::: + +If there are no hidden resources in your Aspire app then the show/hide UI is disabled. + +### 🏗️ Enhanced dashboard infrastructure with proxied endpoints + +.NET Aspire 9.4 introduces significant infrastructure improvements to the dashboard system, implementing proxied endpoints that make dashboard launching more reliable and avoiding port reuse problems. This architectural enhancement resolves issues with dashboard connectivity during application startup and shutdown scenarios. The UI when the dashboard is attempting to reconnect has also been updated to be more reliable and with a new cohesive look and animation. + +### 🐳 Docker Compose with integrated Aspire Dashboard + +Managing observability in Docker Compose environments often requires running separate monitoring tools or losing the rich insights that Aspire provides during development. .NET Aspire 9.4 introduces native Aspire Dashboard integration for Docker Compose environments. + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +var compose = builder.AddDockerComposeEnvironment("production") + .WithDashboard(dashboard => dashboard.WithHostPort(8080)); // Configure dashboard with specific port + +// Add services that will automatically report to the dashboard +builder.AddProject("frontend"); +builder.AddProject("api"); + +builder.Build().Run(); +``` + +## 🔗 Updated integrations + +### 🐙 GitHub Models integration + +.NET Aspire 9.4 introduces support for [GitHub Models](https://docs.github.com/en/github-models), enabling easy integration with AI models hosted on GitHub's platform. This provides a simple way to incorporate AI capabilities into your applications using GitHub's model hosting service. + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +// Add GitHub Model - API key parameter is automatically created +var model = builder.AddGitHubModel("chat-model", "gpt-4o-mini"); + +// You can also specify an API key explicitly if needed +var apiKey = builder.AddParameter("github-api-key", secret: true); +var explicitModel = builder.AddGitHubModel("explicit-chat", "gpt-4o-mini") + .WithApiKey(apiKey); + +// Use the model in your services +var chatService = builder.AddProject("chat") + .WithReference(model); + +builder.Build().Run(); +``` + +The [GitHub Models integration](../github/github-models-integration.md) provides: + +- **Simple model integration** with GitHub's hosted AI models +- **Automatic API key parameter creation** with the pattern `{name}-gh-apikey` +- **Explicit API key support** using `WithApiKey()` for custom scenarios +- **GITHUB_TOKEN fallback** when no explicit API key is provided +- **Built-in health checks** for model availability + +### 🤖 Azure AI Foundry integration + +.NET Aspire 9.4 introduces comprehensive [Azure AI Foundry](https://ai.azure.com/) support, bringing enterprise AI capabilities directly into your distributed applications. This integration simplifies working with AI models and deployments through the Azure AI platform, supporting both Azure-hosted deployments and local development with [Foundry Local](https://github.com/microsoft/Foundry-Local). + +#### Hosting configuration + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +// Add Azure AI Foundry project +var foundry = builder.AddAzureAIFoundry("foundry"); + +// Add specific model deployments +var chat = foundry.AddDeployment("chat", "qwen2.5-0.5b", "1", "Microsoft"); +var embedding = foundry.AddDeployment("embedding", "text-embedding-ada-002", "2", "OpenAI"); + +// Connect your services to AI capabilities +var webService = builder.AddProject("webservice") + .WithReference(chat) + .WaitFor(chat); + +builder.Build().Run(); +``` + +##### Azure AI Foundry Local support + +[Azure AI Foundry Local](https://learn.microsoft.com/azure/ai-foundry/foundry-local/) is an on-device AI inference solution that runs models locally on your hardware, providing performance, privacy, and cost advantages without requiring an Azure subscription. It's ideal for scenarios requiring data privacy, offline operation, cost reduction, or low-latency responses. + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +// For local development, run with Foundry Local +var localFoundry = builder.AddAzureAIFoundry("foundry") + .RunAsFoundryLocal() + .AddDeployment("chat", "phi-3.5-mini", "1", "Microsoft"); + +var webService = builder.AddProject("webservice") + .WithReference(localFoundry) + .WaitFor(localFoundry); + +builder.Build().Run(); +``` + +#### Client integration + +Once you've configured the [Azure AI Foundry resource](../azureai/azureai-foundry-integration.md) in your app host, consume it in your services using the [Azure AI Inference SDK](../azureai/azureai-inference-integration.md) or [OpenAI SDK](../azureai/azureai-openai-integration.md) for compatible models: + +**Using Azure AI Inference SDK:** + +```csharp +// In Program.cs +var builder = WebApplication.CreateBuilder(args); + +builder.AddAzureChatCompletionsClient("chat") + .AddChatClient(); + +var app = builder.Build(); + +// Minimal API endpoint for chat completion +app.MapPost("/generate", async (IChatClient chatClient, ChatRequest request) => +{ + var messages = new List + { + new(ChatRole.System, "You are a helpful assistant."), + new(ChatRole.User, request.Prompt) + }; + + var response = await chatClient.GetResponseAsync(messages); + return Results.Ok(new { Response = response.Text }); +}); + +app.Run(); + +public record ChatRequest(string Prompt); +``` + +**Using OpenAI SDK (for compatible models):** + +```csharp +// In Program.cs +var builder = WebApplication.CreateBuilder(args); + +builder.AddOpenAIClient("chat") + .AddChatClient(); + +// Usage is identical to the Azure AI Inference SDK example above +``` + +**Key differences between Azure AI Foundry and Foundry Local:** + +- **Azure AI Foundry** - Cloud-hosted models with enterprise-grade scaling, supports all Azure AI model deployments +- **Foundry Local** - On-device inference with different model selection optimized for local hardware, no Azure subscription required + +The `RunAsFoundryLocal()` method enables local development scenarios using [Azure AI Foundry Local](https://learn.microsoft.com/azure/ai-foundry/foundry-local/), allowing you to test AI capabilities without requiring cloud resources during development. This supports automatic model downloading, loading, and management through the integrated Foundry Local runtime. + +### 🗄️ Database hosting improvements + +Several database integrations have been updated with **improved initialization patterns**: + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +// MongoDB - new WithInitFiles method (replaces WithInitBindMount) +var mongo = builder.AddMongoDB("mongo") + .WithInitFiles("./mongo-init"); // Initialize with scripts + +// MySQL - improved initialization with better file handling +var mysql = builder.AddMySql("mysql", password: builder.AddParameter("mysql-password")) + .WithInitFiles("./mysql-init"); // Initialize with SQL scripts + +// Oracle - enhanced setup capabilities with consistent API +var oracle = builder.AddOracle("oracle") + .WithInitFiles("./oracle-init"); // Initialize with Oracle scripts + +builder.Build().Run(); +``` + +All database providers now support `WithInitFiles()` method, replacing the more complex `WithInitBindMount()` method and enabling better error handling. + +## ☁️ Azure goodies + +### 🏷️ Consistent resource name exposure + +.NET Aspire 9.4 now consistently exposes the actual names of all Azure resources through `Aspire.Hosting.Azure.NameOutputReference` property. This enables applications to access the real Azure resource names that get generated during deployment, which is essential for scenarios requiring direct Azure resource coordination. This is particularly valuable for external automation scripts and monitoring and alerting systems that reference resources by their actual names. + +### 🗄️ Azure Cosmos DB + +#### Hierarchical partition keys + +.NET Aspire 9.4 introduces support for **hierarchical partition keys** (subpartitioning) in Azure Cosmos DB, enabling multi-level partitioning for better data distribution and query performance. + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +var cosmos = builder.AddAzureCosmosDB("cosmos"); +var database = cosmos.AddCosmosDatabase("ecommerce"); + +// Traditional single partition key +var ordersContainer = database.AddContainer("orders", "/customerId"); + +// New hierarchical partition keys (up to 3 levels) +var productsContainer = database.AddContainer("products", + ["/category", "/subcategory", "/brand"]); + +// Multi-tenant scenario +var eventsContainer = database.AddContainer("events", + ["/tenantId", "/userId", "/sessionId"]); + +builder.Build().Run(); +``` + +**Key benefits:** + +- **Scale beyond 20GB per logical partition** through multi-level distribution +- **Improved query performance** with efficient routing to relevant partitions +- **Better data distribution** for multi-dimensional datasets +- **Enhanced scalability** up to 10,000+ RU/s per logical partition prefix + +For detailed guidance on design patterns and best practices, see the [Azure Cosmos DB hierarchical partition keys documentation](https://learn.microsoft.com/azure/cosmos-db/hierarchical-partition-keys). + +#### Serverless support + +Azure Cosmos DB accounts now default to serverless mode for cost optimization with consumption-based billing. + +```csharp +// Default behavior: Creates serverless account (new in 9.4) +var cosmos = builder.AddAzureCosmosDB("cosmos"); + +// Explicitly enable provisioned throughput mode +var provisionedCosmos = builder.AddAzureCosmosDB("cosmos") + .WithDefaultAzureSku(); // Uses provisioned throughput instead of serverless +``` + +**Serverless benefits:** + +- **Pay-per-use** - Only charged for consumed Request Units and storage +- **No minimum costs** - Ideal for intermittent or unpredictable workloads +- **Automatic scaling** - No capacity planning required +- **Perfect for development/testing** environments + +**Use serverless for:** Variable workloads, development/testing, applications with low average-to-peak traffic ratios. +**Use provisioned throughput for:** Sustained traffic requiring predictable performance guarantees. + +For detailed comparison and limits, see [Azure Cosmos DB serverless documentation](https://learn.microsoft.com/azure/cosmos-db/serverless). + +### 🆔 Consistent user-assigned managed identity support + +.NET Aspire 9.4 introduces comprehensive support for Azure user-assigned managed identities, providing enhanced security and consistent identity management across your Azure infrastructure: + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +// Create a user-assigned managed identity +var appIdentity = builder.AddAzureUserAssignedIdentity("app-identity"); + +// Create the container app environment +var containerEnv = builder.AddAzureContainerAppEnvironment("container-env"); + +// Apply the identity to compute resources +var functionApp = builder.AddAzureFunctionsProject("functions") + .WithAzureUserAssignedIdentity(appIdentity); + +// The identity can be shared across multiple resources +var webApp = builder.AddProject("webapp") + .WithAzureUserAssignedIdentity(appIdentity); + +// Use the same identity for accessing Azure services +var keyVault = builder.AddAzureKeyVault("secrets"); +var storage = builder.AddAzureStorage("storage"); + +// Services using the shared identity can access resources securely +var processor = builder.AddProject("processor") + .WithAzureUserAssignedIdentity(appIdentity) + .WithReference(keyVault) + .WithReference(storage); + +builder.Build().Run(); +``` + +This approach provides: + +- **Flexible identity control** - Override Aspire's secure defaults when you need specific identity configurations +- **Consistent identity management** across all compute resources + +#### 🔐 Disabled local authentication to enforce managed identity + +.NET Aspire 9.4 automatically disables local authentication for [Azure EventHubs](../messaging/azure-event-hubs-integration.md) and [Azure Web PubSub](../messaging/azure-web-pubsub-integration.md) resources, enforcing managed identity authentication by default. + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +// Azure EventHubs with automatic local auth disabled +var eventHubs = builder.AddAzureEventHubs("eventhubs"); +var hub = eventHubs.AddEventHub("orders"); + +// Azure Web PubSub with automatic local auth disabled +var webPubSub = builder.AddAzureWebPubSub("webpubsub"); + +// Services connect using managed identity automatically +var processor = builder.AddProject("processor") + .WithReference(hub) + .WithReference(webPubSub); + +builder.Build().Run(); +``` + +This change automatically applies to all Azure EventHubs and Web PubSub resources, ensuring secure-by-default behavior. + +### 🔐 Azure Key Vault enhancements + +.NET Aspire 9.4 introduces significant improvements to the [Azure Key Vault integration](../security/azure-security-key-vault-integration.md) with new secret management APIs that provide strongly typed access to secrets: + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +var secrets = builder.AddAzureKeyVault("secrets"); + +// Add a secret from a parameter +var connectionStringParam = builder.AddParameter("connectionString", secret: true); +var connectionString = secrets.AddSecret("connection-string", connectionStringParam); + +// Add a secret with custom secret name in Key Vault +var apiKeyParam = builder.AddParameter("api-key", secret: true); +var apiKey = secrets.AddSecret("api-key", "ApiKey", apiKeyParam); + +// Get a secret reference for consumption (for existing secrets) +var existingSecret = secrets.GetSecret("ExistingSecret"); + +// Use in your services +var webApi = builder.AddProject("webapi") + .WithEnvironment("CONNECTION_STRING", connectionString) + .WithEnvironment("API_KEY", apiKey) + .WithEnvironment("EXISTING_SECRET", existingSecret); +``` + +**Key features**: + +- `Aspire.Hosting.Azure.KeyVault.AzureKeyVaultResourceExtensions.AddSecret` method for adding new secrets to Key Vault from parameters or expressions +- `Aspire.Hosting.Azure.KeyVault.AzureKeyVaultResourceExtensions.GetSecret` method for referencing existing secrets in Key Vault +- **Strongly-typed secret references** that can be used with `WithEnvironment()` for environment variables +- **Custom secret naming** support with optional `secretName` parameter + +#### 📥Resource Deep Linking for Azure Storage Queues + +.NET Aspire 9.4 expands resource deep linking to include Azure Queue Storage queues, building on the model already used for Azure Blob Storage, Cosmos DB, etc. + +You can now model individual storage queues directly in your app host, then inject scoped QueueClient instances into your services—making it easy to interact with queues without manually configuring connection strings or access. + +**AppHost:** + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +var storage = builder.AddAzureStorage("storage"); + +// Model individual queues as first-class resources +var orderQueue = storage.AddQueue("orders", "order-processing"); +var notificationQueue = storage.AddQueue("notifications", "user-notifications"); + +// Services get scoped access to specific queues +builder.AddProject("order-processor") + .WithReference(orderQueue); // Only has access to order-processing queue + +builder.AddProject("notifications") + .WithReference(notificationQueue); // Only has access to user-notifications queue + +builder.Build().Run(); +``` + +**In the OrderProcessor project:** + +```csharp +using Azure.Storage.Queues; + +var builder = WebApplication.CreateBuilder(args); + +// Register the queue client +builder.AddAzureQueue("orders"); + +var app = builder.Build(); + +// Minimal POST endpoint for image upload +app.MapPost("/process-order", async (QueueClient ordersQueue) => +{ + // read a message for the queue + var message = await ordersQueue.ReceiveMessageAsync(); + ProcessMessage(message); + + return Results.Ok(); +}); + +app.Run(); +``` + +This approach provides clean separation of concerns, secure container scoping, and minimal ceremony—ideal for microservices that interact with specific storage queues. + +### 📡 OpenTelemetry tracing support for Azure App Configuration + +.NET Aspire 9.4 introduces **OpenTelemetry tracing support** for [Azure App Configuration](../azure/azure-app-configuration-integration.md), completing the observability story for this integration. The Azure App Configuration integration now automatically instruments configuration retrieval operations and refresh operations with distributed tracing. + +```csharp +var builder = WebApplication.CreateBuilder(args); + +// Azure App Configuration now includes automatic tracing +builder.AddAzureAppConfiguration("config", settings => +{ + settings.Endpoint = new Uri("https://myconfig.azconfig.io"); + // Tracing is enabled by default - traces configuration operations +}); + +// Optionally disable tracing for specific scenarios +builder.AddAzureAppConfiguration("sensitive-config", settings => +{ + settings.DisableTracing = true; // Disable OpenTelemetry tracing +}); + +var app = builder.Build(); +``` + +**What gets traced:** + +- **Configuration retrieval operations** - When configuration values are loaded from Azure App Configuration +- **Configuration refresh operations** - When the configuration is refreshed in the background +- **Activity source**: `Microsoft.Extensions.Configuration.AzureAppConfiguration` - for filtering and correlation + +Tracing can be disabled using `DisableTracing = true` for sensitive scenarios. + +This enhancement brings Azure App Configuration in line with other Azure components that support comprehensive observability, providing developers with better insights into configuration-related performance and behavior. + +### ⚙️ Enhanced Azure provisioning interaction + +.NET Aspire 9.4 significantly improves the Azure provisioning experience by leveraging the interaction services to streamline Azure subscription and resource group configuration during deployment workflows. + +The enhanced Azure provisioning system: + +- **Automatically prompts for missing Azure configuration** during deploy operations +- **Saves configuration to user secrets** for future deployments +- **Provides smart defaults** like auto-generated resource group names +- **Includes validation callbacks** for Azure-specific inputs like subscription IDs and locations +- **Supports rich HTML prompts** with links to create free Azure accounts + +This enhancement makes Azure deployment significantly more user-friendly, especially for developers new to Azure or setting up projects for the first time. The interaction system ensures that all necessary Azure configuration is collected interactively and stored securely for subsequent deployments. + +### 🐳 Azure App Service container support + +.NET Aspire 9.4 introduces support for deploying containerized applications with Dockerfiles to Azure App Service environments. This enables a seamless transition from local container development to Azure App Service deployment. + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +// Create an Azure App Service environment +builder.AddAzureAppServiceEnvironment("app-service-env"); + +// Add a containerized project with Dockerfile +var containerApp = builder.AddContainer("my-app", "my-app:latest") + .WithDockerfile("./Dockerfile"); + +// Or add a project that builds to a container +var webApp = builder.AddProject("webapp"); + +builder.Build().Run(); +``` + +This feature bridges the gap between container development and Azure App Service deployment, allowing developers to use the same container-based workflows they use locally in production Azure environments. + +### 🏗️ Improvements to the Azure Container Apps integration + +Managing complex Azure Container Apps environments often requires integrating with existing Azure resources like Log Analytics workspaces. .NET Aspire 9.4 enhances the [Container Apps integration](../azure/configure-aca-environments.md) with support for existing Azure resources and improved configuration. + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +// Reference existing Log Analytics workspace +var workspaceName = builder.AddParameter("workspace-name"); +var workspaceRg = builder.AddParameter("workspace-rg"); + +var logWorkspace = builder.AddAzureLogAnalyticsWorkspace("workspace") + .AsExisting(workspaceName, workspaceRg); + +var containerEnv = builder.AddAzureContainerAppEnvironment("production") + .WithAzureLogAnalyticsWorkspace(logWorkspace); + +builder.AddProject("api") + .WithComputeEnvironment(containerEnv); + +builder.Build().Run(); +``` + +This also helps manage cost control by reusing existing resources like Log Analytics. + +#### 🛡️ Automatic DataProtection configuration for .NET on ACA + +.NET Aspire 9.4 automatically configures DataProtection for .NET projects deployed to Azure Container Apps, ensuring applications work correctly when scaling beyond a single instance. + +When ASP.NET Core applications scale to multiple instances, they need shared DataProtection keys to decrypt cookies, authentication tokens, and other protected data across all instances. Without proper configuration, users experience authentication issues and data corruption when load balancers route requests to different container instances. + +.NET Aspire now automatically enables `autoConfigureDataProtection` for all .NET projects deployed to Azure Container Apps: + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +builder.AddAzureContainerAppEnvironment("production"); + +// DataProtection is automatically configured for scaling +var api = builder.AddProject("api"); + +var frontend = builder.AddProject("frontend"); + +builder.Build().Run(); +``` + +This enhancement aligns Aspire-generated deployments with Azure Developer CLI (`azd`) behavior and resolves common production scaling issues without requiring manual DataProtection configuration. + +### ⚡ Azure Functions Container Apps integration + +.NET Aspire 9.4 improves Azure Functions deployment to Azure Container Apps by automatically setting the correct function app kind. This ensures Azure Functions are properly recognized and managed within the Azure Container Apps environment. + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +builder.AddAzureContainerAppEnvironment("functions-env"); + +// Azure Functions project deployed to Container Apps +var functionsApp = builder.AddAzureFunctionsProject("functions"); + +builder.Build().Run(); +``` + +This change resolves issues where Azure Functions deployed to Container Apps weren't properly recognized by Azure tooling and monitoring systems, providing a more seamless serverless experience. + +## 📋 Project template improvements + +.NET Aspire 9.4 introduces enhancements to project templates, including .NET 10 support and improved file naming conventions. + +### 🚀 .NET 10 framework support + +All .NET Aspire project templates now support .NET 10 with framework selection. .NET 9.0 remains the default target framework. + +```bash +# Create a new Aspire project targeting .NET 10 +dotnet new aspire --framework net10.0 + +# Create an app host project targeting .NET 10 +dotnet new aspire-apphost --framework net10.0 +``` + +### 📝 Improved file naming convention + +The `aspire-apphost` template now uses a more descriptive file naming convention making it easier to distinguish app host files in multi-project solutions. Instead of `Program.cs`, the main program file is now named `AppHost.cs`. + +The content and functionality remain unchanged — only the filename has been updated to be more descriptive. + +## 💔 Breaking changes + +### 🔑 Azure Key Vault secret reference changes + +Azure Key Vault secret handling has been updated with improved APIs that provide better type safety and consistency: + +```csharp +// ❌ Before (obsolete): +var keyVault = builder.AddAzureKeyVault("secrets"); +var secretOutput = keyVault.GetSecretOutput("ApiKey"); // Obsolete +var secretRef = new BicepSecretOutputReference(secretOutput); // Obsolete - class removed + +// ✅ After (recommended): +var keyVault = builder.AddAzureKeyVault("secrets"); +var secretRef = keyVault.GetSecret("ApiKey"); // New strongly-typed API + +// For environment variables: +// ❌ Before (obsolete): +builder.AddProject("api") + .WithEnvironment("API_KEY", secretRef); // Using BicepSecretOutputReference + +// ✅ After (recommended): +builder.AddProject("api") + .WithEnvironment("API_KEY", secretRef); // Using IAzureKeyVaultSecretReference +``` + +**Migration impact**: Replace `GetSecretOutput()` and `BicepSecretOutputReference` usage with the new `GetSecret()` method that returns `IAzureKeyVaultSecretReference`. + +### 📦 Azure Storage blob container creation changes + +Azure Storage blob container creation has been moved from specialized blob storage resources to the main storage resource for better consistency: + +```csharp +// ❌ Before (obsolete): +var storage = builder.AddAzureStorage("storage"); +var blobs = storage.AddBlobs("blobs"); +var container = blobs.AddBlobContainer("images"); // Obsolete + +// ✅ After (recommended): +var storage = builder.AddAzureStorage("storage"); +var container = storage.AddBlobContainer("images"); // Direct on storage resource +``` + +**Migration impact**: Use `AddBlobContainer()` directly on `AzureStorageResource` instead of on specialized blob storage resources. + +### 🔐 Keycloak realm import simplification + +The `WithRealmImport` method signature has been **simplified by removing the confusing `isReadOnly` parameter**: + +```csharp +// ❌ Before (deprecated): +var keycloak = builder.AddKeycloak("keycloak") + .WithRealmImport("./realm.json", isReadOnly: false); // Confusing parameter + +// ✅ After (recommended): +var keycloak = builder.AddKeycloak("keycloak") + .WithRealmImport("./realm.json"); // Clean, simple API + +// If you need explicit read-only control: +var keycloak = builder.AddKeycloak("keycloak") + .WithRealmImport("./realm.json", isReadOnly: true); // Still available as overload +``` + +**Migration impact**: Remove the `isReadOnly` parameter from single-parameter `WithRealmImport()` calls - the method now defaults to appropriate behavior. Use the two-parameter overload if explicit control is needed. + +### 🔧 Milvus configuration method updates + +Milvus configuration has been updated with more descriptive method names: + +```csharp +// ❌ Before (deprecated): +var milvus = builder.AddMilvus("milvus") + .WithConfigurationBindMount("./milvus.yaml"); // Old method name + +// ✅ After (recommended): +var milvus = builder.AddMilvus("milvus") + .WithConfigurationFile("./milvus.yaml"); // Method renamed for clarity +``` + +**Migration impact**: Update method calls to use `WithConfigurationFile` instead of `WithConfigurationBindMount` for Milvus configuration. + +### 🔄 Azure Storage client registration updates + +Client registration methods for Azure Storage have been standardized with new naming conventions: + +```csharp +// ❌ Before (obsolete): +builder.AddAzureTableClient("tables"); // Obsolete +builder.AddKeyedAzureTableClient("tables"); // Obsolete +builder.AddAzureBlobClient("blobs"); // Obsolete +builder.AddKeyedAzureBlobClient("blobs"); // Obsolete +builder.AddAzureQueueClient("queues"); // Obsolete +builder.AddKeyedAzureQueueClient("queues"); // Obsolete + +// ✅ After (recommended): +builder.AddAzureTableServiceClient("tables"); // Standardized naming +builder.AddKeyedAzureTableServiceClient("tables"); // Standardized naming +builder.AddAzureBlobServiceClient("blobs"); // Standardized naming +builder.AddKeyedAzureBlobServiceClient("blobs"); // Standardized naming +builder.AddAzureQueueServiceClient("queues"); // Standardized naming +builder.AddKeyedAzureQueueServiceClient("queues"); // Standardized naming +``` + +**Migration impact**: Update all client registration calls to use the new `*ServiceClient` naming convention. + +### 🗄️ Database initialization method changes + +Several database resources have **deprecated `WithInitBindMount` in favor of the more consistent `WithInitFiles`**: + +```csharp +// ❌ Before (deprecated): +var mongo = builder.AddMongoDB("mongo") + .WithInitBindMount("./init", isReadOnly: true); // Complex parameters + +var mysql = builder.AddMySql("mysql") + .WithInitBindMount("./mysql-scripts", isReadOnly: false); + +var oracle = builder.AddOracle("oracle") + .WithInitBindMount("./oracle-init", isReadOnly: true); + +var postgres = builder.AddPostgres("postgres") + .WithInitBindMount("./postgres-init", isReadOnly: true); + +// ✅ After (recommended): +var mongo = builder.AddMongoDB("mongo") + .WithInitFiles("./init"); // Simplified, consistent API + +var mysql = builder.AddMySql("mysql") + .WithInitFiles("./mysql-scripts"); // Same pattern across all providers + +var oracle = builder.AddOracle("oracle") + .WithInitFiles("./oracle-init"); // Unified approach + +var postgres = builder.AddPostgres("postgres") + .WithInitFiles("./postgres-init"); // Consistent across all databases +``` + +**Affected database providers**: MongoDB, MySQL, Oracle, and PostgreSQL + +**Migration impact**: Replace `WithInitBindMount()` calls with `WithInitFiles()` - the new method handles read-only mounting automatically and provides better error handling. + +### Resource lifecycle event updates + +The generic `AfterEndpointsAllocatedEvent` has been deprecated in favor of more specific, type-safe events: + +```csharp +// ❌ Before (deprecated): +builder.Services.AddSingleton(); + +public class MyLifecycleHook : IDistributedApplicationLifecycleHook +{ + public Task AfterEndpointsAllocatedAsync(DistributedApplicationModel appModel, CancellationToken cancellationToken) + { + // Generic event handling - deprecated + return Task.CompletedTask; + } +} + +// ✅ After (recommended): +var api = builder.AddProject("api") + .OnBeforeResourceStarted(async (resource, evt, cancellationToken) => + { + // Resource-specific event handling + }) + .OnResourceEndpointsAllocated(async (resource, evt, cancellationToken) => + { + // Endpoint-specific event handling + }); +``` + +**Migration impact**: Replace usage of `AfterEndpointsAllocatedEvent` with resource-specific lifecycle events like `OnBeforeResourceStarted` or `OnResourceEndpointsAllocated` for better type safety and clarity. + +### 🧊 Azure Container Apps hybrid mode removal + +Azure Container Apps hybrid mode support has been **removed** to simplify the deployment model and improve consistency. Previously, `PublishAsAzureContainerApp` would automatically create Azure infrastructure, but this behavior has been streamlined. + +```csharp +// ❌ Before (hybrid mode - no longer supported): +// In hybrid mode, this would automatically add Azure Container Apps infrastructure +var api = builder.AddProject("api") + .PublishAsAzureContainerApp((infrastructure, containerApp) => + { + app.Template.Scale.MinReplicas = 0; + + }); + +// The hybrid approach mixed azd-generated environments with Aspire-managed infrastructure +// This caused confusion and maintenance complexity + +// ✅ After (required approach): +// Explicitly add Azure Container App Environment first +var containerAppEnvironment = builder.AddAzureContainerAppEnvironment("cae"); + +// Then use PublishAsAzureContainerApp for customization only (same API) +var api = builder.AddProject("api") + .PublishAsAzureContainerApp((infrastructure, containerApp) => + { + app.Template.Scale.MinReplicas = 0; + + }); +``` + +**Key changes:** + +- `PublishAsAzureContainerApp()` **no longer automatically creates infrastructure** - it only adds customization annotations +- **BicepSecretOutput APIs have been removed** from the Azure Container Apps logic for simplified secret handling + +**Migration impact:** + +1. **Add explicit Azure Container App Environment**: Use `builder.AddAzureContainerAppEnvironment("name")` before calling `PublishAsAzureContainerApp()` +2. **Update secret references**: Replace any `BicepSecretOutputReference` usage with proper Azure Key Vault resources using `IAzureKeyVaultSecretReference` +3. **Review infrastructure setup**: Ensure your Bicep templates or infrastructure setup properly creates the Container App Environment that your apps will deploy to + +This change provides **clearer separation** between infrastructure provisioning (handled by explicit resource creation) and application deployment configuration (handled by `PublishAsAzureContainerApp`), making the deployment process more predictable and easier to understand. + +### ⚠️ Known parameter deprecations + +Several auto-injected known parameters have been deprecated and removed from Azure resources in favor of explicit resource modeling: + +**Deprecated parameters:** + +- `AzureBicepResource.KnownParameters.KeyVaultName` +- `AzureBicepResource.KnownParameters.LogAnalyticsWorkspaceId` + +#### KeyVaultName parameter deprecation + +The `AzureBicepResource.KnownParameters.KeyVaultName` parameter is now obsolete. Previously, this parameter was automatically injected into Azure resources to reference Key Vault instances for storing secrets. + +```csharp +// ❌ Before (deprecated): +var customResource = builder.AddAzureInfrastructure("custom", infra => +{ + // Custom Bicep template that expected keyVaultName parameter to be auto-filled + var kvNameParam = new ProvisioningParameter(AzureBicepResource.KnownParameters.KeyVaultName, typeof(string)); + infra.Add(kvNameParam); + + var keyVault = KeyVaultService.FromExisting("keyVault"); + keyVault.Name = kvNameParam; // This was auto-populated by Aspire + infra.Add(keyVault); + + // Store secrets in the auto-injected Key Vault + var secret = new KeyVaultSecret("mySecret", keyVault) + { + Properties = { Value = "sensitive-value" } + }; + infra.Add(secret); +}); + +// ✅ After (recommended): +var keyVault = builder.AddAzureKeyVault("secrets"); +var customResource = builder.AddAzureInfrastructure("custom", infra => +{ + // Use explicit Key Vault resource reference + var existingKeyVault = (KeyVaultService)keyVault.Resource.AddAsExistingResource(infra); + + var secret = new KeyVaultSecret("mySecret", existingKeyVault) + { + Properties = { Value = "sensitive-value" } + }; + infra.Add(secret); +}); +``` + +#### LogAnalyticsWorkspaceId parameter deprecation + +The `AzureBicepResource.KnownParameters.LogAnalyticsWorkspaceId` parameter is now obsolete. Application Insights resources will now automatically create their own Log Analytics workspace or use explicitly provided ones. + +```csharp +// ❌ Before (deprecated): +var appInsights = builder.AddAzureApplicationInsights("ai") + .WithParameter(AzureBicepResource.KnownParameters.LogAnalyticsWorkspaceId, workspaceId); + +// ✅ After (recommended): +// Option 1: Auto-generated workspace (default behavior) +var appInsights = builder.AddAzureApplicationInsights("ai"); + +// Option 2: Explicit workspace resource +var workspace = builder.AddAzureLogAnalyticsWorkspace("workspace"); +var appInsights = builder.AddAzureApplicationInsights("ai") + .WithLogAnalyticsWorkspace(workspace); + +// Option 3: Reference existing workspace from another resource +var env = builder.AddAzureContainerAppEnvironment("env"); +var appInsights = builder.AddAzureApplicationInsights("ai") + .WithLogAnalyticsWorkspace(env.GetOutput("AZURE_LOG_ANALYTICS_WORKSPACE_ID")); +``` + +#### Container App Environment parameter changes + +Previously, container app environment properties (managed identity, workspace ID) were automatically injected into other Azure resources. These are no longer auto-injected as Aspire now supports multiple compute environments. + +```csharp +// ❌ Before (auto-injection): +// These properties were automatically available in other resources: +// - MANAGED_IDENTITY_NAME +// - MANAGED_IDENTITY_PRINCIPAL_ID +// - logAnalyticsWorkspaceId + +// ✅ After (explicit references): +var env = builder.AddAzureContainerAppEnvironment("env"); +var resource = builder.AddAzureInfrastructure("custom", infra => +{ + // Use explicit references when needed + var managedEnv = (ContainerAppManagedEnvironment)env.Resource.AddAsExistingResource(infra); + // Access properties through the bicep resource directly +}); +``` + +**Migration impact**: Replace auto-injected parameters with explicit resource modeling for better resource graph representation and support for multiple Azure compute environments. See [Azure resource customization docs](https://learn.microsoft.com/dotnet/aspire/azure/customize-azure-resources) for more details. + +### 🔧 ParameterResource.Value synchronous behavior change + +The `ParameterResource.Value` property now blocks synchronously when waiting for parameter value resolution, which can potentially cause deadlocks in async contexts. The new `GetValueAsync()` method should be used instead for proper async handling. + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +// Parameters that need resolution +var apiKey = builder.AddParameter("api-key", secret: true); +var connectionString = builder.AddParameter("connection-string", secret: true); + +// ❌ Before (can cause deadlocks in async contexts): +builder.AddProject("api") + .WithEnvironment("API_KEY", apiKey.Resource.Value) // Blocks synchronously - can deadlock + .WithEnvironment("CONNECTION_STRING", connectionString.Resource.Value); + +// ✅ After (recommended for async contexts): +// Use the parameter resources directly with WithEnvironment - they handle async resolution internally +builder.AddProject("api") + .WithEnvironment("API_KEY", apiKey) // Let Aspire handle async resolution + .WithEnvironment("CONNECTION_STRING", connectionString); + +// Or if you need the actual value in custom code with WithEnvironment callback: +builder.AddProject("api") + .WithEnvironment("API_KEY", async (context, cancellationToken) => + { + return await apiKey.Resource.GetValueAsync(cancellationToken); // Proper async handling + }) + .WithEnvironment("CONNECTION_STRING", async (context, cancellationToken) => + { + return await connectionString.Resource.GetValueAsync(cancellationToken); + }); + +// For non-async contexts where blocking is acceptable: +var syncValue = apiKey.Resource.Value; // Still works but may block +``` + +**Migration impact**: When working with `ParameterResource` values in async contexts, use the new `GetValueAsync()` method instead of the `Value` property to avoid potential deadlocks. For `WithEnvironment()` calls, prefer passing the parameter resource directly rather than accessing `.Value` synchronously. + +With every release, we strive to make .NET Aspire better. However, some changes may break existing functionality. For complete details on breaking changes in this release, see: + +- [Breaking changes in .NET Aspire 9.4](../compatibility/9.4/index.md) + +## 🎯 Upgrade today + +Follow the directions outlined in the [Upgrade to .NET Aspire 9.4](#-upgrade-to-net-aspire-94) section to make the switch to 9.4 and take advantage of all these new features today! As always, we're listening for your feedback on [GitHub](https://github.com/dotnet/aspire/issues)—and looking out for what you want to see in 9.5 ☺️. + +For a complete list of issues addressed in this release, see [.NET Aspire GitHub repository—9.4 milestone](https://github.com/dotnet/aspire/issues?q=is%3Aissue%20state%3Aclosed%20milestone%3A9.4%20). diff --git a/docs/whats-new/index.yml b/docs/whats-new/index.yml index b31cea2c23..28b1417787 100644 --- a/docs/whats-new/index.yml +++ b/docs/whats-new/index.yml @@ -5,13 +5,15 @@ summary: Welcome to what's new in .NET Aspire docs. Use this page to quickly fin metadata: title: .NET Aspire what's new? description: Learn about new and updated content in .NET Aspire docs. - ms.date: 07/01/2025 + ms.date: 07/03/2025 ms.topic: landing-page landingContent: - title: .NET Aspire release documentation linkLists: - linkListType: whats-new links: + - text: What's new in .NET Aspire 9.4 + url: dotnet-aspire-9.4.md - text: What's new in .NET Aspire 9.3 url: dotnet-aspire-9.3.md - text: What's new in .NET Aspire 9.2 diff --git a/docs/whats-new/media/dashboard-connection-strings.png b/docs/whats-new/media/dashboard-connection-strings.png new file mode 100644 index 0000000000..3933725312 Binary files /dev/null and b/docs/whats-new/media/dashboard-connection-strings.png differ diff --git a/docs/whats-new/media/dashboard-console-logs-wrapping.gif b/docs/whats-new/media/dashboard-console-logs-wrapping.gif new file mode 100644 index 0000000000..c91fecd322 Binary files /dev/null and b/docs/whats-new/media/dashboard-console-logs-wrapping.gif differ diff --git a/docs/whats-new/media/dashboard-hidden-resources.png b/docs/whats-new/media/dashboard-hidden-resources.png new file mode 100644 index 0000000000..686dbdc26b Binary files /dev/null and b/docs/whats-new/media/dashboard-hidden-resources.png differ diff --git a/docs/whats-new/media/dashboard-interaction-service.gif b/docs/whats-new/media/dashboard-interaction-service.gif new file mode 100644 index 0000000000..3de5e18620 Binary files /dev/null and b/docs/whats-new/media/dashboard-interaction-service.gif differ diff --git a/docs/whats-new/media/dashboard-parameters.png b/docs/whats-new/media/dashboard-parameters.png new file mode 100644 index 0000000000..56bc742e38 Binary files /dev/null and b/docs/whats-new/media/dashboard-parameters.png differ diff --git a/docs/whats-new/media/dashboard-tracing-peers.png b/docs/whats-new/media/dashboard-tracing-peers.png new file mode 100644 index 0000000000..275db6b6dc Binary files /dev/null and b/docs/whats-new/media/dashboard-tracing-peers.png differ diff --git a/docs/whats-new/media/dashboard-update-notification.png b/docs/whats-new/media/dashboard-update-notification.png new file mode 100644 index 0000000000..bf5e7d7f6a Binary files /dev/null and b/docs/whats-new/media/dashboard-update-notification.png differ diff --git a/docs/whats-new/toc.yml b/docs/whats-new/toc.yml index 47030c3dee..3ee29a2508 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 .NET Aspire 9.4 + href: dotnet-aspire-9.4.md - name: What's new in .NET Aspire 9.3 href: dotnet-aspire-9.3.md - name: What's new in .NET Aspire 9.2