diff --git a/.github/prompts/adhere-to-writing-style.prompt.md b/.github/prompts/adhere-to-writing-style.prompt.md
new file mode 100644
index 0000000000..5140ca11f9
--- /dev/null
+++ b/.github/prompts/adhere-to-writing-style.prompt.md
@@ -0,0 +1,33 @@
+The following is a set of guidelines to follow when writing documentation for .NET Aspire. Please ensure that all documentation adheres to these guidelines:
+
+## Voice and tone
+
+Write for readers who may have a limited vocabulary or are not native English speakers. Use simple, clear language and avoid jargon whenever possible. If you must use technical terms, provide a brief explanation of what they mean.
+
+- Use active voice
+- Use the second person (you, your) to address the reader directly
+
+## 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.
+
+## Text styling
+
+Use _italics_ for files, folders, paths (for long items, split onto their own line), new terms.
+
+Use **bold** for UI elements.
+
+Use `code` for inline code, language keywords, NuGet package names, command-line commands, database table and column names, and URLs that you don't want to be clickable.
+
+## Links
+
+Strive to use relative links whenever possible. Use absolute links only when necessary. For example, if you are linking to a page on a different site, use an absolute link. When using absolute links, use the full URL (including the protocol) and remove the locale from the URL. Avoid HTTP links, always prefer HTTPS. Whenever providing additional resources, use the following format:
+
+"For more information, see [link text](../relative/link/to/content.md)."
+
+Never link to `https://learn.microsoft.com` or `https://learn.microsoft.com/en-us`, always remove these and instead use site relative links. For example, if the original link is `https://learn.microsoft.com/en-us/aspnet/core/mvc/overview`, it should be changed to `/aspnet/core/mvc/overview`.
+
+## Things to avoid
+
+- Avoid future tense: In some non-English languages the concept of future tense is not the same as English. Using future tense can make your documents harder to read.
+- Avoid passive voice: Passive voice can make your writing less clear and harder to read. Use active voice whenever possible.
diff --git a/.github/prompts/rephrase-selected-text.prompt.md b/.github/prompts/rephrase-selected-text.prompt.md
new file mode 100644
index 0000000000..bf609e6146
--- /dev/null
+++ b/.github/prompts/rephrase-selected-text.prompt.md
@@ -0,0 +1,3 @@
+Please rephrase the selected text to make it more concise and clear, while maintaining the original meaning. The rephrased text should strive to improve readability. Please ensure that the rephrased text is grammatically correct and flows well. Take obvious opportunities to improve structure, and emphasis of domain specific intent.
+
+Please also always [adhere to our writing style](adhere-to-writing-style.prompt.md).
diff --git a/.gitignore b/.gitignore
index 0b3c331187..acae18877d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -34,6 +34,9 @@ _dependentPackages/
!/xml/System.IO.Log/
!/xml/System.Net.Cache/
+# Playwright dependencies
+**/*/playwright-deps
+
# Visual Studio Code
.vscode/*
!.vscode/extensions.json
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 e47acdcb24..db1507a32b 100644
--- a/docs/app-host/snippets/AspireApp/AspireApp.AppHost/AspireApp.AppHost.csproj
+++ b/docs/app-host/snippets/AspireApp/AspireApp.AppHost/AspireApp.AppHost.csproj
@@ -1,13 +1,12 @@
-
+
Exe
net9.0
enable
enable
- true
7b352f08-305b-4032-9a21-90deb02efc04
@@ -17,8 +16,8 @@
-
-
+
+
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 5d2de61e73..45d5b30662 100644
--- a/docs/app-host/snippets/AspireApp/AspireApp.ResourceAppHost/AspireApp.ResourceAppHost.csproj
+++ b/docs/app-host/snippets/AspireApp/AspireApp.ResourceAppHost/AspireApp.ResourceAppHost.csproj
@@ -1,13 +1,12 @@
-
+
Exe
net9.0
enable
enable
- true
7b352f08-305b-4032-9a21-90deb02efc04
@@ -17,8 +16,8 @@
-
-
+
+
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 73da5c0d31..763ede8394 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 ade0c23b90..041f727dc0 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/authentication/snippets/AspireApp/AspireApp.AppHost/AspireApp.AppHost.csproj b/docs/authentication/snippets/AspireApp/AspireApp.AppHost/AspireApp.AppHost.csproj
index 0e70c4f95d..ecf2b62c33 100644
--- a/docs/authentication/snippets/AspireApp/AspireApp.AppHost/AspireApp.AppHost.csproj
+++ b/docs/authentication/snippets/AspireApp/AspireApp.AppHost/AspireApp.AppHost.csproj
@@ -1,13 +1,12 @@
-
+
Exe
net9.0
enable
enable
- true
10daccf5-26f1-4641-80b2-95f0357607cf
@@ -17,7 +16,7 @@
-
+
-
+
diff --git a/docs/azure/configure-aca-environments.md b/docs/azure/configure-aca-environments.md
new file mode 100644
index 0000000000..0fb9c3d7ee
--- /dev/null
+++ b/docs/azure/configure-aca-environments.md
@@ -0,0 +1,70 @@
+---
+title: Configure Azure Container Apps environments
+description: Learn how to configure Azure Container Apps environments in .NET Aspire.
+ms.topic: how-to
+ms.date: 04/09/2025
+---
+
+# Configure Azure Container Apps environments
+
+It's easy to [publish resources as Azure Container Apps (ACA)](integrations-overview.md#publish-as-azure-container-app) using any of the supported APIs:
+
+-
+-
+-
+
+These APIs automatically create a default ACA environment when you publish your app. While this default setup works well for most scenarios, you might need to customize the ACA environment to meet specific requirements. To achieve this, use the `AddAzureContainerAppEnvironment` method.
+
+The [.NET Aspire app host](../fundamentals/app-host-overview.md) simplifies infrastructure provisioning by generating code to create Azure resources for your applications. This approach enables you to model and configure deployment-related aspects directly in C#, reducing the need to rely on tools like Bicep. These aspects include configuring ACA environments, which provide a serverless platform for running containerized applications.
+
+By using the APIs (explained in [Infrastructure as code](integrations-overview.md#infrastructure-as-code)), you can configure and customize ACA environments along with related resources, such as container registries and file share volumes. Any available deployment setting can be configured. For more information on the available settings, see [Microsoft.App managedEnvironments](/azure/templates/microsoft.app/managedenvironments).
+
+This article guides you through the process of tailoring ACA environments for your .NET Aspire solutions.
+
+## Add an ACA environment
+
+
+
+The `AzureContainerAppEnvironmentResource` type models an ACA environment resource. When you call the `AddAzureContainerAppEnvironment` method, it creates an instance of this type (wrapped in the ).
+
+:::code language="csharp" source="snippets/aca/AspireAca.AppHost/AspireApp.AppHost/Program.cs":::
+
+By default, the calling this API to add an ACA environment generates the following provisioning Bicep module:
+
+:::code language="bicep" source="snippets/aca/AspireAca.AppHost/AspireApp.AppHost/aca-env.module.bicep":::
+
+This module configures:
+
+- A user-assigned managed identity for the ACA environment.
+- An Azure Container Registry (ACR) for the ACA environment.
+- A Log Analytics workspace for the ACA environment.
+- An Azure Container Apps environment.
+- The [.NET Aspire dashboard](../fundamentals/dashboard/overview.md) for the ACA environment.
+- A role assignment for the user principal ID to the ACA environment.
+- Various outputs for the ACA environment.
+
+Using the `acaEnv` variable, you can chain a call to the API to customize the ACA environment to your liking. For more information, see [Configure infrastructure](integrations-overview.md#configure-infrastructure).
+
+## Handle naming conventions
+
+
+
+By default, `AddAzureContainerAppEnvironment` uses a different Azure resource naming scheme than the [Azure Developer CLI (`azd`)](/azure/developer/azure-developer-cli/). If you're upgrading an existing deployment that previously used `azd`, you might see duplicate Azure resources. To avoid this issue, call the `WithAzdResourceNaming` method to revert to the naming convention used by `azd`:
+
+```csharp
+var builder = DistributionApplicationBuilder.Create(args);
+
+var acaEnv = builder.AddAzureContainerAppEnvironment("aca-env")
+ .WithAzdResourceNaming();
+
+// Omitted for brevity...
+
+builder.Build().Run();
+```
+
+Calling this API ensures your existing Azure resources remain consistent and prevents duplication.
+
+## See also
+
+- [.NET Aspire Azure integrations overview](integrations-overview.md)
+- [Azure Container Apps overview](/azure/container-apps/overview)
diff --git a/docs/azure/integrations-overview.md b/docs/azure/integrations-overview.md
index d5499b51e3..85bfae3535 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: 03/07/2025
+ms.date: 04/10/2025
uid: dotnet/aspire/integrations/azure-overview
---
@@ -107,7 +107,7 @@ You can query whether a resource is marked as an existing resource, by calling t
.NET Aspire provides support for referencing existing Azure resources. You mark an existing resource through the `PublishAsExisting`, `RunAsExisting`, and `AsExisting` APIs. These APIs allow developers to reference already-deployed Azure resources, configure them, and generate appropriate deployment manifests using Bicep templates.
-Existing resources referenced with these APIs can be enhanced with role assignments and other customizations that are available with .NET Aspire's [infrastructure as code capabilities](#infrastructure-as-code). These APIs are limited to Azure resources that can be deployed with Bicep templates.
+Existing resources referenced with these APIs can be enhanced with [role assignments](role-assignments.md) and other customizations that are available with .NET Aspire's [infrastructure as code capabilities](#infrastructure-as-code). These APIs are limited to Azure resources that can be deployed with Bicep templates.
### Configure existing Azure resources for run mode
@@ -321,7 +321,7 @@ The preceding code:
- Adds an environment variable named `Hello` to the container app, using the `env` parameter.
- The `AsProvisioningParameter` method is used to treat `env` as either a new in infrastructure, or reuses an existing bicep parameter if one with the same name already exists.
-For more information, see and .
+To configure the Azure Container App environment, see [Configure Azure Container Apps environments](configure-aca-environments.md). For more information, see and .
## Infrastructure as code
@@ -462,11 +462,11 @@ By default, when you call any of the Bicep-related APIs, a call is also made to
Imagine that you have a Bicep template in a file named `storage.bicep` that provisions an Azure Storage Account:
-:::code language="bicep" source="snippets/AppHost.Bicep/storage.bicep":::
+:::code language="bicep" source="snippets/bicep/AppHost.Bicep/storage.bicep":::
To add a reference to the Bicep file on disk, call the method. Consider the following example:
-:::code language="csharp" source="snippets/AppHost.Bicep/Program.ReferenceBicep.cs" id="addfile":::
+:::code language="csharp" source="snippets/bicep/AppHost.Bicep/Program.ReferenceBicep.cs" id="addfile":::
The preceding code adds a reference to a Bicep file located at `../infra/storage.bicep`. The file paths should be relative to the _app host_ project. This reference results in an being added to the application's resources collection with the `"storage"` name, and the API returns an `IResourceBuilder` instance that can be used to further customize the resource.
@@ -474,7 +474,7 @@ The preceding code adds a reference to a Bicep file located at `../infra/storage
While having a Bicep file on disk is the most common scenario, you can also add Bicep templates inline. Inline templates can be useful when you want to define a template in code or when you want to generate the template dynamically. To add an inline Bicep template, call the method with the Bicep template as a `string`. Consider the following example:
-:::code language="csharp" source="snippets/AppHost.Bicep/Program.InlineBicep.cs" id="addinline":::
+:::code language="csharp" source="snippets/bicep/AppHost.Bicep/Program.InlineBicep.cs" id="addinline":::
In this example, the Bicep template is defined as an inline `string` and added to the application's resources collection with the name `"ai"`. This example provisions an Azure AI resource.
@@ -482,7 +482,7 @@ In this example, the Bicep template is defined as an inline `string` and added t
[Bicep supports accepting parameters](/azure/azure-resource-manager/bicep/parameters), which can be used to customize the behavior of the template. To pass parameters to a Bicep template from .NET Aspire, chain calls to the method as shown in the following example:
-:::code language="csharp" source="snippets/AppHost.Bicep/Program.PassParameter.cs" id="addparameter":::
+:::code language="csharp" source="snippets/bicep/AppHost.Bicep/Program.PassParameter.cs" id="addparameter":::
The preceding code:
@@ -511,11 +511,11 @@ To use a well-known parameter, pass the parameter name to the method on an `IResourceBuilder` instance as demonstrated in following C# code snippet:
-:::code language="csharp" source="snippets/AppHost.Bicep/Program.GetOutputReference.cs" id="getoutput":::
+:::code language="csharp" source="snippets/bicep/AppHost.Bicep/Program.GetOutputReference.cs" id="getoutput":::
In this example, the output from the Bicep template is retrieved and stored in an `endpoint` variable. Typically, you would pass this output as an environment variable to another resource that relies on it. For instance, if you had an ASP.NET Core Minimal API project that depended on this endpoint, you could pass the output as an environment variable to the project using the following code snippet:
@@ -558,11 +558,11 @@ It's important to [avoid outputs for secrets](/azure/azure-resource-manager/bice
Consider the following Bicep template as an example the helps to demonstrate this concept of securing secret outputs:
-:::code language="bicep" source="snippets/AppHost.Bicep/cosmosdb.bicep" highlight="2,41":::
+:::code language="bicep" source="snippets/bicep/AppHost.Bicep/cosmosdb.bicep" highlight="2,41":::
The preceding Bicep template expects a `keyVaultName` parameter, among several other parameters. It then defines an Azure Cosmos DB resource and stashes a secret into Azure Key Vault, named `connectionString` which represents the fully qualified connection string to the Cosmos DB instance. To access this secret connection string value, you can use the following code snippet:
-:::code language="csharp" source="snippets/AppHost.Bicep/Program.cs" id="secrets":::
+:::code language="csharp" source="snippets/bicep/AppHost.Bicep/Program.cs" id="secrets":::
In the preceding code snippet, the `cosmos` Bicep template is added as a reference to the `builder`. The `connectionString` secret output is retrieved from the Bicep template and stored in a variable. The secret output is then passed as an environment variable (`ConnectionStrings__cosmos`) to the `api` project. This environment variable is used to connect to the Cosmos DB instance.
diff --git a/docs/azure/role-assignments.md b/docs/azure/role-assignments.md
new file mode 100644
index 0000000000..e336ab448e
--- /dev/null
+++ b/docs/azure/role-assignments.md
@@ -0,0 +1,261 @@
+---
+title: Manage Azure role assignments
+description: Learn how to override Azure role assignments on .NET Aspire resources.
+ms.date: 03/31/2025
+---
+
+# Manage Azure role assignments
+
+All .NET Aspire Azure hosting integrations define Azure resources. [These resources](integrations-overview.md#add-azure-resources) come with default role assignments. You can replace these default role assignments with built-in role [or custom role assignments](integrations-overview.md#infrastructure-as-code). In this article, you learn how to manage Azure role assignments on .NET Aspire resources.
+
+## Default built-in role assignments
+
+When you add an Azure resource to the [app model](xref:dotnet/aspire/app-host#terminology), it's assigned default roles. If a resource depends on another resource, it inherits the same role assignments as the referenced resource unless explicitly overridden.
+
+Consider a scenario where an API project resource references an [Azure Search](../azureai/azureai-search-document-integration.md) resource. The API project is given the default role assignments, as shown in the following example:
+
+```csharp
+var builder = DistributedApplication.CreateBuilder(args);
+
+var search = builder.AddAzureSearch("search");
+
+var api = builder.AddProject("api")
+ .WithReference(search);
+```
+
+In the example code, the `api` project resource depends on the Azure `search` resource, meaning it references the `search` resource. By default, the `search` resource is assigned the following built-in roles:
+
+-
+-
+
+These role assignments allow the API project to read and write data to the Azure Search resource, and manage it. However, this behavior might not always be desirable. For instance, you might want to restrict the API project to only read data from the Azure Search resource.
+
+## Override default role assignments
+
+
+
+To override the default role assignment, use the `WithRoleAssignments` API and assign built-in roles as shown in the following example:
+
+```csharp
+var builder = DistributedApplication.CreateBuilder(args);
+
+var search = builder.AddAzureSearch("search");
+
+var api = builder.AddProject("api")
+ .WithRoleAssignments(search, SearchBuiltInRole.SearchIndexDataReader)
+ .WithReference(search);
+```
+
+When you use the `WithRoleAssignments` method, it replaces the default role assignments with the specified ones. This method requires two parameters: the resource to which the role assignment applies and the built-in role to assign. In the preceding example, the `search` resource is assigned the role.
+
+When you replace the default role assignments with the `SearchIndexDataReader` role, the API project is restricted to only reading data from the Azure Search resource. This ensures the API project can't write data to the Azure Search resource.
+
+For more information, see [Azure built-in roles](/azure/role-based-access-control/built-in-roles).
+
+## Built-in role assignment reference
+
+All built-in roles are defined within the namespaces and are included in the corresponding [📦 Azure.Provisioning.*](https://www.nuget.org/packages?q=Azure.Provisioning) NuGet packages. Each .NET Aspire Azure hosting integration automatically depends on the appropriate provisioning package. For more information, see [Infrastructure as code](integrations-overview.md#infrastructure-as-code).
+
+The following sections list the built-in roles for each Azure provisioning type that can be used as a parameter to the `WithRoleAssignments` API.
+
+### Azure App Configuration
+
+The provisioning resource type is , and the built-in roles are defined in the struct. The built-in roles are:
+
+-
+-
+
+
+
+### Azure App Container
+
+The provisioning resource type is , and the built-in roles are defined in the struct. The built-in roles are:
+
+-
+-
+-
+
+
+
+### Azure Application Insights
+
+The provisioning resource type is , and the built-in roles are defined in the struct. The built-in roles are:
+
+-
+-
+-
+-
+-
+-
+-
+
+For more information, see [Use Application Insights for .NET Aspire telemetry](../deployment/azure/application-insights.md).
+
+### Azure AI (formerly Cognitive Services)
+
+The provisioning resource type is , and the built-in roles are defined in the struct. The built-in roles are:
+
+-
+-
+-
+-
+-
+-
+-
+-
+-
+-
+-
+-
+-
+-
+-
+-
+-
+-
+-
+
+For more information, see [.NET Aspire Azure OpenAI integration (Preview)](../azureai/azureai-openai-integration.md).
+
+### Azure Cosmos DB
+
+The provisioning resource type is , and the built-in roles are defined in the struct. The built-in roles are:
+
+-
+-
+-
+
+For more information, see:
+
+- [.NET Aspire Azure Cosmos DB integration](../database/azure-cosmos-db-integration.md).
+- [.NET Aspire Cosmos DB Entity Framework Core integration](../database/azure-cosmos-db-entity-framework-integration.md).
+
+### Azure Event Hubs
+
+The provisioning resource type is , and the built-in roles are defined in the struct. The built-in roles are:
+
+-
+-
+-
+-
+-
+
+For more information, see [.NET Aspire Azure Event Hubs integration](../messaging/azure-event-hubs-integration.md).
+
+### Azure Key Vault
+
+The provisioning resource type is , and the built-in roles are defined in the struct. The built-in roles are:
+
+-
+-
+-
+-
+-
+-
+-
+-
+-
+-
+-
+-
+-
+-
+
+For more information, see [.NET Aspire Azure Key Vault integration](../security/azure-security-key-vault-integration.md).
+
+### Azure AI Search
+
+The provisioning resource type is , and the built-in roles are defined in the struct. The built-in roles are:
+
+-
+-
+-
+
+For more information, see [.NET Aspire Azure AI Search integration](../azureai/azureai-search-document-integration.md).
+
+### Azure Service Bus
+
+The provisioning resource type is , and the built-in roles are defined in the struct. The built-in roles are:
+
+-
+-
+-
+
+For more information, see [.NET Aspire Azure Service Bus integration](../messaging/azure-service-bus-integration.md).
+
+### Azure SignalR Service
+
+The provisioning resource type is , and the built-in roles are defined in the struct. The built-in roles are:
+
+-
+-
+-
+-
+-
+-
+
+For more information, see [.NET Aspire support for Azure SignalR Service](../real-time/azure-signalr-scenario.md).
+
+### Azure SQL
+
+The provisioning resource type is , and the built-in roles are defined in the struct. The built-in roles are:
+
+-
+-
+-
+-
+-
+
+
+
+### Azure Storage
+
+The provisioning resource type is , and the built-in roles are defined in the struct. The built-in roles are:
+
+-
+-
+-
+-
+-
+-
+-
+-
+-
+-
+-
+-
+-
+-
+-
+-
+-
+-
+-
+-
+
+For more information, see:
+
+- [.NET Aspire Azure Blob Storage integration](../storage/azure-storage-blobs-integration.md)
+- [.NET Aspire Azure Data Tables integration](../storage/azure-storage-tables-integration.md)
+- [.NET Aspire Azure Queue Storage integration](../storage/azure-storage-queues-integration.md)
+
+### Azure Web PubSub
+
+The provisioning resource type is , and the built-in roles are defined in the struct. The built-in roles are:
+
+-
+-
+-
+
+For more information, see [.NET Aspire Azure Web PubSub integration](../messaging/azure-web-pubsub-integration.md).
+
+## See also
+
+- [.NET Aspire Azure integrations overview](integrations-overview.md)
+- [Azure role-based access control (RBAC)](/azure/role-based-access-control/overview)
diff --git a/docs/azure/snippets/aca/AspireAca.AppHost/AspireAca.AppHost.sln b/docs/azure/snippets/aca/AspireAca.AppHost/AspireAca.AppHost.sln
new file mode 100644
index 0000000000..098595375a
--- /dev/null
+++ b/docs/azure/snippets/aca/AspireAca.AppHost/AspireAca.AppHost.sln
@@ -0,0 +1,24 @@
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.14.35906.104
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspireApp.AppHost", "AspireApp.AppHost\AspireApp.AppHost.csproj", "{A27E29D6-FA6E-404D-A41E-1D53EE2DF262}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {A27E29D6-FA6E-404D-A41E-1D53EE2DF262}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A27E29D6-FA6E-404D-A41E-1D53EE2DF262}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A27E29D6-FA6E-404D-A41E-1D53EE2DF262}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A27E29D6-FA6E-404D-A41E-1D53EE2DF262}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {D45E500B-8092-41D3-BEE7-55C647029894}
+ EndGlobalSection
+EndGlobal
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
new file mode 100644
index 0000000000..1923ae506e
--- /dev/null
+++ b/docs/azure/snippets/aca/AspireAca.AppHost/AspireApp.AppHost/AspireApp.AppHost.csproj
@@ -0,0 +1,19 @@
+
+
+
+
+
+ Exe
+ net9.0
+ enable
+ enable
+ true
+ 4a4b5271-16e5-49fd-a7a1-ff2849aefc25
+
+
+
+
+
+
+
+
diff --git a/docs/azure/snippets/aca/AspireAca.AppHost/AspireApp.AppHost/Program.cs b/docs/azure/snippets/aca/AspireAca.AppHost/AspireApp.AppHost/Program.cs
new file mode 100644
index 0000000000..b1e7d39690
--- /dev/null
+++ b/docs/azure/snippets/aca/AspireAca.AppHost/AspireApp.AppHost/Program.cs
@@ -0,0 +1,7 @@
+var builder = DistributedApplication.CreateBuilder(args);
+
+var acaEnv = builder.AddAzureContainerAppEnvironment("aca-env");
+
+// Omitted for brevity...
+
+builder.Build().Run();
diff --git a/docs/azure/snippets/aca/AspireAca.AppHost/AspireApp.AppHost/Properties/launchSettings.json b/docs/azure/snippets/aca/AspireAca.AppHost/AspireApp.AppHost/Properties/launchSettings.json
new file mode 100644
index 0000000000..d1592b5639
--- /dev/null
+++ b/docs/azure/snippets/aca/AspireAca.AppHost/AspireApp.AppHost/Properties/launchSettings.json
@@ -0,0 +1,35 @@
+{
+ "$schema": "https://json.schemastore.org/launchsettings.json",
+ "profiles": {
+ "https": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": true,
+ "applicationUrl": "https://localhost:17189;http://localhost:15026",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development",
+ "DOTNET_ENVIRONMENT": "Development",
+ "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21283",
+ "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22129"
+ }
+ },
+ "http": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": true,
+ "applicationUrl": "http://localhost:15026",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development",
+ "DOTNET_ENVIRONMENT": "Development",
+ "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19097",
+ "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20224"
+ }
+ },
+ "generate-manifest": {
+ "commandName": "Project",
+ "launchBrowser": false,
+ "dotnetRunMessages": true,
+ "commandLineArgs": "--publisher manifest --output-path aspire-manifest.json",
+ }
+ }
+}
diff --git a/docs/azure/snippets/aca/AspireAca.AppHost/AspireApp.AppHost/aca-env.module.bicep b/docs/azure/snippets/aca/AspireAca.AppHost/AspireApp.AppHost/aca-env.module.bicep
new file mode 100644
index 0000000000..5e12838131
--- /dev/null
+++ b/docs/azure/snippets/aca/AspireAca.AppHost/AspireApp.AppHost/aca-env.module.bicep
@@ -0,0 +1,100 @@
+@description('The location for the resource(s) to be deployed.')
+param location string = resourceGroup().location
+
+param userPrincipalId string
+
+param tags object = { }
+
+resource aca_env_mi 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = {
+ name: take('aca_env_mi-${uniqueString(resourceGroup().id)}', 128)
+ location: location
+ tags: tags
+}
+
+resource aca_env_acr 'Microsoft.ContainerRegistry/registries@2023-07-01' = {
+ name: take('acaenvacr${uniqueString(resourceGroup().id)}', 50)
+ location: location
+ sku: {
+ name: 'Basic'
+ }
+ tags: tags
+}
+
+resource aca_env_acr_aca_env_mi_AcrPull 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
+ name: guid(aca_env_acr.id, aca_env_mi.id, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d'))
+ properties: {
+ principalId: aca_env_mi.properties.principalId
+ roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d')
+ principalType: 'ServicePrincipal'
+ }
+ scope: aca_env_acr
+}
+
+resource aca_env_law 'Microsoft.OperationalInsights/workspaces@2023-09-01' = {
+ name: take('acaenvlaw-${uniqueString(resourceGroup().id)}', 63)
+ location: location
+ properties: {
+ sku: {
+ name: 'PerGB2018'
+ }
+ }
+ tags: tags
+}
+
+resource aca_env 'Microsoft.App/managedEnvironments@2024-03-01' = {
+ name: take('acaenv${uniqueString(resourceGroup().id)}', 24)
+ location: location
+ properties: {
+ appLogsConfiguration: {
+ destination: 'log-analytics'
+ logAnalyticsConfiguration: {
+ customerId: aca_env_law.properties.customerId
+ sharedKey: aca_env_law.listKeys().primarySharedKey
+ }
+ }
+ workloadProfiles: [
+ {
+ name: 'consumption'
+ workloadProfileType: 'Consumption'
+ }
+ ]
+ }
+ tags: tags
+}
+
+resource aspireDashboard 'Microsoft.App/managedEnvironments/dotNetComponents@2024-10-02-preview' = {
+ name: 'aspire-dashboard'
+ properties: {
+ componentType: 'AspireDashboard'
+ }
+ parent: aca_env
+}
+
+resource aca_env_Contributor 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
+ name: guid(aca_env.id, userPrincipalId, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c'))
+ properties: {
+ principalId: userPrincipalId
+ roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')
+ }
+ scope: aca_env
+}
+
+output MANAGED_IDENTITY_NAME string = aca_env_mi.name
+
+output MANAGED_IDENTITY_PRINCIPAL_ID string = aca_env_mi.properties.principalId
+
+output AZURE_LOG_ANALYTICS_WORKSPACE_NAME string = aca_env_law.name
+
+output AZURE_LOG_ANALYTICS_WORKSPACE_ID string = aca_env_law.id
+
+output AZURE_CONTAINER_REGISTRY_NAME string = aca_env_acr.name
+
+output AZURE_CONTAINER_REGISTRY_ENDPOINT string = aca_env_acr.properties.loginServer
+
+output AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID string = aca_env_mi.id
+
+output AZURE_CONTAINER_APPS_ENVIRONMENT_NAME string = aca_env.name
+
+output AZURE_CONTAINER_APPS_ENVIRONMENT_ID string = aca_env.id
+
+output AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN string = aca_env.properties.defaultDomain
\ No newline at end of file
diff --git a/docs/azure/snippets/AppHost.Bicep/appsettings.Development.json b/docs/azure/snippets/aca/AspireAca.AppHost/AspireApp.AppHost/appsettings.Development.json
similarity index 100%
rename from docs/azure/snippets/AppHost.Bicep/appsettings.Development.json
rename to docs/azure/snippets/aca/AspireAca.AppHost/AspireApp.AppHost/appsettings.Development.json
diff --git a/docs/azure/snippets/AppHost.Bicep/appsettings.json b/docs/azure/snippets/aca/AspireAca.AppHost/AspireApp.AppHost/appsettings.json
similarity index 100%
rename from docs/azure/snippets/AppHost.Bicep/appsettings.json
rename to docs/azure/snippets/aca/AspireAca.AppHost/AspireApp.AppHost/appsettings.json
diff --git a/docs/azure/snippets/aca/AspireAca.AppHost/AspireApp.AppHost/aspire-manifest.json b/docs/azure/snippets/aca/AspireAca.AppHost/AspireApp.AppHost/aspire-manifest.json
new file mode 100644
index 0000000000..e97b92b5c7
--- /dev/null
+++ b/docs/azure/snippets/aca/AspireAca.AppHost/AspireApp.AppHost/aspire-manifest.json
@@ -0,0 +1,12 @@
+{
+ "$schema": "https://json.schemastore.org/aspire-8.0.json",
+ "resources": {
+ "aca-env": {
+ "type": "azure.bicep.v0",
+ "path": "aca-env.module.bicep",
+ "params": {
+ "userPrincipalId": ""
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/docs/azure/snippets/AppHost.Bicep.sln b/docs/azure/snippets/bicep/AppHost.Bicep.sln
similarity index 100%
rename from docs/azure/snippets/AppHost.Bicep.sln
rename to docs/azure/snippets/bicep/AppHost.Bicep.sln
diff --git a/docs/azure/snippets/AppHost.Bicep/AppHost.Bicep.csproj b/docs/azure/snippets/bicep/AppHost.Bicep/AppHost.Bicep.csproj
similarity index 83%
rename from docs/azure/snippets/AppHost.Bicep/AppHost.Bicep.csproj
rename to docs/azure/snippets/bicep/AppHost.Bicep/AppHost.Bicep.csproj
index f6174aadd1..3f7ea1bcd0 100644
--- a/docs/azure/snippets/AppHost.Bicep/AppHost.Bicep.csproj
+++ b/docs/azure/snippets/bicep/AppHost.Bicep/AppHost.Bicep.csproj
@@ -1,16 +1,15 @@
-
+
Exe
net9.0
enable
enable
- true
5e45e8bd-353b-48d8-ac57-a89e5ee3f8ec
-
-
+
+
diff --git a/docs/azure/snippets/AppHost.Bicep/Program.GetOutputReference.cs b/docs/azure/snippets/bicep/AppHost.Bicep/Program.GetOutputReference.cs
similarity index 100%
rename from docs/azure/snippets/AppHost.Bicep/Program.GetOutputReference.cs
rename to docs/azure/snippets/bicep/AppHost.Bicep/Program.GetOutputReference.cs
diff --git a/docs/azure/snippets/AppHost.Bicep/Program.InlineBicep.cs b/docs/azure/snippets/bicep/AppHost.Bicep/Program.InlineBicep.cs
similarity index 100%
rename from docs/azure/snippets/AppHost.Bicep/Program.InlineBicep.cs
rename to docs/azure/snippets/bicep/AppHost.Bicep/Program.InlineBicep.cs
diff --git a/docs/azure/snippets/AppHost.Bicep/Program.PassParameter.cs b/docs/azure/snippets/bicep/AppHost.Bicep/Program.PassParameter.cs
similarity index 100%
rename from docs/azure/snippets/AppHost.Bicep/Program.PassParameter.cs
rename to docs/azure/snippets/bicep/AppHost.Bicep/Program.PassParameter.cs
diff --git a/docs/azure/snippets/AppHost.Bicep/Program.ReferenceBicep.cs b/docs/azure/snippets/bicep/AppHost.Bicep/Program.ReferenceBicep.cs
similarity index 100%
rename from docs/azure/snippets/AppHost.Bicep/Program.ReferenceBicep.cs
rename to docs/azure/snippets/bicep/AppHost.Bicep/Program.ReferenceBicep.cs
diff --git a/docs/azure/snippets/AppHost.Bicep/Program.cs b/docs/azure/snippets/bicep/AppHost.Bicep/Program.cs
similarity index 100%
rename from docs/azure/snippets/AppHost.Bicep/Program.cs
rename to docs/azure/snippets/bicep/AppHost.Bicep/Program.cs
diff --git a/docs/azure/snippets/AppHost.Bicep/Properties/launchSettings.json b/docs/azure/snippets/bicep/AppHost.Bicep/Properties/launchSettings.json
similarity index 100%
rename from docs/azure/snippets/AppHost.Bicep/Properties/launchSettings.json
rename to docs/azure/snippets/bicep/AppHost.Bicep/Properties/launchSettings.json
diff --git a/docs/azure/snippets/WebHook.Api/appsettings.Development.json b/docs/azure/snippets/bicep/AppHost.Bicep/appsettings.Development.json
similarity index 100%
rename from docs/azure/snippets/WebHook.Api/appsettings.Development.json
rename to docs/azure/snippets/bicep/AppHost.Bicep/appsettings.Development.json
diff --git a/docs/azure/snippets/bicep/AppHost.Bicep/appsettings.json b/docs/azure/snippets/bicep/AppHost.Bicep/appsettings.json
new file mode 100644
index 0000000000..31c092aa45
--- /dev/null
+++ b/docs/azure/snippets/bicep/AppHost.Bicep/appsettings.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning",
+ "Aspire.Hosting.Dcp": "Warning"
+ }
+ }
+}
diff --git a/docs/azure/snippets/AppHost.Bicep/cosmosdb.bicep b/docs/azure/snippets/bicep/AppHost.Bicep/cosmosdb.bicep
similarity index 100%
rename from docs/azure/snippets/AppHost.Bicep/cosmosdb.bicep
rename to docs/azure/snippets/bicep/AppHost.Bicep/cosmosdb.bicep
diff --git a/docs/azure/snippets/AppHost.Bicep/event-grid-webhook.bicep b/docs/azure/snippets/bicep/AppHost.Bicep/event-grid-webhook.bicep
similarity index 100%
rename from docs/azure/snippets/AppHost.Bicep/event-grid-webhook.bicep
rename to docs/azure/snippets/bicep/AppHost.Bicep/event-grid-webhook.bicep
diff --git a/docs/azure/snippets/AppHost.Bicep/storage-out.bicep b/docs/azure/snippets/bicep/AppHost.Bicep/storage-out.bicep
similarity index 100%
rename from docs/azure/snippets/AppHost.Bicep/storage-out.bicep
rename to docs/azure/snippets/bicep/AppHost.Bicep/storage-out.bicep
diff --git a/docs/azure/snippets/AppHost.Bicep/storage.bicep b/docs/azure/snippets/bicep/AppHost.Bicep/storage.bicep
similarity index 100%
rename from docs/azure/snippets/AppHost.Bicep/storage.bicep
rename to docs/azure/snippets/bicep/AppHost.Bicep/storage.bicep
diff --git a/docs/azure/snippets/WebHook.Api/Program.cs b/docs/azure/snippets/bicep/WebHook.Api/Program.cs
similarity index 100%
rename from docs/azure/snippets/WebHook.Api/Program.cs
rename to docs/azure/snippets/bicep/WebHook.Api/Program.cs
diff --git a/docs/azure/snippets/WebHook.Api/Properties/launchSettings.json b/docs/azure/snippets/bicep/WebHook.Api/Properties/launchSettings.json
similarity index 100%
rename from docs/azure/snippets/WebHook.Api/Properties/launchSettings.json
rename to docs/azure/snippets/bicep/WebHook.Api/Properties/launchSettings.json
diff --git a/docs/azure/snippets/WebHook.Api/WebHook.Api.csproj b/docs/azure/snippets/bicep/WebHook.Api/WebHook.Api.csproj
similarity index 100%
rename from docs/azure/snippets/WebHook.Api/WebHook.Api.csproj
rename to docs/azure/snippets/bicep/WebHook.Api/WebHook.Api.csproj
diff --git a/docs/azure/snippets/WebHook.Api/WebHook.Api.http b/docs/azure/snippets/bicep/WebHook.Api/WebHook.Api.http
similarity index 100%
rename from docs/azure/snippets/WebHook.Api/WebHook.Api.http
rename to docs/azure/snippets/bicep/WebHook.Api/WebHook.Api.http
diff --git a/docs/azure/snippets/bicep/WebHook.Api/appsettings.Development.json b/docs/azure/snippets/bicep/WebHook.Api/appsettings.Development.json
new file mode 100644
index 0000000000..0c208ae918
--- /dev/null
+++ b/docs/azure/snippets/bicep/WebHook.Api/appsettings.Development.json
@@ -0,0 +1,8 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ }
+}
diff --git a/docs/azure/snippets/WebHook.Api/appsettings.json b/docs/azure/snippets/bicep/WebHook.Api/appsettings.json
similarity index 100%
rename from docs/azure/snippets/WebHook.Api/appsettings.json
rename to docs/azure/snippets/bicep/WebHook.Api/appsettings.json
diff --git a/docs/azureai/azureai-openai-integration.md b/docs/azureai/azureai-openai-integration.md
index c092a3884b..b25e6fd8b3 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: 03/06/2025
+ms.date: 04/03/2025
---
# .NET Aspire Azure OpenAI integration (Preview)
@@ -83,25 +83,9 @@ If you're new to [Bicep](/azure/azure-resource-manager/bicep/overview), it's a d
:::code language="bicep" source="../snippets/azure/AppHost/openai.module.bicep":::
-The preceding Bicep is a module that provisions an Azure Cognitive Services resource with the following defaults:
-
-- `location`: The location of the resource group.
-- `principalType`: The principal type of the Cognitive Services resource.
-- `principalId`: The principal ID of the Cognitive Services resource.
-- `openai`: The Cognitive Services account resource.
- - `kind`: The kind of the resource, set to `OpenAI`.
- - `properties`: The properties of the resource.
- - `customSubDomainName`: The custom subdomain name for the resource, based on the unique string of the resource group ID.
- - `publicNetworkAccess`: Set to `Enabled`.
- - `disableLocalAuth`: Set to `true`.
- - `sku`: The SKU of the resource, set to `S0`.
-- `openai_CognitiveServicesOpenAIContributor`: The Cognitive Services resource owner, based on the build-in `Azure Cognitive Services OpenAI Contributor` role. For more information, see [Azure Cognitive Services OpenAI Contributor](/azure/role-based-access-control/built-in-roles/ai-machine-learning#cognitive-services-openai-contributor).
-- `preview`: The deployment resource, based on the `preview` name.
- - `properties`: The properties of the deployment resource.
- - `format`: The format of the deployment resource, set to `OpenAI`.
- - `modelName`: The model name of the deployment resource, set to `gpt-4.5-preview`.
- - `modelVersion`: The model version of the deployment resource, set to `2025-02-27`.
-- `connectionString`: The connection string, containing the endpoint of the Cognitive Services resource.
+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":::
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 cc2b3ec146..5d46781821 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: 03/07/2025
+ms.date: 04/03/2025
---
# .NET Aspire Azure AI Search integration
@@ -57,21 +57,9 @@ If you're new to [Bicep](/azure/azure-resource-manager/bicep/overview), it's a d
:::code language="bicep" source="../snippets/azure/AppHost/search.module.bicep":::
-The preceding Bicep is a module that provisions an Azure AI Search service resource with the following defaults:
-
-- `location`: The location parameter of the resource group, defaults to `resourceGroup().location`.
-- `principalType`: The principal type parameter of the Azure AI Search resource.
-- `principalId`: The principal ID parameter of the Azure AI Search resource.
-- `search`: The resource representing the Azure AI Search service.
- - `properties`: The properties of the Azure AI Search service:
- - `hostingMode`: Is set to `default`.
- - `disableLocalAuth`: Is set to `true`.
- - `partitionCount`: Is set to `1`.
- - `replicaCount`: Is set to `1`.
- - `sku`: Defaults to `basic`.
-- `search_SearchIndexDataContributor`: The role assignment for the Azure AI Search index data contributor role. For more information, see [Search Index Data Contributor](/azure/role-based-access-control/built-in-roles/ai-machine-learning#search-index-data-contributor).
-- `search_SearchServiceContributor`: The role assignment for the Azure AI Search service contributor role. For more information, see [Search Service Contributor](/azure/role-based-access-control/built-in-roles/ai-machine-learning#search-service-contributor).
-- `connectionString`: The connection string for the Azure AI Search service, which is used to connect to the service. The connection string is generated using the `Endpoint` property of the Azure AI Search service.
+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":::
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 597be5dc4e..9837bf8818 100644
--- a/docs/caching/includes/azure-redis-app-host.md
+++ b/docs/caching/includes/azure-redis-app-host.md
@@ -50,19 +50,9 @@ If you're new to [Bicep](/azure/azure-resource-manager/bicep/overview), it's a d
:::code language="bicep" source="../../snippets/azure/AppHost/redis.module.bicep":::
-The preceding Bicep is a module that provisions an Azure Cache for Redis with the following defaults:
-
-- `location`: The location of the Azure Cache for Redis resource. The default is the location of the resource group.
-- `principalId`: The principal ID of the Azure Cache for Redis resource.
-- `principalName`: The principal name of the Azure Cache for Redis resource.
-- `sku`: The SKU of the Azure Cache for Redis resource. The default is `Basic` with a capacity of `1`.
-- `enableNonSslPort`: The non-SSL port of the Azure Cache for Redis resource. The default is `false`.
-- `disableAccessKeyAuthentication`: The access key authentication of the Azure Cache for Redis resource. The default is `true`.
-- `minimumTlsVersion`: The minimum TLS version of the Azure Cache for Redis resource. The default is `1.2`.
-- `redisConfiguration`: The Redis configuration of the Azure Cache for Redis resource. The default is `aad-enabled` set to `true`.
-- `tags`: The tags of the Azure Cache for Redis resource. The default is `aspire-resource-name` set to the name of the Aspire resource, in this case `redis`.
-- `redis_contributor`: The contributor of the Azure Cache for Redis resource, with an access policy name of `Data Contributor`.
-- `connectionString`: The connection string of the Azure Cache for Redis resource.
+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":::
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/compatibility/9.2/generated-bicep-updates.md b/docs/compatibility/9.2/generated-bicep-updates.md
new file mode 100644
index 0000000000..e6c3d771b4
--- /dev/null
+++ b/docs/compatibility/9.2/generated-bicep-updates.md
@@ -0,0 +1,62 @@
+---
+title: "Breaking change - Role Assignments separated from Azure resource bicep"
+description: "Learn about the breaking change in .NET Aspire 9.2 where role assignments are moved to separate bicep modules."
+ms.date: 4/2/2025
+ai-usage: ai-assisted
+ms.custom: https://github.com/dotnet/docs-aspire/issues/2911
+---
+
+# Role Assignments separated from Azure resource bicep
+
+In .NET Aspire 9.2, role assignments for Azure resources are no longer included in the same bicep file as the resource. Instead, they're moved to separate bicep modules. This change affects how role assignments are customized during infrastructure configuration.
+
+## Version introduced
+
+.NET Aspire 9.2
+
+## Previous behavior
+
+Previously, when an Azure resource's bicep file was generated, default role assignments were included in the same bicep module as the resource. This allowed customization of role assignments in the `ConfigureInfrastructure` callback. For example:
+
+```csharp
+var storage = builder.AddAzureStorage("storage")
+ .ConfigureInfrastructure(infra =>
+ {
+ var roles = infra.GetProvisionableResources().OfType().ToList();
+
+ foreach (var role in roles)
+ {
+ infra.Remove(role);
+ }
+
+ var storageAccount = infra.GetProvisionableResources().OfType().Single();
+ infra.Add(storageAccount.CreateRoleAssignment(StorageBuiltInRole.StorageBlobDataContributor, ...));
+ });
+```
+
+## New behavior
+
+Role assignments are now moved to their own bicep modules. The `ConfigureInfrastructure` callback no longer contains any `RoleAssignment` instances. Instead, role assignments are configured using the `WithRoleAssignments` API. For example:
+
+```csharp
+var storage = builder.AddAzureStorage("storage");
+
+builder.AddProject("api")
+ .WithRoleAssignments(storage, StorageBuiltInRole.StorageBlobDataContributor);
+```
+
+## Type of breaking change
+
+This is a [behavioral change](../categories.md#behavioral-change).
+
+## Reason for change
+
+This change was necessary to implement the `WithRoleAssignments` APIs, which provide a more structured and flexible way to configure role assignments per application.
+
+## Recommended action
+
+To customize role assignments in .NET Aspire 9.2, use the `WithRoleAssignments` API instead of relying on the `ConfigureInfrastructure` callback. Update your code as shown in the [preceding example](#new-behavior).
+
+## Affected APIs
+
+-
diff --git a/docs/compatibility/9.2/index.md b/docs/compatibility/9.2/index.md
new file mode 100644
index 0000000000..e44e6b7397
--- /dev/null
+++ b/docs/compatibility/9.2/index.md
@@ -0,0 +1,25 @@
+---
+title: Breaking changes in .NET Aspire 9.2
+titleSuffix: ""
+description: Navigate to the breaking changes in .NET Aspire 9.2.
+ms.date: 04/02/2025
+---
+
+# Breaking changes in .NET Aspire 9.2
+
+If you're migrating an app to .NET Aspire 9.2, 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.2.
+
+## Breaking changes
+
+| Title | Type of change | Introduced version |
+|--|--|--|
+| [AzureContainerApps infrastructure creates managed identity per container app](managed-identity-per-app.md) | Behavioral change | 9.2 |
+| [KeyVault default role assignment changing from KeyVaultAdministrator to KeyVaultSecretsUser](keyvault-role-assignment-changes.md) | Behavioral change | 9.2 |
+| [Role Assignments separated from Azure resource bicep](generated-bicep-updates.md) | Behavioral change | 9.2 |
+| [With authentication API creates keyvault resource in the app model](withauthentication-changes.md) | Behavioral change | 9.2 |
+| [WithCommand obsolete and new overload with CommandOptions](withcommand-obsolete.md) | Source incompatible | 9.2 |
diff --git a/docs/compatibility/9.2/keyvault-role-assignment-changes.md b/docs/compatibility/9.2/keyvault-role-assignment-changes.md
new file mode 100644
index 0000000000..cb6c1c1c24
--- /dev/null
+++ b/docs/compatibility/9.2/keyvault-role-assignment-changes.md
@@ -0,0 +1,59 @@
+---
+title: "Breaking change - KeyVault default role assignment changing from KeyVaultAdministrator to KeyVaultSecretsUser"
+description: "Learn about the breaking change in .NET Aspire 9.2 where the default role for Azure KeyVault applications changes to KeyVaultSecretsUser."
+ms.date: 03/27/2025
+ai-usage: ai-assisted
+ms.custom: https://github.com/dotnet/docs-aspire/issues/2899
+---
+
+# KeyVault default role assignment changing from KeyVaultAdministrator to KeyVaultSecretsUser
+
+In .NET Aspire 9.2, the default role assigned to applications referencing Azure KeyVault has changed from to . This change enhances security by limiting default privileges to only reading secrets. Applications requiring higher privileges must explicitly configure them.
+
+## Version introduced
+
+.NET Aspire 9.2
+
+## Previous behavior
+
+Previously, applications referencing Azure KeyVault were automatically granted the `KeyVaultAdministrator` role, which allowed full management of KeyVault settings.
+
+## New behavior
+
+Applications referencing Azure KeyVault are now granted the `KeyVaultSecretsUser` role by default, which restricts access to reading secrets. If higher privileges are required, they can be configured using the `WithRoleAssignments` API.
+
+Example:
+
+```csharp
+using Azure.Provisioning.KeyVault;
+
+var kv = builder.AddAzureKeyVault("kv");
+
+builder.AddProject("api")
+ .WithRoleAssignments(kv, KeyVaultBuiltInRole.KeyVaultContributor);
+```
+
+## Type of breaking change
+
+This is a [behavioral change](../categories.md#behavioral-change).
+
+## Reason for change
+
+The `KeyVaultAdministrator` role provides excessive privileges for most applications, as they typically only need to read secrets. Assigning the `KeyVaultSecretsUser` role by default improves security by adhering to the principle of least privilege.
+
+## Recommended action
+
+If your application requires higher privileges than the `KeyVaultSecretsUser` role, explicitly configure the necessary roles using the `WithRoleAssignments` API. For example:
+
+```csharp
+using Azure.Provisioning.KeyVault;
+
+var kv = builder.AddAzureKeyVault("kv");
+
+builder.AddProject("api")
+ .WithRoleAssignments(kv, KeyVaultBuiltInRole.KeyVaultContributor);
+```
+
+## Affected APIs
+
+-
diff --git a/docs/compatibility/9.2/managed-identity-per-app.md b/docs/compatibility/9.2/managed-identity-per-app.md
new file mode 100644
index 0000000000..9044b5290e
--- /dev/null
+++ b/docs/compatibility/9.2/managed-identity-per-app.md
@@ -0,0 +1,54 @@
+---
+title: "Breaking change - AzureContainerApps infrastructure creates managed identity per container app"
+description: "Learn about the breaking change in .NET Aspire 9.2 where each ContainerApp now has its own managed identity."
+ms.date: 4/2/2025
+ai-usage: ai-assisted
+ms.custom: https://github.com/dotnet/docs-aspire/issues/2914
+---
+
+# Azure Container Apps managed identity changes
+
+Starting with .NET Aspire 9.2, each Azure Container App created using [📦 Aspire.Hosting.Azure.AppContainers](https://www.nuget.org/packages/Aspire.Hosting.Azure.AppContainers) NuGet package now has its own Azure Managed Identity. This change enables more granular role assignments for Azure resources but might require updates to applications that rely on shared managed identities.
+
+## Version introduced
+
+.NET Aspire 9.2
+
+## Previous behavior
+
+All ContainerApps shared a single Azure Managed Identity. This allowed applications to interact with Azure resources using a common identity.
+
+## New behavior
+
+Each ContainerApp now has its own unique Azure Managed Identity. This enables applications to have distinct role assignments for different Azure resources.
+
+## Type of breaking change
+
+This is a [behavioral change](../categories.md#behavioral-change).
+
+## Reason for change
+
+This change was introduced to support scenarios where applications require different role assignments for different Azure resources. By assigning a unique managed identity to each ContainerApp, applications can now operate with more granular access control.
+
+## Recommended action
+
+The recommended action is to update your Azure resources to use the new managed identities. This may include:
+
+### Azure SQL Server
+
+Grant access to all Azure Managed Identities that need to interact with the database. Follow the guidance in [Configure and manage Azure AD authentication with Azure SQL](/azure/azure-sql/database/authentication-aad-configure).
+
+### Azure PostgreSQL
+
+Grant necessary privileges to all Azure Managed Identities that need to interact with the database. Use the PostgreSQL documentation on [granting privileges](https://www.postgresql.org/docs/current/ddl-priv.html) as a reference. For example:
+
+```sql
+GRANT INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO ;
+```
+
+## Affected APIs
+
+- `Aspire.Hosting.AzureContainerAppExtensions.AddAzureContainerAppsInfrastructure`
+- `Aspire.Hosting.AzureContainerAppProjectExtensions.PublishAsAzureContainerApp`
+- `Aspire.Hosting.AzureContainerAppExecutableExtensions.PublishAsAzureContainerApp`
+- `Aspire.Hosting.AzureContainerAppContainerExtensions.PublishAsAzureContainerApp`
diff --git a/docs/compatibility/9.2/withauthentication-changes.md b/docs/compatibility/9.2/withauthentication-changes.md
new file mode 100644
index 0000000000..ffd9e332d4
--- /dev/null
+++ b/docs/compatibility/9.2/withauthentication-changes.md
@@ -0,0 +1,47 @@
+---
+title: "Breaking change - WithAccessKeyAuthentication and WithPasswordAuthentication create a keyvault resource in the app model"
+description: "Learn about the breaking change in .NET Aspire 9.2 where key vault resources are now created or referenced directly in the app model."
+ms.date: 3/25/2025
+ai-usage: ai-assisted
+ms.custom: https://github.com/dotnet/docs-aspire/issues/2889
+---
+
+# With authentication API creates keyvault resource in the app model
+
+Starting in .NET Aspire 9.2, calling any of the following methods:
+
+-
+-
+-
+
+Will now create (or add references to) a key vault resource directly in the app model. This change allows better customization and management of connection strings and secrets.
+
+## Version introduced
+
+.NET Aspire 9.2
+
+## Previous behavior
+
+Previously, calling `WithAccessKeyAuthentication` on CosmosDB or AzureRedis, or `WithPasswordAuthentication` on AzurePostgres, automatically created and managed Bicep resources. These resources were invisible to the app model and could not be managed or customized in C#.
+
+## New behavior
+
+In .NET Aspire 9.2, calling `WithAccessKeyAuthentication` or `WithPasswordAuthentication` adds an empty `keyVaultName` parameter as a known parameter in the Bicep file. The app model now directly creates the key vault resource or allows you to pass a reference to an existing AzureKeyVault resource where secrets will be stored. Key vault secret names for connection strings are now formatted as `connectionstrings--{resourcename}` to avoid conflicts with other connection strings.
+
+## Type of breaking change
+
+This is a [behavioral change](../categories.md#behavioral-change).
+
+## Reason for change
+
+This change moves resource management to the app host, making it easier to customize and manage resources. It allows sharing a key vault across multiple resources or using an existing key vault to manage connection strings and secrets.
+
+## Recommended action
+
+There is currently no workaround for this change. Ensure that your app model is updated to handle the new behavior for key vault resources and connection string management.
+
+## Affected APIs
+
+-
+-
+-
diff --git a/docs/compatibility/9.2/withcommand-obsolete.md b/docs/compatibility/9.2/withcommand-obsolete.md
new file mode 100644
index 0000000000..efbab0e30e
--- /dev/null
+++ b/docs/compatibility/9.2/withcommand-obsolete.md
@@ -0,0 +1,96 @@
+---
+title: "Breaking change - WithCommand obsolete and new overload with CommandOptions"
+description: "Learn about the breaking change in .NET Aspire 9.2 where the WithCommand method overload with optional parameters is marked obsolete."
+ms.date: 3/25/2025
+ai-usage: ai-assisted
+ms.custom: https://github.com/dotnet/docs-aspire/issues/2888
+---
+
+# WithCommand obsolete and new overload with CommandOptions
+
+The method overload that accepted multiple optional parameters is now marked obsolete. A new overload that accepts an instance of `CommandOptions` has been introduced. This change requires updates to existing code to use the new overload.
+
+## Version introduced
+
+.NET Aspire 9.2
+
+## Previous behavior
+
+The `WithCommand` method overload that accepted multiple optional parameters was available and not marked as obsolete.
+
+```csharp
+var builder = DistributedApplication.CreateBuilder(args);
+
+builder.AddProject("apiservice")
+ .WithCommand(
+ name: "command-name",
+ displayName: "Command display name",
+ executeCommand: async (ExecuteCommandContext context) =>
+ {
+ // Command execution logic here
+ await Task.CompletedTask;
+ return CommandResults.Success();
+ },
+ updateState: (UpdateCommandStateContext context) =>
+ {
+ // State update logic here
+ return ResourceCommandState.Enabled;
+ },
+ displayDescription: "Command Description",
+ parameter: new[] { "", "" },
+ confirmationMessage: "Are you sure?",
+ iconName: "Icons",
+ iconVariant: "Red",
+ isHighlighted: false);
+```
+
+## New behavior
+
+The existing method overload is now marked obsolete. A new overload that accepts an instance of `CommandOptions` should be used instead.
+
+```csharp
+var builder = DistributedApplication.CreateBuilder(args);
+
+builder.AddProject("apiservice")
+ .WithCommand(
+ name: "command-name",
+ displayName: "Command display name",
+ executeCommand: async (ExecuteCommandContext context) =>
+ {
+ // Command execution logic here
+ await Task.CompletedTask;
+ return CommandResults.Success();
+ },
+ commandOptions: new CommandOptions
+ {
+ UpdateState = (UpdateCommandStateContext context) =>
+ {
+ // State update logic here
+ return ResourceCommandState.Enabled;
+ },
+ Description = "Command Description",
+ Parameter = new[] { "", "" },
+ ConfirmationMessage = "Are you sure?",
+ IconName = "Icons",
+ IconVariant = "Red",
+ IsHighlighted = false
+ });
+```
+
+The only required parameters are the `name`, `displayName`, and `executeCommand`. The rest of the parameters are now encapsulated within the `CommandOptions` object, which provides a cleaner and more maintainable API.
+
+## Type of breaking change
+
+This is a [source incompatible](../categories.md#source-compatibility) change.
+
+## Reason for change
+
+This change was made following an API review to improve clarity and maintainability by consolidating optional parameters into a single `CommandOptions` object.
+
+## Recommended action
+
+Update your code to use the new `WithCommand` overload that accepts an instance of `CommandOptions`. Replace calls to the obsolete overload with the new overload.
+
+## Affected APIs
+
+-
diff --git a/docs/compatibility/api-removal.md b/docs/compatibility/api-removal.md
index 0a777b74f2..cc99a34d82 100644
--- a/docs/compatibility/api-removal.md
+++ b/docs/compatibility/api-removal.md
@@ -14,3 +14,7 @@ ms.date: 10/24/2024
- By documenting it under [Breaking changes in .NET](breaking-changes.md).
In most cases, an API that shipped in a long-term support (LTS) release is obsoleted in the subsequent LTS release before it's removed. In rare cases, based on business needs, exceptions are made to obsolete an API before the subsequent LTS release. All obsoletions are documented and communicated to customers.
+
+## See also
+
+- [.NET Aspire Support Policy](https://dotnet.microsoft.com/platform/support/policy/aspire)
diff --git a/docs/compatibility/breaking-changes.md b/docs/compatibility/breaking-changes.md
index 6a94a19066..a23b208b74 100644
--- a/docs/compatibility/breaking-changes.md
+++ b/docs/compatibility/breaking-changes.md
@@ -20,4 +20,5 @@ You can also view individual issues that detail the breaking changes introduced
## See also
- [API removal in .NET](api-removal.md)
+- [.NET Aspire Support Policy](https://dotnet.microsoft.com/platform/support/policy/aspire)
- [.NET runtime compatibility](/dotnet/core/versions/#net-runtime-compatibility)
diff --git a/docs/compatibility/categories.md b/docs/compatibility/categories.md
index 6ddbac207d..7d1b1470b2 100644
--- a/docs/compatibility/categories.md
+++ b/docs/compatibility/categories.md
@@ -56,3 +56,7 @@ Forward compatibility refers to the ability of an existing consumer of an API to
Maintaining forward compatibility virtually precludes any changes or additions from version to version, since those changes prevent a consumer that targets a later version from running under an earlier version. Developers expect that a consumer that relies on a newer API may not function correctly against the older API.
Maintaining forward compatibility is not a goal of .NET Aspire.
+
+## See also
+
+- [.NET Aspire Support Policy](https://dotnet.microsoft.com/platform/support/policy/aspire)
diff --git a/docs/compatibility/toc.yml b/docs/compatibility/toc.yml
index 74236c499a..e60fb712ea 100644
--- a/docs/compatibility/toc.yml
+++ b/docs/compatibility/toc.yml
@@ -5,9 +5,28 @@ items:
href: ../get-started/aspire-overview.md
- name: Breaking changes
href: breaking-changes.md
-- name: .NET Aspire 9.1
+- name: .NET Aspire 9.2
expanded: true
items:
+ - name: Overview
+ href: 9.2/index.md
+ - name: Breaking changes in 9.2
+ expanded: true
+ items:
+ - name: Azure Container Apps managed identity changes
+ href: 9.2/managed-identity-per-app.md
+ - name: KeyVault default role assignment changes
+ href: 9.2/keyvault-role-assignment-changes.md
+ - name: Role Assignments is separate bicep
+ href: 9.2/generated-bicep-updates.md
+ - name: With authentication APIs include semantic changes
+ href: 9.2/withauthentication-changes.md
+ - name: WithCommand obsolete, use new overload
+ href: 9.2/withcommand-obsolete.md
+
+- name: .NET Aspire 9.1
+ expanded: false
+ items:
- name: Overview
href: 9.1/index.md
- name: Breaking changes in 9.1
@@ -50,3 +69,5 @@ items:
href: 9.0/azure-resource-name-scheme.md
- name: Ollama integration updates
href: 9.0/ollama-integration-updates.md
+- name: .NET Aspire Support Policy
+ href: https://dotnet.microsoft.com/platform/support/policy/aspire
diff --git a/docs/database/azure-cosmos-db-entity-framework-integration.md b/docs/database/azure-cosmos-db-entity-framework-integration.md
index d6b18de7b7..d05181f287 100644
--- a/docs/database/azure-cosmos-db-entity-framework-integration.md
+++ b/docs/database/azure-cosmos-db-entity-framework-integration.md
@@ -1,7 +1,7 @@
---
title: .NET Aspire Cosmos DB Entity Framework Core integration
description: Learn how to install and configure the .NET Aspire Cosmos DB Entity Framework Core integration to connect to existing Cosmos DB instances or create new instances from .NET with the Azure Cosmos DB emulator.
-ms.date: 02/26/2025
+ms.date: 04/01/2025
uid: dotnet/aspire/azure-cosmos-db-entity-framework-integration
---
@@ -42,12 +42,18 @@ dotnet add package Aspire.Microsoft.EntityFrameworkCore.Cosmos
### Add Cosmos DB context
-In the :::no-loc text="Program.cs"::: file of your client-consuming project, call the extension method to register a for use via the dependency injection container. The method takes a connection name parameter.
+In the :::no-loc text="Program.cs"::: file of your client-consuming project, call the extension method to register a for use via the dependency injection container. The method takes a connection name parameter and a database name parameter.
```csharp
builder.AddCosmosDbContext("cosmosdb", "databaseName");
```
+Alternatively, the database name can be inferred from the connection when there's a single database in the connection string. In this case, you can omit the database name parameter:
+
+```csharp
+builder.AddCosmosDbContext("cosmosdb");
+```
+
> [!TIP]
> The `connectionName` parameter must match the name used when adding the Cosmos DB resource in the app host project. In other words, when you call `AddAzureCosmosDB` and provide a name of `cosmosdb` that same name should be used when calling `AddCosmosDbContext`. For more information, see [Add Azure Cosmos DB resource](#add-azure-cosmos-db-resource).
diff --git a/docs/database/azure-cosmos-db-integration.md b/docs/database/azure-cosmos-db-integration.md
index db311bbdbf..3c42820cec 100644
--- a/docs/database/azure-cosmos-db-integration.md
+++ b/docs/database/azure-cosmos-db-integration.md
@@ -1,7 +1,7 @@
---
title: .NET Aspire Azure Cosmos DB integration
description: Learn how to install and configure the .NET Aspire Azure Cosmos DB integration to connect to existing Cosmos DB instances or create new instances from .NET with the Azure Cosmos DB emulator.
-ms.date: 02/26/2025
+ms.date: 04/10/2025
uid: dotnet/aspire/azure-cosmos-db-integration
---
@@ -11,6 +11,8 @@ uid: dotnet/aspire/azure-cosmos-db-integration
[Azure Cosmos DB](https://azure.microsoft.com/services/cosmos-db/) is a fully managed NoSQL database service for modern app development. The .NET Aspire Azure Cosmos DB integration enables you to connect to existing Cosmos DB instances or create new instances from .NET with the Azure Cosmos DB emulator.
+If you're looking for the Entity Framework Core integration, see [.NET Aspire Cosmos DB Entity Framework Core integration](azure-cosmos-db-entity-framework-integration.md).
+
## Hosting integration
[!INCLUDE [cosmos-app-host](includes/cosmos-app-host.md)]
@@ -51,7 +53,7 @@ builder.AddAzureCosmosClient(connectionName: "cosmos-db");
> [!TIP]
> The `connectionName` parameter must match the name used when adding the Cosmos DB resource in the app host project. In other words, when you call `AddAzureCosmosDB` and provide a name of `cosmos-db` that same name should be used when calling `AddAzureCosmosClient`. For more information, see [Add Azure Cosmos DB resource](#add-azure-cosmos-db-resource).
-You can then retrieve the instance using dependency injection. For example, to retrieve the connection from an example service:
+You can then retrieve the instance using dependency injection. For example, to retrieve the client from an example service:
```csharp
public class ExampleService(CosmosClient client)
@@ -87,6 +89,142 @@ public class ExampleService(
For more information on keyed services, see [.NET dependency injection: Keyed services](/dotnet/core/extensions/dependency-injection#keyed-services).
+### Add Azure Cosmos DB database
+
+
+
+In the app host, the database resource (`AzureCosmosDBDatabaseResource`) can be added as a child resource to the parent . In your client-consuming project, you can deep-link to the database resource by name, registering a instance for use with dependency injection. For example, consider the following code that calls `AddAzureCosmosDatabase` on an instance:
+
+```csharp
+builder.AddAzureCosmosDatabase(connectionName: "customers");
+```
+
+
+
+The `AddAzureCosmosDatabase` API returns a `CosmosDatabaseBuilder` instance that you can use to attach multiple containers under the same database connection. All child containers share the same and database connection and `CosmosClient` instance. This strategy is useful when associating the same with multiple containers.
+
+After calling `AddAzureCosmosDatabase`, you can then retrieve the `Database` instance using dependency injection. For example, to retrieve the database from a delegate in a call consider the following code:
+
+```csharp
+app.MapGet("/api/customers", async (Database database) =>
+{
+ // Query data from database...
+});
+```
+
+### Add keyed Azure Cosmos DB database
+
+
+
+There's also an `AddKeyedAzureCosmosDatabase` API that returns a `CosmosDatabaseBuilder` instance that you can use to attach multiple containers under the same database connection. method that allows you to register multiple databases with different connection names. For example, consider the following code that calls `AddKeyedAzureCosmosDatabase` on an instance:
+
+```csharp
+var builder = WebApplication.CreateBuilder(args);
+
+builder.AddKeyedAzureCosmosDatabase("customers");
+builder.AddKeyedAzureCosmosDatabase("orders");
+
+var app = builder.Build();
+
+app.MapGet("/api/customers", async (
+ [FromKeyedServices("customers")] Database database) =>
+{
+ // Get container from database and query data
+});
+
+app.MapPost("/api/orders", async (
+ [FromKeyedServices("orders")] Database database,
+ [FromBody] OrderRequest order) =>
+{
+ // Get container from database and query data
+});
+
+app.Run();
+```
+
+The preceding example code demonstrates how to register two databases, `details` and `customers`. Each named database can be used to get their corresponding containers to query data.
+
+### Add Azure Cosmos DB container
+
+
+
+When you add a Cosmos DB resource in the app host project, you can also add an Azure Cosmos DB container resource as well. The container resource is considered a child resource to the parent `AzureCosmosDBDatabaseResource`. In your client-consuming project, you can deep-link to the container resource by name, registering a instance for use with dependency injection. For example, consider the following code that calls `AddAzureCosmosContainer` on an instance:
+
+```csharp
+builder.AddAzureCosmosContainer(connectionName: "details");
+```
+
+You can then retrieve the `Container` instance using dependency injection. For example, to retrieve the container from a delegate in a call consider the following code:
+
+```csharp
+app.MapGet("/api/orders/{id:guid}", async (
+ Container container,
+ [FromRoute] Guid id) =>
+{
+ // Query data from container...
+});
+```
+
+### Add keyed Azure Cosmos DB container
+
+
+
+There's also an `AddKeyedAzureCosmosContainer` method that allows you to register multiple containers with different connection names. For example, consider the following code that calls `AddKeyedAzureCosmosContainer` on an instance:
+
+```csharp
+var builder = WebApplication.CreateBuilder(args);
+
+builder.AddKeyedAzureCosmosContainer("customers");
+
+var app = builder.Build();
+
+app.MapGet("/api/customers", async (
+ [FromKeyedServices("customers")] Container container) =>
+{
+ // Query data from container...
+});
+
+app.Run();
+```
+
+If you have multiple containers under the same database connection, you can use the `AddAzureCosmosDatabase` API to attach multiple containers under the same database connection. All child containers share the same and database connection. This strategy is useful when associating the same with multiple containers. Consider the following alternative code, to register multiple containers under the same database connection:
+
+```csharp
+var builder = WebApplication.CreateBuilder(args);
+
+builder.AddAzureCosmosDatabase("customers", configureClientOptions: options =>
+ {
+ options.SerializerOptions = new CosmosSerializationOptions()
+ {
+ PropertyNamingPolicy = CosmosPropertyNamingPolicy.CamelCase
+ };
+ })
+ .AddKeyedContainer(name: "profiles");
+
+builder.AddAzureCosmosDatabase(connectionName: "orders")
+ .AddKeyedContainer(name: "details")
+ .AddKeyedContainer(name: "history");
+
+var app = builder.Build();
+
+app.MapGet("/api/customers", async (
+ [FromKeyedServices("profiles")] Container container) =>
+{
+ // Query data from container
+});
+
+app.MapGet("/api/orders", async (
+ [FromKeyedServices("details")] Container container,
+ [FromKeyedServices("history")] Container container) =>
+{
+ // Query data from container
+});
+
+app.Run();
+```
+
+The preceding example code demonstrates how to register two databases, `customers` and `orders`, each with their own containers. The `customers` database has a single container named `profiles`, while the `orders` database has two containers named `details` and `history`. Each container can be queried individually using its respective key.
+
### Configuration
The .NET Aspire Azure Cosmos DB integration provides multiple options to configure the connection based on the requirements and conventions of your project.
@@ -201,6 +339,7 @@ The .NET Aspire Azure Cosmos DB integration currently doesn't support metrics by
## See also
- [Azure Cosmos DB](https://azure.microsoft.com/services/cosmos-db)
+- [Sample repository showing parent-child relationships](https://github.com/captainsafia/aspire-child-resources)
- [.NET Aspire Cosmos DB Entity Framework Core integration](azure-cosmos-db-entity-framework-integration.md)
- [.NET Aspire integrations overview](../fundamentals/integrations-overview.md)
- [.NET Aspire Azure integrations overview](../azure/integrations-overview.md)
diff --git a/docs/database/azure-postgresql-entity-framework-integration.md b/docs/database/azure-postgresql-entity-framework-integration.md
index d6095fd6ca..eca0f14f38 100644
--- a/docs/database/azure-postgresql-entity-framework-integration.md
+++ b/docs/database/azure-postgresql-entity-framework-integration.md
@@ -1,7 +1,7 @@
---
title: .NET Aspire Azure PostgreSQL Entity Framework Core integration
description: Learn how to integrate Azure PostgreSQL with .NET Aspire applications, using both hosting and Entity Framework Core client integrations.
-ms.date: 01/21/2025
+ms.date: 03/31/2025
uid: dotnet/aspire/azure-postgresql-entity-framework-integration
---
@@ -17,8 +17,6 @@ uid: dotnet/aspire/azure-postgresql-entity-framework-integration
## Client integration
-[!INCLUDE [postgresql-ef-client](includes/postgresql-ef-client.md)]
-
[!INCLUDE [azure-postgresql-ef-client](includes/azure-postgresql-ef-client.md)]
## See also
diff --git a/docs/database/azure-postgresql-integration.md b/docs/database/azure-postgresql-integration.md
index ce3733a0fe..79f4ba482b 100644
--- a/docs/database/azure-postgresql-integration.md
+++ b/docs/database/azure-postgresql-integration.md
@@ -1,7 +1,7 @@
---
title: .NET Aspire Azure PostgreSQL integration
description: Learn how to integrate Azure PostgreSQL with .NET Aspire applications, using both hosting and client integrations.
-ms.date: 01/21/2025
+ms.date: 03/31/2025
uid: dotnet/aspire/azure-postgresql-integration
---
@@ -17,8 +17,6 @@ uid: dotnet/aspire/azure-postgresql-integration
## Client integration
-[!INCLUDE [postgresql-client](includes/postgresql-client.md)]
-
[!INCLUDE [azure-postgresql-client](includes/azure-postgresql-client.md)]
## See also
diff --git a/docs/database/includes/azure-postgresql-client.md b/docs/database/includes/azure-postgresql-client.md
index c0c26f0027..e130f50753 100644
--- a/docs/database/includes/azure-postgresql-client.md
+++ b/docs/database/includes/azure-postgresql-client.md
@@ -2,42 +2,165 @@
ms.topic: include
---
-### Add Azure authenticated Npgsql client
-
-By default, when you call `AddAzurePostgresFlexibleServer` in your PostgreSQL hosting integration, it configures [📦 Azure.Identity](https://www.nuget.org/packages/Azure.Identity) NuGet package to enable authentication:
+To get started with the .NET Aspire Azure PostgreSQL client integration, install the [📦 Aspire.Azure.Npgsql](https://www.nuget.org/packages/Aspire.Azure.Npgsql) NuGet package in the client-consuming project, that is, the project for the application that uses the PostgreSQL client. The PostgreSQL client integration registers an [NpgsqlDataSource](https://www.npgsql.org/doc/api/Npgsql.NpgsqlDataSource.html) instance that you can use to interact with PostgreSQL.
### [.NET CLI](#tab/dotnet-cli)
```dotnetcli
-dotnet add package Azure.Identity
+dotnet add package Aspire.Azure.Npgsql
```
### [PackageReference](#tab/package-reference)
```xml
-
```
---
-The PostgreSQL connection can be consumed using the client integration and :
+
+
+The PostgreSQL connection can be consumed using the client integration by calling the `AddAzureNpgsqlDataSource`:
+
+```csharp
+builder.AddAzureNpgsqlDataSource(connectionName: "postgresdb");
+```
+
+> [!TIP]
+> The `connectionName` parameter must match the name used when adding the PostgreSQL server resource in the app host project.
+
+The preceding code snippet demonstrates how to use the `AddAzureNpgsqlDataSource` method to register an `NpgsqlDataSource` instance that uses Azure authentication ([Microsoft Entra ID](/azure/postgresql/flexible-server/concepts-azure-ad-authentication)). This `"postgresdb"` connection name corresponds to a connection string configuration value.
+
+After adding `NpgsqlDataSource` to the builder, you can get the `NpgsqlDataSource` instance using dependency injection. For example, to retrieve your data source object from an example service define it as a constructor parameter and ensure the `ExampleService` class is registered with the dependency injection container:
+
+```csharp
+public class ExampleService(NpgsqlDataSource dataSource)
+{
+ // Use dataSource...
+}
+```
+
+For more information on dependency injection, see [.NET dependency injection](/dotnet/core/extensions/dependency-injection).
+
+### Add keyed Azure Npgsql client
+
+
+
+There might be situations where you want to register multiple `NpgsqlDataSource` instances with different connection names. To register keyed Npgsql clients, call the `AddKeyedAzureNpgsqlDataSource` method:
+
+```csharp
+builder.AddKeyedAzureNpgsqlDataSource(name: "sales_db");
+builder.AddKeyedAzureNpgsqlDataSource(name: "inventory_db");
+```
+
+Then you can retrieve the `NpgsqlDataSource` instances using dependency injection. For example, to retrieve the connection from an example service:
+
+```csharp
+public class ExampleService(
+ [FromKeyedServices("sales_db")] NpgsqlDataSource salesDataSource,
+ [FromKeyedServices("inventory_db")] NpgsqlDataSource inventoryDataSource)
+{
+ // Use data sources...
+}
+```
+
+For more information on keyed services, see [.NET dependency injection: Keyed services](/dotnet/core/extensions/dependency-injection#keyed-services).
+
+#### Configuration
+
+The .NET Aspire Azure Npgsql integration provides multiple options to configure the database connection based on the requirements and conventions of your project.
+
+##### Use a connection string
+
+When using a connection string defined in the `ConnectionStrings` configuration section, you provide the name of the connection string when calling `AddAzureNpgsqlDataSource`:
```csharp
-builder.AddNpgsqlDataSource(
- "postgresdb",
- configureDataSourceBuilder: (dataSourceBuilder) =>
+builder.AddAzureNpgsqlDataSource("postgresdb");
+```
+
+The connection string is retrieved from the `ConnectionStrings` configuration section, for example, consider the following JSON configuration:
+
+```json
{
- if (string.IsNullOrEmpty(dataSourceBuilder.ConnectionStringBuilder.Password))
- {
- var credentials = new DefaultAzureCredential();
- var tokenRequest = new TokenRequestContext(["https://ossrdbms-aad.database.windows.net/.default"]);
-
- dataSourceBuilder.UsePasswordProvider(
- passwordProvider: _ => credentials.GetToken(tokenRequest).Token,
- passwordProviderAsync: async (_, ct) => (await credentials.GetTokenAsync(tokenRequest, ct)).Token);
+ "ConnectionStrings": {
+ "postgresdb": "Host=myserver;Database=test"
+ }
+}
+```
+
+For more information on how to configure the connection string, see the [Npgsql connection string documentation](https://www.npgsql.org/doc/connection-string-parameters.html).
+
+> [!NOTE]
+> The username and password are automatically inferred from the credential provided in the settings.
+
+##### Use configuration providers
+
+
+
+The .NET Aspire Azure Npgsql integration supports . It loads the `AzureNpgsqlSettings` from configuration using the `Aspire:Azure:Npgsql` key. For example, consider the following _appsettings.json_ file that configures some of the available options:
+
+```json
+{
+ "Aspire": {
+ "Npgsql": {
+ "DisableHealthChecks": true,
+ "DisableTracing": true
}
-});
+ }
+}
```
-The preceding code snippet demonstrates how to use the class from the package to authenticate with [Microsoft Entra ID](/azure/postgresql/flexible-server/concepts-azure-ad-authentication) and retrieve a token to connect to the PostgreSQL database. The [UsePasswordProvider](https://www.npgsql.org/doc/api/Npgsql.NpgsqlDataSourceBuilder.html#Npgsql_NpgsqlDataSourceBuilder_UsePasswordProvider_System_Func_Npgsql_NpgsqlConnectionStringBuilder_System_String__System_Func_Npgsql_NpgsqlConnectionStringBuilder_System_Threading_CancellationToken_System_Threading_Tasks_ValueTask_System_String___) method is used to provide the token to the data source builder.
+##### Use inline delegates
+
+You can configure settings in code, by passing the `Action configureSettings` delegate to set up some or all the options inline, for example to disable health checks from code:
+
+```csharp
+builder.AddAzureNpgsqlDataSource(
+ "postgresdb",
+ settings => settings.DisableHealthChecks = true);
+```
+
+
+
+Use the `AzureNpgsqlSettings.Credential` property to establish a connection. If no credential is configured, the is used. When the connection string contains a username and password, the credential is ignored.
+
+[!INCLUDE [client-integration-health-checks](../../includes/client-integration-health-checks.md)]
+
+- Adds the [`NpgSqlHealthCheck`](https://github.com/Xabaril/AspNetCore.Diagnostics.HealthChecks/blob/master/src/HealthChecks.NpgSql/NpgSqlHealthCheck.cs), which verifies that commands can be successfully executed against the underlying Postgres database.
+- Integrates with the `/health` HTTP endpoint, which specifies all registered health checks must pass for app to be considered ready to accept traffic
+
+[!INCLUDE [integration-observability-and-telemetry](../../includes/integration-observability-and-telemetry.md)]
+
+#### Logging
+
+The .NET Aspire PostgreSQL integration uses the following log categories:
+
+- `Npgsql.Connection`
+- `Npgsql.Command`
+- `Npgsql.Transaction`
+- `Npgsql.Copy`
+- `Npgsql.Replication`
+- `Npgsql.Exception`
+
+#### Tracing
+
+The .NET Aspire PostgreSQL integration will emit the following tracing activities using OpenTelemetry:
+
+- `Npgsql`
+
+#### Metrics
+
+The .NET Aspire PostgreSQL integration will emit the following metrics using OpenTelemetry:
+
+- Npgsql:
+ - `ec_Npgsql_bytes_written_per_second`
+ - `ec_Npgsql_bytes_read_per_second`
+ - `ec_Npgsql_commands_per_second`
+ - `ec_Npgsql_total_commands`
+ - `ec_Npgsql_current_commands`
+ - `ec_Npgsql_failed_commands`
+ - `ec_Npgsql_prepared_commands_ratio`
+ - `ec_Npgsql_connection_pools`
+ - `ec_Npgsql_multiplexing_average_commands_per_batch`
+ - `ec_Npgsql_multiplexing_average_write_time_per_batch`
diff --git a/docs/database/includes/azure-postgresql-ef-client.md b/docs/database/includes/azure-postgresql-ef-client.md
index 6cedb1b170..a97ca201c6 100644
--- a/docs/database/includes/azure-postgresql-ef-client.md
+++ b/docs/database/includes/azure-postgresql-ef-client.md
@@ -2,66 +2,263 @@
ms.topic: include
---
-### Add Azure authenticated Npgsql client
-
-By default, when you call `AddAzurePostgresFlexibleServer` in your PostgreSQL hosting integration, it requires [📦 Azure.Identity](https://www.nuget.org/packages/Azure.Identity) NuGet package to enable authentication:
+To get started with the .NET Aspire PostgreSQL Entity Framework Core client integration, install the [📦 Aspire.Azure.Npgsql.EntityFrameworkCore.PostgreSQL](https://www.nuget.org/packages/) NuGet package in the client-consuming project, that is, the project for the application that uses the PostgreSQL client. The .NET Aspire PostgreSQL Entity Framework Core client integration registers your desired `DbContext` subclass instances that you can use to interact with PostgreSQL.
### [.NET CLI](#tab/dotnet-cli)
```dotnetcli
-dotnet add package Azure.Identity
+dotnet add package Aspire.Azure.Npgsql.EntityFrameworkCore.PostgreSQL
```
### [PackageReference](#tab/package-reference)
```xml
-
```
---
-The PostgreSQL connection can be consumed using the client integration and .
+
+
+The PostgreSQL connection can be consumed using the client integration by calling the `AddAzureNpgsqlDataSource`:
+
+```csharp
+builder.AddAzureNpgsqlDbContext(connectionName: "postgresdb");
+```
+
+> [!TIP]
+> The `connectionName` parameter must match the name used when adding the PostgreSQL server resource in the app host project.
-The following code snippets demonstrate how to use the class from the package to authenticate with [Microsoft Entra ID](/azure/postgresql/flexible-server/concepts-azure-ad-authentication) and retrieve a token to connect to the PostgreSQL database. The [UsePasswordProvider](https://www.npgsql.org/doc/api/Npgsql.NpgsqlDataSourceBuilder.html#Npgsql_NpgsqlDataSourceBuilder_UsePasswordProvider_System_Func_Npgsql_NpgsqlConnectionStringBuilder_System_String__System_Func_Npgsql_NpgsqlConnectionStringBuilder_System_Threading_CancellationToken_System_Threading_Tasks_ValueTask_System_String___) method is used to provide the token to the data source builder.
+The preceding code snippet demonstrates how to use the `AddAzureNpgsqlDbContext` method to register an `YourDbContext` (that's [pooled for performance](/ef/core/performance/advanced-performance-topics)) instance that uses Azure authentication ([Microsoft Entra ID](/azure/postgresql/flexible-server/concepts-azure-ad-authentication)). This `"postgresdb"` connection name corresponds to a connection string configuration value.
-### EF Core version 8
+After adding `YourDbContext` to the builder, you can get the `YourDbContext` instance using dependency injection. For example, to retrieve your data source object from an example service define it as a constructor parameter and ensure the `ExampleService` class is registered with the dependency injection container:
```csharp
-var dsBuilder = new NpgsqlDataSourceBuilder(builder.Configuration.GetConnectionString("postgresdb"));
-if (string.IsNullOrEmpty(dsBuilder.ConnectionStringBuilder.Password))
+public class ExampleService(YourDbContext context)
{
- var credentials = new DefaultAzureCredential();
- var tokenRequest = new TokenRequestContext(["https://ossrdbms-aad.database.windows.net/.default"]);
+ // Use context...
+}
+```
+
+For more information on dependency injection, see [.NET dependency injection](/dotnet/core/extensions/dependency-injection).
+
+### Enrich an Npgsql database context
+
+You may prefer to use the standard Entity Framework method to obtain a database context and add it to the dependency injection container:
+
+```csharp
+builder.Services.AddDbContext(options =>
+ options.UseNpgsql(builder.Configuration.GetConnectionString("postgresdb")
+ ?? throw new InvalidOperationException("Connection string 'postgresdb' not found.")));
+```
+
+> [!NOTE]
+> The connection string name that you pass to the method must match the name used when adding the PostgreSQL server resource in the app host project. For more information, see [Add PostgreSQL server resource](#add-postgresql-server-resource).
+
+You have more flexibility when you create the database context in this way, for example:
+
+- You can reuse existing configuration code for the database context without rewriting it for .NET Aspire.
+- You can use Entity Framework Core interceptors to modify database operations.
+- You can choose not to use Entity Framework Core context pooling, which may perform better in some circumstances.
+
+If you use this method, you can enhance the database context with .NET Aspire-style retries, health checks, logging, and telemetry features by calling the `EnrichAzureNpgsqlDbContext` method:
+
+```csharp
+builder.EnrichAzureNpgsqlDbContext(
+ configureSettings: settings =>
+ {
+ settings.DisableRetry = false;
+ settings.CommandTimeout = 30;
+ });
+```
+
+The `settings` parameter is an instance of the `AzureNpgsqlEntityFrameworkCorePostgreSQLSettings` class.
- dsBuilder.UsePasswordProvider(
- passwordProvider: _ => credentials.GetToken(tokenRequest).Token,
- passwordProviderAsync: async (_, ct) => (await credentials.GetTokenAsync(tokenRequest, ct)).Token);
+
+
+You might also need to configure specific options of Npgsql, or register a in other ways. In this case, you do so by calling the `EnrichAzureNpgsqlDbContext` extension method, as shown in the following example:
+
+```csharp
+var connectionString = builder.Configuration.GetConnectionString("postgresdb");
+
+builder.Services.AddDbContextPool(
+ dbContextOptionsBuilder => dbContextOptionsBuilder.UseNpgsql(connectionString));
+
+builder.EnrichAzureNpgsqlDbContext();
+```
+
+#### Configuration
+
+The .NET Aspire Azure PostgreSQL EntityFrameworkCore Npgsql integration provides multiple options to configure the database connection based on the requirements and conventions of your project.
+
+##### Use a connection string
+
+When using a connection string defined in the `ConnectionStrings` configuration section, you provide the name of the connection string when calling `AddAzureNpgsqlDataSource`:
+
+```csharp
+builder.AddAzureNpgsqlDbContext("postgresdb");
+```
+
+The connection string is retrieved from the `ConnectionStrings` configuration section, for example, consider the following JSON configuration:
+
+```json
+{
+ "ConnectionStrings": {
+ "postgresdb": "Host=myserver;Database=test"
+ }
}
+```
-builder.AddNpgsqlDbContext(
- "postgresdb",
- configureDbContextOptions: (options) => options.UseNpgsql(dsBuilder.Build()));
+For more information on how to configure the connection string, see the [Npgsql connection string documentation](https://www.npgsql.org/doc/connection-string-parameters.html).
+
+> [!NOTE]
+> The username and password are automatically inferred from the credential provided in the settings.
+
+##### Use configuration providers
+
+
+
+The .NET Aspire Azure PostgreSQL EntityFrameworkCore Npgsql integration supports . It loads the `AzureNpgsqlEntityFrameworkCorePostgreSQLSettings` from configuration using the `Aspire:Npgsql:EntityFrameworkCore:PostgreSQL` key. For example, consider the following _appsettings.json_ file that configures some of the available options:
+
+```json
+{
+ "Aspire": {
+ "Npgsql": {
+ "EntityFrameworkCore": {
+ "PostgreSQL": {
+ "DisableHealthChecks": true,
+ "DisableTracing": true
+ }
+ }
+ }
+ }
+}
```
-### EF Core version 9+
+##### Use inline delegates
-With EF Core version 9, you can use the `ConfigureDataSource` method to configure the `NpgsqlDataSourceBuilder` that's used by the integration instead of building one outside of the integration and passing it in.
+You can configure settings in code, by passing the `Action configureSettings` delegate to set up some or all the options inline, for example to disable health checks from code:
```csharp
-builder.AddNpgsqlDbContext(
+builder.AddAzureNpgsqlDbContext(
"postgresdb",
- configureDbContextOptions: (options) => options.UseNpgsql(npgsqlOptions =>
- npgsqlOptions.ConfigureDataSource(dsBuilder =>
- {
- if (string.IsNullOrEmpty(dsBuilder.ConnectionStringBuilder.Password))
- {
- var credentials = new DefaultAzureCredential();
- var tokenRequest = new TokenRequestContext(["https://ossrdbms-aad.database.windows.net/.default"]);
-
- dsBuilder.UsePasswordProvider(
- passwordProvider: _ => credentials.GetToken(tokenRequest).Token,
- passwordProviderAsync: async (_, ct) => (await credentials.GetTokenAsync(tokenRequest, ct)).Token);
- }
- })));
+ settings => settings.DisableHealthChecks = true);
+```
+
+Alternatively, you can use the `EnrichAzureNpgsqlDbContext` extension method to configure the settings:
+
+```csharp
+builder.EnrichAzureNpgsqlDbContext(
+ settings => settings.DisableHealthChecks = true);
+```
+
+
+
+Use the `AzureNpgsqlEntityFrameworkCorePostgreSQLSettings.Credential` property to establish a connection. If no credential is configured, the is used.
+
+When the connection string contains a username and password, the credential is ignored.
+
+##### Troubleshooting
+
+In the rare case that the `Username` property isn't provided and the integration can't detect it using the application's Managed Identity, Npgsql throws an exception with a message similar to the following:
+
+> Npgsql.PostgresException (0x80004005): 28P01: password authentication failed for user ...
+
+In this case you can configure the `Username` property in the connection string and use `EnrichAzureNpgsqlDbContext`, passing the connection string in `UseNpgsql`:
+
+```csharp
+builder.Services.AddDbContextPool(
+ options => options.UseNpgsql(newConnectionString));
+
+builder.EnrichAzureNpgsqlDbContext();
```
+
+#### Configure multiple DbContext classes
+
+If you want to register more than one with different configuration, you can use `$"Aspire:Npgsql:EntityFrameworkCore:PostgreSQL:{typeof(TContext).Name}"` configuration section name. The json configuration would look like:
+
+```json
+{
+ "Aspire": {
+ "Npgsql": {
+ "EntityFrameworkCore": {
+ "PostgreSQL": {
+ "ConnectionString": "",
+ "DisableHealthChecks": true,
+ "DisableTracing": true,
+ "AnotherDbContext": {
+ "ConnectionString": "",
+ "DisableTracing": false
+ }
+ }
+ }
+ }
+ }
+}
+```
+
+Then calling the method with `AnotherDbContext` type parameter would load the settings from `Aspire:Npgsql:EntityFrameworkCore:PostgreSQL:AnotherDbContext` section.
+
+```csharp
+builder.AddAzureNpgsqlDbContext();
+```
+
+[!INCLUDE [client-integration-health-checks](../../includes/client-integration-health-checks.md)]
+
+By default, the .NET Aspire PostgreSQL Entity Framework Core integrations handles the following:
+
+- Adds the [`DbContextHealthCheck`](https://github.com/Xabaril/AspNetCore.Diagnostics.HealthChecks/blob/master/src/HealthChecks.NpgSql/NpgSqlHealthCheck.cs), which calls EF Core's method. The name of the health check is the name of the `TContext` type.
+- Integrates with the `/health` HTTP endpoint, which specifies all registered health checks must pass for app to be considered ready to accept traffic
+
+[!INCLUDE [integration-observability-and-telemetry](../../includes/integration-observability-and-telemetry.md)]
+
+#### Logging
+
+The .NET Aspire PostgreSQL Entity Framework Core integration uses the following Log categories:
+
+- `Microsoft.EntityFrameworkCore.ChangeTracking`
+- `Microsoft.EntityFrameworkCore.Database.Command`
+- `Microsoft.EntityFrameworkCore.Database.Connection`
+- `Microsoft.EntityFrameworkCore.Database.Transaction`
+- `Microsoft.EntityFrameworkCore.Migrations`
+- `Microsoft.EntityFrameworkCore.Infrastructure`
+- `Microsoft.EntityFrameworkCore.Migrations`
+- `Microsoft.EntityFrameworkCore.Model`
+- `Microsoft.EntityFrameworkCore.Model.Validation`
+- `Microsoft.EntityFrameworkCore.Query`
+- `Microsoft.EntityFrameworkCore.Update`
+
+#### Tracing
+
+The .NET Aspire PostgreSQL Entity Framework Core integration will emit the following tracing activities using OpenTelemetry:
+
+- `Npgsql`
+
+#### Metrics
+
+The .NET Aspire PostgreSQL Entity Framework Core integration will emit the following metrics using OpenTelemetry:
+
+- Microsoft.EntityFrameworkCore:
+ - `ec_Microsoft_EntityFrameworkCore_active_db_contexts`
+ - `ec_Microsoft_EntityFrameworkCore_total_queries`
+ - `ec_Microsoft_EntityFrameworkCore_queries_per_second`
+ - `ec_Microsoft_EntityFrameworkCore_total_save_changes`
+ - `ec_Microsoft_EntityFrameworkCore_save_changes_per_second`
+ - `ec_Microsoft_EntityFrameworkCore_compiled_query_cache_hit_rate`
+ - `ec_Microsoft_Entity_total_execution_strategy_operation_failures`
+ - `ec_Microsoft_E_execution_strategy_operation_failures_per_second`
+ - `ec_Microsoft_EntityFramew_total_optimistic_concurrency_failures`
+ - `ec_Microsoft_EntityF_optimistic_concurrency_failures_per_second`
+
+- Npgsql:
+ - `ec_Npgsql_bytes_written_per_second`
+ - `ec_Npgsql_bytes_read_per_second`
+ - `ec_Npgsql_commands_per_second`
+ - `ec_Npgsql_total_commands`
+ - `ec_Npgsql_current_commands`
+ - `ec_Npgsql_failed_commands`
+ - `ec_Npgsql_prepared_commands_ratio`
+ - `ec_Npgsql_connection_pools`
+ - `ec_Npgsql_multiplexing_average_commands_per_batch`
+ - `ec_Npgsql_multiplexing_average_write_time_per_batch`
diff --git a/docs/database/includes/cosmos-app-host.md b/docs/database/includes/cosmos-app-host.md
index 89d7635890..268ffff328 100644
--- a/docs/database/includes/cosmos-app-host.md
+++ b/docs/database/includes/cosmos-app-host.md
@@ -5,6 +5,8 @@ ms.topic: include
The .NET Aspire [Azure Cosmos DB](https://azure.microsoft.com/services/cosmos-db/) hosting integration models the various Cosmos DB resources as the following types:
- : Represents an Azure Cosmos DB resource.
+- : Represents an Azure Cosmos DB container resource.
+- : Represents an Azure Cosmos DB database resource.
- : Represents an Azure Cosmos DB emulator resource.
To access these types and APIs for expressing them, add the [📦 Aspire.Hosting.Azure.CosmosDB](https://www.nuget.org/packages/Aspire.Hosting.Azure.CosmosDB) NuGet package in the [app host](xref:dotnet/aspire/app-host) project.
@@ -49,13 +51,11 @@ If you're new to [Bicep](/azure/azure-resource-manager/bicep/overview), it's a d
:::code language="bicep" source="../../snippets/azure/AppHost/cosmos.module.bicep":::
-The preceding Bicep is a module that provisions an Azure Cosmos DB account with the following defaults:
+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:
-- `kind`: The kind of Cosmos DB account. The default is `GlobalDocumentDB`.
-- `consistencyPolicy`: The consistency policy of the Cosmos DB account. The default is `Session`.
-- `locations`: The locations for the Cosmos DB account. The default is the resource group's location.
+:::code language="bicep" source="../../snippets/azure/AppHost/cosmos-roles.module.bicep":::
-In addition to the Cosmos DB account, it also adds the current application to the `Data Contributor` role for the Cosmos DB account. 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.
+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
@@ -105,13 +105,15 @@ The dependent resource can access the injected connection string by calling the
### Add Azure Cosmos DB database and container resources
+.NET Aspire models parent child relationships between Azure Cosmos DB resources. For example, an Azure Cosmos DB account () can have multiple databases (), and each database can have multiple containers (). When you add a database or container resource, you do so on a parent resource.
+
To add an Azure Cosmos DB database resource, call the method on an `IResourceBuilder` instance:
```csharp
var builder = DistributedApplication.CreateBuilder(args);
var cosmos = builder.AddAzureCosmosDB("cosmos-db");
-cosmos.AddCosmosDatabase("db");
+var db = cosmos.AddCosmosDatabase("db");
// After adding all resources, run the app...
```
@@ -127,14 +129,44 @@ var builder = DistributedApplication.CreateBuilder(args);
var cosmos = builder.AddAzureCosmosDB("cosmos-db");
var db = cosmos.AddCosmosDatabase("db");
-db.AddContainer("entries", "/id");
+var container = db.AddContainer("entries", "/id");
// After adding all resources, run the app...
```
-The container is created in the database that's represented by the `AzureCosmosDBDatabaseResource` that you added earlier.
+The container is created in the database that's represented by the `AzureCosmosDBDatabaseResource` that you added earlier. For more information, see [Databases, containers, and items in Azure Cosmos DB](/azure/cosmos-db/resource-model).
+
+#### Parent child resource relationship example
+
+To better understand the parent-child relationship between Azure Cosmos DB resources, consider the following example, which demonstrates adding an Azure Cosmos DB resource along with a database and container:
+
+```csharp
+var builder = DistributedApplication.CreateBuilder(args);
+
+var cosmos = builder.AddAzureCosmosDB("cosmos");
+
+var customers = cosmos.AddCosmosDatabase("customers");
+var profiles = customers.AddContainer("profiles", "/id");
+
+var orders = cosmos.AddCosmosDatabase("orders");
+var details = orders.AddContainer("details", "/id");
+var history = orders.AddContainer("history", "/id");
+
+builder.AddProject("api")
+ .WithReference(profiles)
+ .WithReference(details)
+ .WithReference(history);
+
+builder.Build().Run();
+```
+
+The preceding code adds an Azure Cosmos DB resource named `cosmos` with two databases: `customers` and `orders`. The `customers` database has a single container named `profiles`, while the `orders` database has two containers: `details` and `history`. The partition key for each container is `/id`.
+
+The following diagram illustrates the parent child relationship between the Azure Cosmos DB resources:
+
+:::image type="content" source="media/cosmos-resource-relationships-thumb.png" alt-text="A diagram depicting Azure Cosmos DB resource parent child relationships." lightbox="media/cosmos-resource-relationships.png":::
-For more information, see [Databases, containers, and items in Azure Cosmos DB](/azure/cosmos-db/resource-model).
+When your app host code expresses parent-child relationships, the client can deep-link to these resources by name. For example, the `customers` database can be referenced by name in the client project, registering a instance that connects to the `customers` database. The same applies to named containers, for example, the `details` container can be referenced by name in the client project, registering a instance that connects to the `details` container.
### Add Azure Cosmos DB emulator resource
diff --git a/docs/database/includes/media/cosmos-resource-relationships-thumb.png b/docs/database/includes/media/cosmos-resource-relationships-thumb.png
new file mode 100644
index 0000000000..3a547fa1fa
Binary files /dev/null and b/docs/database/includes/media/cosmos-resource-relationships-thumb.png differ
diff --git a/docs/database/includes/media/cosmos-resource-relationships.excalidraw b/docs/database/includes/media/cosmos-resource-relationships.excalidraw
new file mode 100644
index 0000000000..0076064c28
--- /dev/null
+++ b/docs/database/includes/media/cosmos-resource-relationships.excalidraw
@@ -0,0 +1,1017 @@
+{
+ "type": "excalidraw",
+ "version": 2,
+ "source": "https://excalidraw.com",
+ "elements": [
+ {
+ "id": "BetIdnuJVAsEvfFGxv3z6",
+ "type": "rectangle",
+ "x": 348.0752632417392,
+ "y": 75.95778493021734,
+ "width": 222.2791527694995,
+ "height": 71.7333455034731,
+ "angle": 0,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "#a5d8ff",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "groupIds": [],
+ "frameId": null,
+ "index": "a2",
+ "roundness": {
+ "type": 3
+ },
+ "seed": 2079267916,
+ "version": 165,
+ "versionNonce": 1652020980,
+ "isDeleted": false,
+ "boundElements": [
+ {
+ "type": "text",
+ "id": "BXbw_yfxj0B93dXb_UgFq"
+ },
+ {
+ "id": "chAR-1rsc7Lz5cSklA66f",
+ "type": "arrow"
+ },
+ {
+ "id": "KAeYgvQLsk7g-2wwXW4H4",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1743532088033,
+ "link": null,
+ "locked": false
+ },
+ {
+ "id": "BXbw_yfxj0B93dXb_UgFq",
+ "type": "text",
+ "x": 369.73493545168424,
+ "y": 84.8244576819539,
+ "width": 178.95980834960938,
+ "height": 54,
+ "angle": 0,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "groupIds": [],
+ "frameId": null,
+ "index": "a3",
+ "roundness": null,
+ "seed": 2008317556,
+ "version": 193,
+ "versionNonce": 866699340,
+ "isDeleted": false,
+ "boundElements": [],
+ "updated": 1743532086308,
+ "link": null,
+ "locked": false,
+ "text": "Customer Database\n\"customers\"",
+ "fontSize": 20,
+ "fontFamily": 6,
+ "textAlign": "center",
+ "verticalAlign": "middle",
+ "containerId": "BetIdnuJVAsEvfFGxv3z6",
+ "originalText": "Customer Database\n\"customers\"",
+ "autoResize": true,
+ "lineHeight": 1.35
+ },
+ {
+ "id": "ZU3FetO0xJojd3Oha8XrC",
+ "type": "rectangle",
+ "x": 900.1514216527121,
+ "y": 75.31335742581805,
+ "width": 214.6578694645427,
+ "height": 71.7333455034731,
+ "angle": 0,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "#ffc9c9",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "groupIds": [],
+ "frameId": null,
+ "index": "a4",
+ "roundness": {
+ "type": 3
+ },
+ "seed": 584954572,
+ "version": 238,
+ "versionNonce": 2111329524,
+ "isDeleted": false,
+ "boundElements": [
+ {
+ "type": "text",
+ "id": "jQ2fd3ucDH63pjUJIvctu"
+ },
+ {
+ "id": "qpjk7KGMDPmEHp3VGv9m7",
+ "type": "arrow"
+ },
+ {
+ "id": "09RFhqle1PAya4XMEkV8R",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1743532141629,
+ "link": null,
+ "locked": false
+ },
+ {
+ "id": "jQ2fd3ucDH63pjUJIvctu",
+ "type": "text",
+ "x": 935.6404287116436,
+ "y": 84.18003017755461,
+ "width": 143.6798553466797,
+ "height": 54,
+ "angle": 0,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "groupIds": [],
+ "frameId": null,
+ "index": "a5",
+ "roundness": null,
+ "seed": 89551220,
+ "version": 269,
+ "versionNonce": 1286166516,
+ "isDeleted": false,
+ "boundElements": [],
+ "updated": 1743531824565,
+ "link": null,
+ "locked": false,
+ "text": "Order Database\n\"orders\"",
+ "fontSize": 20,
+ "fontFamily": 6,
+ "textAlign": "center",
+ "verticalAlign": "middle",
+ "containerId": "ZU3FetO0xJojd3Oha8XrC",
+ "originalText": "Order Database\n\"orders\"",
+ "autoResize": true,
+ "lineHeight": 1.35
+ },
+ {
+ "id": "une2hoBfhcrggQ1PhV1tR",
+ "type": "rectangle",
+ "x": 286.06458413579264,
+ "y": 276.7456465575891,
+ "width": 345.01165597259416,
+ "height": 71.7333455034731,
+ "angle": 0,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "#96f2d7",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "groupIds": [],
+ "frameId": null,
+ "index": "a6",
+ "roundness": {
+ "type": 3
+ },
+ "seed": 1286961484,
+ "version": 170,
+ "versionNonce": 82919924,
+ "isDeleted": false,
+ "boundElements": [
+ {
+ "type": "text",
+ "id": "u7LSObHA8IaZai9QWKedt"
+ },
+ {
+ "id": "KAeYgvQLsk7g-2wwXW4H4",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1743532135042,
+ "link": null,
+ "locked": false
+ },
+ {
+ "id": "u7LSObHA8IaZai9QWKedt",
+ "type": "text",
+ "x": 331.62054487355454,
+ "y": 285.61231930932564,
+ "width": 253.8997344970703,
+ "height": 54,
+ "angle": 0,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "groupIds": [],
+ "frameId": null,
+ "index": "a7",
+ "roundness": null,
+ "seed": 1935123572,
+ "version": 192,
+ "versionNonce": 1193860084,
+ "isDeleted": false,
+ "boundElements": [],
+ "updated": 1743532121611,
+ "link": null,
+ "locked": false,
+ "text": "Customer Profiles Container\n\"profiles\"",
+ "fontSize": 20,
+ "fontFamily": 6,
+ "textAlign": "center",
+ "verticalAlign": "middle",
+ "containerId": "une2hoBfhcrggQ1PhV1tR",
+ "originalText": "Customer Profiles Container\n\"profiles\"",
+ "autoResize": true,
+ "lineHeight": 1.35
+ },
+ {
+ "id": "9D8gv-h8iSsVeNTfNzb5y",
+ "type": "rectangle",
+ "x": 686.051613873488,
+ "y": 276.10121905318977,
+ "width": 291.03959772504965,
+ "height": 71.7333455034731,
+ "angle": 0,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "#fff9db",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "groupIds": [],
+ "frameId": null,
+ "index": "a8",
+ "roundness": {
+ "type": 3
+ },
+ "seed": 617639884,
+ "version": 166,
+ "versionNonce": 1604913908,
+ "isDeleted": false,
+ "boundElements": [
+ {
+ "type": "text",
+ "id": "14vJvqbbZIuVUVPitvLzU"
+ }
+ ],
+ "updated": 1743532078594,
+ "link": null,
+ "locked": false
+ },
+ {
+ "id": "14vJvqbbZIuVUVPitvLzU",
+ "type": "text",
+ "x": 724.6215302286886,
+ "y": 284.9678918049263,
+ "width": 213.89976501464844,
+ "height": 54,
+ "angle": 0,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "groupIds": [],
+ "frameId": null,
+ "index": "a9",
+ "roundness": null,
+ "seed": 585635700,
+ "version": 198,
+ "versionNonce": 1157176308,
+ "isDeleted": false,
+ "boundElements": [],
+ "updated": 1743531877844,
+ "link": null,
+ "locked": false,
+ "text": "Order Details Container\n\"details\"",
+ "fontSize": 20,
+ "fontFamily": 6,
+ "textAlign": "center",
+ "verticalAlign": "middle",
+ "containerId": "9D8gv-h8iSsVeNTfNzb5y",
+ "originalText": "Order Details Container\n\"details\"",
+ "autoResize": true,
+ "lineHeight": 1.35
+ },
+ {
+ "id": "nEB5lm8By6Meay1Rg8Jlt",
+ "type": "rectangle",
+ "x": 1032.0665601979138,
+ "y": 276.7456465575891,
+ "width": 293.62341305430624,
+ "height": 71.7333455034731,
+ "angle": 0,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "#fff5f5",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "groupIds": [],
+ "frameId": null,
+ "index": "aA",
+ "roundness": {
+ "type": 3
+ },
+ "seed": 1531483724,
+ "version": 166,
+ "versionNonce": 1670389324,
+ "isDeleted": false,
+ "boundElements": [
+ {
+ "type": "text",
+ "id": "Lp9UV-3QEa5eptlCOFk5n"
+ },
+ {
+ "id": "09RFhqle1PAya4XMEkV8R",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1743532172121,
+ "link": null,
+ "locked": false
+ },
+ {
+ "id": "Lp9UV-3QEa5eptlCOFk5n",
+ "type": "text",
+ "x": 1070.7583707900083,
+ "y": 285.61231930932564,
+ "width": 216.2397918701172,
+ "height": 54,
+ "angle": 0,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "groupIds": [],
+ "frameId": null,
+ "index": "aB",
+ "roundness": null,
+ "seed": 1765263988,
+ "version": 186,
+ "versionNonce": 2069938380,
+ "isDeleted": false,
+ "boundElements": [],
+ "updated": 1743531882885,
+ "link": null,
+ "locked": false,
+ "text": "Order History Container\n\"history\"",
+ "fontSize": 20,
+ "fontFamily": 6,
+ "textAlign": "center",
+ "verticalAlign": "middle",
+ "containerId": "nEB5lm8By6Meay1Rg8Jlt",
+ "originalText": "Order History Container\n\"history\"",
+ "autoResize": true,
+ "lineHeight": 1.35
+ },
+ {
+ "id": "chAR-1rsc7Lz5cSklA66f",
+ "type": "arrow",
+ "x": 626.9796183029686,
+ "y": -70.47443625087315,
+ "width": 172.96645180474576,
+ "height": 145.1276644030608,
+ "angle": 0,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "groupIds": [],
+ "frameId": null,
+ "index": "aC",
+ "roundness": {
+ "type": 2
+ },
+ "seed": 217574604,
+ "version": 1062,
+ "versionNonce": 898774476,
+ "isDeleted": false,
+ "boundElements": [
+ {
+ "type": "text",
+ "id": "IXfcsIfo2VgO3GSaGUbxG"
+ }
+ ],
+ "updated": 1743532277789,
+ "link": null,
+ "locked": false,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -115.56509263206794,
+ 43.88317681716592
+ ],
+ [
+ -172.96645180474576,
+ 145.1276644030608
+ ]
+ ],
+ "lastCommittedPoint": null,
+ "startBinding": {
+ "elementId": "jbGlDS-ndKUGUhKtmmFVr",
+ "focus": -0.28932264734562113,
+ "gap": 1
+ },
+ "endBinding": {
+ "elementId": "BetIdnuJVAsEvfFGxv3z6",
+ "focus": -0.19985777113350967,
+ "gap": 3.9878770661829037
+ },
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "elbowed": false
+ },
+ {
+ "id": "IXfcsIfo2VgO3GSaGUbxG",
+ "type": "text",
+ "x": 419.0207187387015,
+ "y": -2.0694712574785257,
+ "width": 79.09992980957031,
+ "height": 27,
+ "angle": 0,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "groupIds": [],
+ "frameId": null,
+ "index": "aD",
+ "roundness": null,
+ "seed": 259439988,
+ "version": 13,
+ "versionNonce": 1772925900,
+ "isDeleted": false,
+ "boundElements": [],
+ "updated": 1743531693628,
+ "link": null,
+ "locked": false,
+ "text": "Contains",
+ "fontSize": 20,
+ "fontFamily": 6,
+ "textAlign": "center",
+ "verticalAlign": "middle",
+ "containerId": "chAR-1rsc7Lz5cSklA66f",
+ "originalText": "Contains",
+ "autoResize": true,
+ "lineHeight": 1.35
+ },
+ {
+ "id": "qpjk7KGMDPmEHp3VGv9m7",
+ "type": "arrow",
+ "x": 882.6586094806715,
+ "y": -70.47443625087317,
+ "width": 133.8355461871546,
+ "height": 142.08858775480422,
+ "angle": 0,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "groupIds": [],
+ "frameId": null,
+ "index": "aE",
+ "roundness": {
+ "type": 2
+ },
+ "seed": 729314124,
+ "version": 813,
+ "versionNonce": 1004641356,
+ "isDeleted": false,
+ "boundElements": [
+ {
+ "type": "text",
+ "id": "6aN3bZRSLclWSyXm4EM8N"
+ }
+ ],
+ "updated": 1743532277789,
+ "link": null,
+ "locked": false,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 90.34434994151366,
+ 54.83859189195486
+ ],
+ [
+ 133.8355461871546,
+ 142.08858775480422
+ ]
+ ],
+ "lastCommittedPoint": null,
+ "startBinding": {
+ "elementId": "jbGlDS-ndKUGUhKtmmFVr",
+ "focus": 0.07223642628949603,
+ "gap": 1
+ },
+ "endBinding": {
+ "elementId": "ZU3FetO0xJojd3Oha8XrC",
+ "focus": 0.22950794369676736,
+ "gap": 10.143362895292128
+ },
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "elbowed": false
+ },
+ {
+ "id": "6aN3bZRSLclWSyXm4EM8N",
+ "type": "text",
+ "x": 965.6748614040347,
+ "y": -2.0694712574785257,
+ "width": 79.09992980957031,
+ "height": 27,
+ "angle": 0,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "groupIds": [],
+ "frameId": null,
+ "index": "aF",
+ "roundness": null,
+ "seed": 1389686900,
+ "version": 13,
+ "versionNonce": 1535262452,
+ "isDeleted": false,
+ "boundElements": [],
+ "updated": 1743531693628,
+ "link": null,
+ "locked": false,
+ "text": "Contains",
+ "fontSize": 20,
+ "fontFamily": 6,
+ "textAlign": "center",
+ "verticalAlign": "middle",
+ "containerId": "qpjk7KGMDPmEHp3VGv9m7",
+ "originalText": "Contains",
+ "autoResize": true,
+ "lineHeight": 1.35
+ },
+ {
+ "id": "KAeYgvQLsk7g-2wwXW4H4",
+ "type": "arrow",
+ "x": 458.9506210487843,
+ "y": 148.00344284536283,
+ "width": 0.37993740529771003,
+ "height": 126.4448360534019,
+ "angle": 0,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "groupIds": [],
+ "frameId": null,
+ "index": "aG",
+ "roundness": {
+ "type": 2
+ },
+ "seed": 1696868812,
+ "version": 499,
+ "versionNonce": 1096420684,
+ "isDeleted": false,
+ "boundElements": [
+ {
+ "type": "text",
+ "id": "Kmh88U-Rb2hPDgI8gfkk7"
+ }
+ ],
+ "updated": 1743532121612,
+ "link": null,
+ "locked": false,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -0.3799374052976532,
+ 51.97062764952466
+ ],
+ [
+ -0.37993740529771003,
+ 126.4448360534019
+ ]
+ ],
+ "lastCommittedPoint": null,
+ "startBinding": {
+ "elementId": "BetIdnuJVAsEvfFGxv3z6",
+ "focus": -0.0000024430666897761114,
+ "gap": 1
+ },
+ "endBinding": {
+ "elementId": "une2hoBfhcrggQ1PhV1tR",
+ "focus": 0.000001573983905659971,
+ "gap": 9.498936015644063
+ },
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "elbowed": false
+ },
+ {
+ "id": "Kmh88U-Rb2hPDgI8gfkk7",
+ "type": "text",
+ "x": 419.0207187387015,
+ "y": 198.71838974514202,
+ "width": 79.09992980957031,
+ "height": 27,
+ "angle": 0,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "groupIds": [],
+ "frameId": null,
+ "index": "aH",
+ "roundness": null,
+ "seed": 1686266740,
+ "version": 13,
+ "versionNonce": 1140611660,
+ "isDeleted": false,
+ "boundElements": [],
+ "updated": 1743531693628,
+ "link": null,
+ "locked": false,
+ "text": "Contains",
+ "fontSize": 20,
+ "fontFamily": 6,
+ "textAlign": "center",
+ "verticalAlign": "middle",
+ "containerId": "KAeYgvQLsk7g-2wwXW4H4",
+ "originalText": "Contains",
+ "autoResize": true,
+ "lineHeight": 1.35
+ },
+ {
+ "id": "S2TmZJ4RNJlXyh3YbdmBe",
+ "type": "arrow",
+ "x": 944.4467255196377,
+ "y": 147.3688785251296,
+ "width": 116.57014473472964,
+ "height": 126.06441154118451,
+ "angle": 0,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "groupIds": [],
+ "frameId": null,
+ "index": "aI",
+ "roundness": {
+ "type": 2
+ },
+ "seed": 219484236,
+ "version": 611,
+ "versionNonce": 1851380084,
+ "isDeleted": false,
+ "boundElements": [
+ {
+ "type": "text",
+ "id": "45FDc_REpZ_97kG_BzywH"
+ }
+ ],
+ "updated": 1743531954339,
+ "link": null,
+ "locked": false,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -72.919689567506,
+ 48.09415027229582
+ ],
+ [
+ -116.57014473472964,
+ 126.06441154118451
+ ]
+ ],
+ "lastCommittedPoint": null,
+ "startBinding": null,
+ "endBinding": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "elbowed": false
+ },
+ {
+ "id": "45FDc_REpZ_97kG_BzywH",
+ "type": "text",
+ "x": 792.0219757745863,
+ "y": 198.71838974514202,
+ "width": 79.09992980957031,
+ "height": 27,
+ "angle": 0,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "groupIds": [],
+ "frameId": null,
+ "index": "aJ",
+ "roundness": null,
+ "seed": 1773082228,
+ "version": 13,
+ "versionNonce": 1279672436,
+ "isDeleted": false,
+ "boundElements": [],
+ "updated": 1743531693628,
+ "link": null,
+ "locked": false,
+ "text": "Contains",
+ "fontSize": 20,
+ "fontFamily": 6,
+ "textAlign": "center",
+ "verticalAlign": "middle",
+ "containerId": "S2TmZJ4RNJlXyh3YbdmBe",
+ "originalText": "Contains",
+ "autoResize": true,
+ "lineHeight": 1.35
+ },
+ {
+ "id": "09RFhqle1PAya4XMEkV8R",
+ "type": "arrow",
+ "x": 1068.2713705624049,
+ "y": 147.3688785251296,
+ "width": 113.65573117432155,
+ "height": 126.72916984732885,
+ "angle": 0,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "groupIds": [],
+ "frameId": null,
+ "index": "aK",
+ "roundness": {
+ "type": 2
+ },
+ "seed": 52766412,
+ "version": 619,
+ "versionNonce": 903483892,
+ "isDeleted": false,
+ "boundElements": [
+ {
+ "type": "text",
+ "id": "DQjdqQy3_nbOQs5wXxRPs"
+ }
+ ],
+ "updated": 1743532067390,
+ "link": null,
+ "locked": false,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 80.96328401413325,
+ 53.249619474157186
+ ],
+ [
+ 113.65573117432155,
+ 126.72916984732885
+ ]
+ ],
+ "lastCommittedPoint": null,
+ "startBinding": {
+ "elementId": "ZU3FetO0xJojd3Oha8XrC",
+ "focus": -0.03563365791928458,
+ "gap": 1
+ },
+ "endBinding": {
+ "elementId": "nEB5lm8By6Meay1Rg8Jlt",
+ "focus": 0.12400707216599659,
+ "gap": 9.498936015644063
+ },
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "elbowed": false
+ },
+ {
+ "id": "DQjdqQy3_nbOQs5wXxRPs",
+ "type": "text",
+ "x": 1139.3288465407904,
+ "y": 198.71838974514202,
+ "width": 79.09992980957031,
+ "height": 27,
+ "angle": 0,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "groupIds": [],
+ "frameId": null,
+ "index": "aL",
+ "roundness": null,
+ "seed": 2072709492,
+ "version": 12,
+ "versionNonce": 848507084,
+ "isDeleted": false,
+ "boundElements": [],
+ "updated": 1743531693628,
+ "link": null,
+ "locked": false,
+ "text": "Contains",
+ "fontSize": 20,
+ "fontFamily": 6,
+ "textAlign": "center",
+ "verticalAlign": "middle",
+ "containerId": "09RFhqle1PAya4XMEkV8R",
+ "originalText": "Contains",
+ "autoResize": true,
+ "lineHeight": 1.35
+ },
+ {
+ "id": "jbGlDS-ndKUGUhKtmmFVr",
+ "type": "rectangle",
+ "x": 586.8288113185816,
+ "y": -254.69543626543532,
+ "width": 332.32608370110074,
+ "height": 183.22100001456215,
+ "angle": 0,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "#e7f5ff",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "groupIds": [
+ "XElObUg25iqfyUHyTuTVl"
+ ],
+ "frameId": null,
+ "index": "aM",
+ "roundness": null,
+ "seed": 1229960652,
+ "version": 369,
+ "versionNonce": 591620980,
+ "isDeleted": false,
+ "boundElements": [
+ {
+ "type": "text",
+ "id": "Npgxu_ENRoObT4I3NmU1L"
+ },
+ {
+ "id": "chAR-1rsc7Lz5cSklA66f",
+ "type": "arrow"
+ },
+ {
+ "id": "qpjk7KGMDPmEHp3VGv9m7",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1743532277789,
+ "link": null,
+ "locked": false
+ },
+ {
+ "id": "Npgxu_ENRoObT4I3NmU1L",
+ "type": "text",
+ "x": 626.981973103214,
+ "y": -230.58493625815424,
+ "width": 252.01976013183594,
+ "height": 135,
+ "angle": 0,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "groupIds": [
+ "XElObUg25iqfyUHyTuTVl"
+ ],
+ "frameId": null,
+ "index": "aN",
+ "roundness": null,
+ "seed": 1564593012,
+ "version": 396,
+ "versionNonce": 1082143436,
+ "isDeleted": false,
+ "boundElements": [],
+ "updated": 1743532277789,
+ "link": null,
+ "locked": false,
+ "text": "\n\n\nAzure Cosmos DB Resource\n\"cosmos\"",
+ "fontSize": 20,
+ "fontFamily": 6,
+ "textAlign": "center",
+ "verticalAlign": "middle",
+ "containerId": "jbGlDS-ndKUGUhKtmmFVr",
+ "originalText": "\n\n\nAzure Cosmos DB Resource\n\"cosmos\"",
+ "autoResize": true,
+ "lineHeight": 1.35
+ },
+ {
+ "id": "oUBSF7qo9QB17fZeK8qO8",
+ "type": "image",
+ "x": 706.9228385836044,
+ "y": -239.6199881329922,
+ "width": 83.29079840430518,
+ "height": 83.29079840430518,
+ "angle": 0,
+ "strokeColor": "transparent",
+ "backgroundColor": "#ffffff",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 0,
+ "opacity": 100,
+ "groupIds": [
+ "XElObUg25iqfyUHyTuTVl"
+ ],
+ "frameId": null,
+ "index": "aO",
+ "roundness": null,
+ "seed": 1869988724,
+ "version": 188,
+ "versionNonce": 753545204,
+ "isDeleted": false,
+ "boundElements": null,
+ "updated": 1743532277789,
+ "link": null,
+ "locked": false,
+ "status": "saved",
+ "fileId": "a4c88afb146a3ec5eabbf5767977de165cc12c32",
+ "scale": [
+ 1,
+ 1
+ ],
+ "crop": null
+ }
+ ],
+ "appState": {
+ "gridSize": 20,
+ "gridStep": 5,
+ "gridModeEnabled": false,
+ "viewBackgroundColor": "#ffffff"
+ },
+ "files": {
+ "a4c88afb146a3ec5eabbf5767977de165cc12c32": {
+ "mimeType": "image/png",
+ "id": "a4c88afb146a3ec5eabbf5767977de165cc12c32",
+ "dataURL": "data:image/png;base64,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",
+ "created": 1743532037162,
+ "lastRetrieved": 1743532037162
+ }
+ }
+}
\ No newline at end of file
diff --git a/docs/database/includes/media/cosmos-resource-relationships.png b/docs/database/includes/media/cosmos-resource-relationships.png
new file mode 100644
index 0000000000..f9d29fb4a7
Binary files /dev/null and b/docs/database/includes/media/cosmos-resource-relationships.png differ
diff --git a/docs/database/includes/postgresql-app-host.md b/docs/database/includes/postgresql-app-host.md
index 0e7c08d23f..d00aceb504 100644
--- a/docs/database/includes/postgresql-app-host.md
+++ b/docs/database/includes/postgresql-app-host.md
@@ -44,13 +44,60 @@ var exampleProject = builder.AddProject()
// After adding all resources, run the app...
```
-When .NET Aspire adds a container image to the app host, as shown in the preceding example with the `docker.io/library/postgres` image, it creates a new PostgreSQL server instance on your local machine. A reference to your PostgreSQL server and your PostgreSQL database instance (the `postgresdb` variable) are used to add a dependency to the `ExampleProject`. The PostgreSQL server resource includes default credentials with a `username` of `"postgres"` and randomly generated `password` using the method.
+When .NET Aspire adds a container image to the app host, as shown in the preceding example with the `docker.io/library/postgres` image, it creates a new PostgreSQL server instance on your local machine. A reference to your PostgreSQL server and database instance (the `postgresdb` variable) are used to add a dependency to the `ExampleProject`.
+
+When adding a database resource to the app model, the database is created if it doesn't already exist. The creation of the database relies on the [app host eventing APIs](../../app-host/eventing.md), specifically . In other words, when the `postgres` resource is _ready_, the event is raised and the database resource is created.
+
+The PostgreSQL server resource includes default credentials with a `username` of `"postgres"` and randomly generated `password` using the method.
The method configures a connection in the `ExampleProject` named `"messaging"`. For more information, see [Container resource lifecycle](../../fundamentals/app-host-overview.md#container-resource-lifecycle).
> [!TIP]
> If you'd rather connect to an existing PostgreSQL server, call instead. For more information, see [Reference existing resources](../../fundamentals/app-host-overview.md#reference-existing-resources).
+### Add PostgreSQL resource with database scripts
+
+By default, when you add a , it relies on the following script to create the database:
+
+```sql
+CREATE DATABASE ""
+```
+
+
+
+To alter the default script, chain a call to the `WithCreationScript` method on the database resource builder:
+
+```csharp
+var builder = DistributedApplication.CreateBuilder(args);
+
+var postgres = builder.AddPostgres("postgres");
+
+var databaseName = "app_db";
+var creationScript = $$"""
+ -- Create the database
+ CREATE DATABASE {{databaseName}};
+
+ """;
+
+var db = postgres.AddDatabase(databaseName)
+ .WithCreationScript(creationScript);
+
+builder.AddProject()
+ .WithReference(db)
+ .WaitFor(db);
+
+// After adding all resources, run the app...
+```
+
+The preceding example creates a database named `app_db`. The script is run when the database resource is created. The script is passed as a string to the `WithCreationScript` method, which is then run in the context of the PostgreSQL resource.
+
+> [!NOTE]
+> The connect to a database command (`\c`) isn't supported when using the creation script.
+
### Add PostgreSQL pgAdmin resource
When adding PostgreSQL resources to the `builder` with the `AddPostgres` method, you can chain calls to to add the [**dpage/pgadmin4**](https://www.pgadmin.org/) container. This container is a cross-platform client for PostgreSQL databases, that serves a web-based admin dashboard. Consider the following example:
diff --git a/docs/database/includes/postgresql-flexible-server.md b/docs/database/includes/postgresql-flexible-server.md
index 09e09e08ce..3f570bde06 100644
--- a/docs/database/includes/postgresql-flexible-server.md
+++ b/docs/database/includes/postgresql-flexible-server.md
@@ -62,16 +62,9 @@ If you're new to [Bicep](/azure/azure-resource-manager/bicep/overview), it's a d
:::code language="bicep" source="../../snippets/azure/AppHost/postgres-flexible.module.bicep":::
-The preceding Bicep is a module that provisions an Azure PostgreSQL flexible server with the following defaults:
-
-- `authConfig`: The authentication configuration of the PostgreSQL server. The default is `ActiveDirectoryAuth` enabled and `PasswordAuth` disabled.
-- `availabilityZone`: The availability zone of the PostgreSQL server. The default is `1`.
-- `backup`: The backup configuration of the PostgreSQL server. The default is `BackupRetentionDays` set to `7` and `GeoRedundantBackup` set to `Disabled`.
-- `highAvailability`: The high availability configuration of the PostgreSQL server. The default is `Disabled`.
-- `storage`: The storage configuration of the PostgreSQL server. The default is `StorageSizeGB` set to `32`.
-- `version`: The version of the PostgreSQL server. The default is `16`.
-- `sku`: The SKU of the PostgreSQL server. The default is `Standard_B1ms`.
-- `tags`: The tags of the PostgreSQL server. The default is `aspire-resource-name` set to the name of the Aspire resource, in this case `postgres-flexible`.
+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":::
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/includes/sql-app-host.md b/docs/database/includes/sql-app-host.md
index 29942842bf..2dbe8dc9dc 100644
--- a/docs/database/includes/sql-app-host.md
+++ b/docs/database/includes/sql-app-host.md
@@ -43,7 +43,11 @@ builder.AddProject()
> [!NOTE]
> The SQL Server container is slow to start, so it's best to use a _persistent_ lifetime to avoid unnecessary restarts. For more information, see [Container resource lifetime](../../fundamentals/app-host-overview.md#container-resource-lifetime).
-When .NET Aspire adds a container image to the app host, as shown in the preceding example with the `mcr.microsoft.com/mssql/server` image, it creates a new SQL Server instance on your local machine. A reference to your SQL Server resource builder (the `sql` variable) is used to add a database. The database is named `database` and then added to the `ExampleProject`. The SQL Server resource includes default credentials with a `username` of `sa` and a random `password` generated using the method.
+When .NET Aspire adds a container image to the app host, as shown in the preceding example with the `mcr.microsoft.com/mssql/server` image, it creates a new SQL Server instance on your local machine. A reference to your SQL Server resource builder (the `sql` variable) is used to add a database. The database is named `database` and then added to the `ExampleProject`.
+
+When adding a database resource to the app model, the database is created if it doesn't already exist. The creation of the database relies on the [app host eventing APIs](../../app-host/eventing.md), specifically . In other words, when the `sql` resource is _ready_, the event is raised and the database resource is created.
+
+The SQL Server resource includes default credentials with a `username` of `sa` and a random `password` generated using the method.
When the app host runs, the password is stored in the app host's secret store. It's added to the `Parameters` section, for example:
@@ -60,6 +64,73 @@ The method conf
> [!TIP]
> If you'd rather connect to an existing SQL Server, call instead. For more information, see [Reference existing resources](../../fundamentals/app-host-overview.md#reference-existing-resources).
+### Add SQL Server resource with database scripts
+
+By default, when you add a , it relies on the following SQL script to create the database:
+
+```sql
+IF
+(
+ NOT EXISTS
+ (
+ SELECT 1
+ FROM sys.databases
+ WHERE name = @DatabaseName
+ )
+)
+CREATE DATABASE [];
+```
+
+
+
+To alter the default script, chain a call to the `WithCreationScript` method on the database resource builder:
+
+```csharp
+var builder = DistributedApplication.CreateBuilder(args);
+
+var sql = builder.AddSqlServer("sql")
+ .WithLifetime(ContainerLifetime.Persistent);
+
+var databaseName = "app_db";
+var creationScript = $$"""
+ IF DB_ID('{{databaseName}}') IS NULL
+ CREATE DATABASE [{{databaseName}}];
+ GO
+
+ -- Use the database
+ USE [{{databaseName}}];
+ GO
+
+ -- Create the todos table
+ CREATE TABLE todos (
+ id INT PRIMARY KEY AUTO_INCREMENT, -- Unique ID for each todo
+ title VARCHAR(255) NOT NULL, -- Short description of the task
+ description TEXT, -- Optional detailed description
+ is_completed BOOLEAN DEFAULT FALSE, -- Completion status
+ due_date DATE, -- Optional due date
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
+ );
+ GO
+
+ """;
+
+var db = sql.AddDatabase(databaseName)
+ .WithCreationScript(creationScript);
+
+builder.AddProject()
+ .WithReference(db)
+ .WaitFor(db);
+
+// After adding all resources, run the app...
+```
+
+The preceding example creates a database named `app_db` with a single `todos` table. The SQL script is executed when the database resource is created. The script is passed as a string to the `WithCreationScript` method, which is then executed in the context of the SQL Server resource.
+
### Add SQL Server resource with data volume
To add a data volume to the SQL Server resource, call the method on the SQL Server resource:
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 2f5c1eeafd..daa106c456 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 @@
-