diff --git a/Aspire.sln b/Aspire.sln
index 5647600b1a6..f48ad76f5ab 100644
--- a/Aspire.sln
+++ b/Aspire.sln
@@ -133,6 +133,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.Hosting.Azure.Provis
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "eShopLite", "eShopLite", "{A68BA1A5-1604-433D-9778-DC0199831C2A}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.Microsoft.Azure.Cosmos", "src\Components\Aspire.Microsoft.Azure.Cosmos\Aspire.Microsoft.Azure.Cosmos.csproj", "{23298562-C1D4-41CD-83FE-426C94FEE35F}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.Microsoft.EntityFrameworkCore.Cosmos", "src\Components\Aspire.Microsoft.EntityFrameworkCore.Cosmos\Aspire.Microsoft.EntityFrameworkCore.Cosmos.csproj", "{00C9BA50-2AFB-4D9C-A2D6-8154BCCD0A63}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.Microsoft.Azure.Cosmos.Tests", "tests\Aspire.Microsoft.Azure.Cosmos.Tests\Aspire.Microsoft.Azure.Cosmos.Tests.csproj", "{A5836BC1-6A45-4BB6-9D22-A7F750890AB8}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.Microsoft.EntityFrameworkCore.Cosmos.Tests", "tests\Aspire.Microsoft.EntityFrameworkCore.Cosmos.Tests\Aspire.Microsoft.EntityFrameworkCore.Cosmos.Tests.csproj", "{FDA02617-9C49-4DA8-A43A-A34DBA9B8596}"
+EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CatalogDb", "samples\eShopLite\CatalogDb\CatalogDb.csproj", "{A84C4EE3-2601-4804-BCDC-E9948E164A22}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{991DB378-6CB5-4441-BFC3-657400690FC3}"
@@ -370,6 +378,22 @@ Global
{D4BD974F-6505-43FC-A94E-2019F0DB5D5D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D4BD974F-6505-43FC-A94E-2019F0DB5D5D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D4BD974F-6505-43FC-A94E-2019F0DB5D5D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {23298562-C1D4-41CD-83FE-426C94FEE35F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {23298562-C1D4-41CD-83FE-426C94FEE35F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {23298562-C1D4-41CD-83FE-426C94FEE35F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {23298562-C1D4-41CD-83FE-426C94FEE35F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {00C9BA50-2AFB-4D9C-A2D6-8154BCCD0A63}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {00C9BA50-2AFB-4D9C-A2D6-8154BCCD0A63}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {00C9BA50-2AFB-4D9C-A2D6-8154BCCD0A63}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {00C9BA50-2AFB-4D9C-A2D6-8154BCCD0A63}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A5836BC1-6A45-4BB6-9D22-A7F750890AB8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A5836BC1-6A45-4BB6-9D22-A7F750890AB8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A5836BC1-6A45-4BB6-9D22-A7F750890AB8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A5836BC1-6A45-4BB6-9D22-A7F750890AB8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {FDA02617-9C49-4DA8-A43A-A34DBA9B8596}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {FDA02617-9C49-4DA8-A43A-A34DBA9B8596}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {FDA02617-9C49-4DA8-A43A-A34DBA9B8596}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {FDA02617-9C49-4DA8-A43A-A34DBA9B8596}.Release|Any CPU.Build.0 = Release|Any CPU
{A84C4EE3-2601-4804-BCDC-E9948E164A22}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A84C4EE3-2601-4804-BCDC-E9948E164A22}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A84C4EE3-2601-4804-BCDC-E9948E164A22}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -446,6 +470,10 @@ Global
{E2EC79D0-80F7-4471-9613-D7C8C3D52F95} = {B80354C7-BE58-43F6-8928-9F3A74AB7F47}
{D4BD974F-6505-43FC-A94E-2019F0DB5D5D} = {B80354C7-BE58-43F6-8928-9F3A74AB7F47}
{A68BA1A5-1604-433D-9778-DC0199831C2A} = {D173887B-AF42-4576-B9C1-96B9E9B3D9C0}
+ {23298562-C1D4-41CD-83FE-426C94FEE35F} = {27381127-6C45-4B4C-8F18-41FF48DFE4B2}
+ {00C9BA50-2AFB-4D9C-A2D6-8154BCCD0A63} = {27381127-6C45-4B4C-8F18-41FF48DFE4B2}
+ {A5836BC1-6A45-4BB6-9D22-A7F750890AB8} = {4981B3A5-4AFD-4191-BF7D-8692D9783D60}
+ {FDA02617-9C49-4DA8-A43A-A34DBA9B8596} = {4981B3A5-4AFD-4191-BF7D-8692D9783D60}
{A84C4EE3-2601-4804-BCDC-E9948E164A22} = {A68BA1A5-1604-433D-9778-DC0199831C2A}
{4D8A92AB-4E77-4965-AD8E-8E206DCE66A4} = {27381127-6C45-4B4C-8F18-41FF48DFE4B2}
{165411FE-755E-4869-A756-F87F455860AC} = {4981B3A5-4AFD-4191-BF7D-8692D9783D60}
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 58ed2336686..1429decffc2 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -14,6 +14,7 @@
+
@@ -45,6 +46,7 @@
+
@@ -101,4 +103,4 @@
-
+
\ No newline at end of file
diff --git a/src/Aspire.Hosting.Azure/AzureCosmosDBCloudApplicationBuilderExtensions.cs b/src/Aspire.Hosting.Azure/AzureCosmosDBCloudApplicationBuilderExtensions.cs
new file mode 100644
index 00000000000..3d307a2ff78
--- /dev/null
+++ b/src/Aspire.Hosting.Azure/AzureCosmosDBCloudApplicationBuilderExtensions.cs
@@ -0,0 +1,60 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Aspire.Hosting.ApplicationModel;
+using Aspire.Hosting.Azure.Data.Cosmos;
+using System.Text.Json;
+
+namespace Aspire.Hosting;
+
+///
+/// Provides extension methods for adding Azure Cosmos DB resources to an .
+///
+public static class AzureCosmosDBCloudApplicationBuilderExtensions
+{
+ ///
+ /// Adds an Azure Cosmos DB connection to the application model.
+ ///
+ /// The .
+ /// The name of the resource. This name will be used as the connection string name when referenced in a dependency.
+ /// The connection string.
+ /// A reference to the .
+ public static IResourceBuilder AddAzureCosmosDB(
+ this IDistributedApplicationBuilder builder,
+ string name,
+ string? connectionString = null)
+ {
+ var connection = new AzureCosmosDBConnectionResource(name, connectionString);
+ return builder.AddResource(connection)
+ .WithAnnotation(new ManifestPublishingCallbackAnnotation(jsonWriter => WriteCosmosDBConnectionToManifest(jsonWriter, connection)));
+ }
+
+ private static void WriteCosmosDBConnectionToManifest(Utf8JsonWriter jsonWriter, AzureCosmosDBConnectionResource cosmosDbConnection)
+ {
+ jsonWriter.WriteString("type", "azure.cosmosdb.connection.v0");
+ jsonWriter.WriteString("connectionString", cosmosDbConnection.GetConnectionString());
+ }
+
+ private static void WriteCosmosDBDatabaseToManifest(Utf8JsonWriter jsonWriter, AzureCosmosDatabaseResource cosmosDatabase)
+ {
+ jsonWriter.WriteString("type", "azure.cosmosdb.database.v0");
+ jsonWriter.WriteString("parent", cosmosDatabase.Parent.Name);
+ jsonWriter.WriteString("databaseName", cosmosDatabase.Name);
+ }
+
+ ///
+ /// Adds an Azure Cosmos DB database to a .
+ ///
+ /// The .
+ /// The name of the resource. This name will be used as the connection string name when referenced in a dependency.
+ /// A reference to the .
+ public static IResourceBuilder AddDatabase(this IResourceBuilder builder, string name)
+ {
+ var cosmosDatabase = new AzureCosmosDatabaseResource(name, builder.Resource);
+ return builder
+ .ApplicationBuilder
+ .AddResource(cosmosDatabase)
+ .WithAnnotation(new ManifestPublishingCallbackAnnotation(
+ (json) => WriteCosmosDBDatabaseToManifest(json, cosmosDatabase)));
+ }
+}
diff --git a/src/Aspire.Hosting.Azure/AzureCosmosDBConnectionResource.cs b/src/Aspire.Hosting.Azure/AzureCosmosDBConnectionResource.cs
new file mode 100644
index 00000000000..53b1f2381e6
--- /dev/null
+++ b/src/Aspire.Hosting.Azure/AzureCosmosDBConnectionResource.cs
@@ -0,0 +1,21 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Aspire.Hosting.ApplicationModel;
+
+namespace Aspire.Hosting.Azure.Data.Cosmos;
+
+///
+/// Represents a connection to an Azure Cosmos DB account.
+///
+/// The resource name.
+/// The connection string to use to connect.
+public class AzureCosmosDBConnectionResource(string name, string? connectionString)
+ : Resource(name), IResourceWithConnectionString
+{
+ ///
+ /// Gets the connection string to use for this database.
+ ///
+ /// The connection string to use for this database.
+ public string? GetConnectionString() => connectionString;
+}
diff --git a/src/Aspire.Hosting.Azure/AzureCosmosDatabaseResource.cs b/src/Aspire.Hosting.Azure/AzureCosmosDatabaseResource.cs
new file mode 100644
index 00000000000..96cc0feb4b2
--- /dev/null
+++ b/src/Aspire.Hosting.Azure/AzureCosmosDatabaseResource.cs
@@ -0,0 +1,29 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Aspire.Hosting.ApplicationModel;
+
+namespace Aspire.Hosting.Azure.Data.Cosmos;
+
+///
+/// Represents an Azure Cosmos DB database.
+///
+/// The database name.
+/// The parent .
+public class AzureCosmosDatabaseResource(string name, AzureCosmosDBConnectionResource parent)
+ : Resource(name), IResourceWithParent, IResourceWithConnectionString
+{
+ ///
+ /// Gets the parent .
+ ///
+ public AzureCosmosDBConnectionResource Parent { get; } = parent;
+
+ ///
+ /// Gets the connection string to use for this database.
+ ///
+ /// The connection string to use for this database.
+ public string? GetConnectionString()
+ {
+ return Parent.GetConnectionString();
+ }
+}
diff --git a/src/Components/Aspire.Microsoft.Azure.Cosmos/Aspire.Microsoft.Azure.Cosmos.csproj b/src/Components/Aspire.Microsoft.Azure.Cosmos/Aspire.Microsoft.Azure.Cosmos.csproj
new file mode 100644
index 00000000000..e41dd949483
--- /dev/null
+++ b/src/Components/Aspire.Microsoft.Azure.Cosmos/Aspire.Microsoft.Azure.Cosmos.csproj
@@ -0,0 +1,20 @@
+
+
+
+ $(NetCurrent)
+ true
+ false
+ false
+ $(ComponentAzurePackageTags) cosmos cosmosdb data database db
+ A client for Azure Cosmos DB that integrates with Aspire, including logging and telemetry.
+ $(SharedDir)AzureCosmosDB_256x.png
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Components/Aspire.Microsoft.Azure.Cosmos/AspireAzureCosmosDBExtensions.cs b/src/Components/Aspire.Microsoft.Azure.Cosmos/AspireAzureCosmosDBExtensions.cs
new file mode 100644
index 00000000000..cfe43cbc4e0
--- /dev/null
+++ b/src/Components/Aspire.Microsoft.Azure.Cosmos/AspireAzureCosmosDBExtensions.cs
@@ -0,0 +1,126 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Aspire.Microsoft.Azure.Cosmos;
+using Azure.Identity;
+using Microsoft.Azure.Cosmos;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Microsoft.Extensions.Hosting;
+
+///
+/// Azure CosmosDB extension
+///
+public static class AspireAzureCosmosDBExtensions
+{
+ private const string DefaultConfigSectionName = "Aspire:Microsoft:Azure:Cosmos";
+
+ ///
+ /// Registers as a singleton in the services provided by the .
+ /// Configures logging and telemetry for the .
+ ///
+ /// The to read config from and add services to.
+ /// The connection name to use to find a connection string.
+ /// An optional method that can be used for customizing the . It's invoked after the settings are read from the configuration.
+ /// An optional method that can be used for customizing the .
+ /// Reads the configuration from "Aspire:Microsoft:Azure:Cosmos" section.
+ /// If required ConnectionString is not provided in configuration section
+ public static void AddAzureCosmosDB(
+ this IHostApplicationBuilder builder,
+ string connectionName,
+ Action? configureSettings = null,
+ Action? configureClientOptions = null)
+ {
+ AddAzureCosmosDB(builder, DefaultConfigSectionName, configureSettings, configureClientOptions, connectionName, serviceKey: null);
+ }
+
+ ///
+ /// Registers as a singleton for given in the services provided by the .
+ /// Configures logging and telemetry for the .
+ ///
+ /// The to read config from and add services to.
+ /// The name of the component, which is used as the of the service and also to retrieve the connection string from the ConnectionStrings configuration section.
+ /// An optional method that can be used for customizing the . It's invoked after the settings are read from the configuration.
+ /// An optional method that can be used for customizing the .
+ /// Reads the configuration from "Aspire:Microsoft:Azure:Cosmos:{name}" section.
+ /// If required ConnectionString is not provided in configuration section
+ public static void AddKeyedAzureCosmosDB(
+ this IHostApplicationBuilder builder,
+ string name,
+ Action? configureSettings = null,
+ Action? configureClientOptions = null)
+ {
+ AddAzureCosmosDB(builder, $"{DefaultConfigSectionName}:{name}", configureSettings, configureClientOptions, connectionName: name, serviceKey: name);
+ }
+
+ private static void AddAzureCosmosDB(
+ this IHostApplicationBuilder builder,
+ string configurationSectionName,
+ Action? configureSettings,
+ Action? configureClientOptions,
+ string connectionName,
+ string? serviceKey)
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+
+ var settings = new AzureCosmosDBSettings();
+ builder.Configuration.GetSection(configurationSectionName).Bind(settings);
+
+ if (builder.Configuration.GetConnectionString(connectionName) is string connectionString)
+ {
+ if (Uri.TryCreate(connectionString, UriKind.Absolute, out var uri))
+ {
+ settings.AccountEndpoint = uri;
+ }
+ else
+ {
+ settings.ConnectionString = connectionString;
+ }
+ }
+
+ configureSettings?.Invoke(settings);
+
+ var clientOptions = new CosmosClientOptions();
+ // Needs to be enabled for either logging or tracing to work.
+ clientOptions.CosmosClientTelemetryOptions.DisableDistributedTracing = false;
+ if (settings.Tracing)
+ {
+ builder.Services.AddOpenTelemetry().WithTracing(tracerProviderBuilder =>
+ {
+ tracerProviderBuilder.AddSource("Azure.Cosmos.Operation");
+ });
+ }
+
+ configureClientOptions?.Invoke(clientOptions);
+
+ if (serviceKey is null)
+ {
+ builder.Services.AddSingleton(_ => ConfigureDb());
+ }
+ else
+ {
+ builder.Services.AddKeyedSingleton(serviceKey, (sp, key) => ConfigureDb());
+ }
+
+ CosmosClient ConfigureDb()
+ {
+ if (!string.IsNullOrEmpty(settings.ConnectionString))
+ {
+ return new CosmosClient(settings.ConnectionString, clientOptions);
+ }
+ else if (settings.AccountEndpoint is not null)
+ {
+ var credential = settings.Credential ?? new DefaultAzureCredential();
+ return new CosmosClient(settings.AccountEndpoint.OriginalString, credential, clientOptions);
+ }
+ else
+ {
+ throw new InvalidOperationException(
+ $"A CosmosClient could not be configured. Ensure valid connection information was provided in 'ConnectionStrings:{connectionName}' or either " +
+ $"{nameof(settings.ConnectionString)} or {nameof(settings.AccountEndpoint)} must be provided " +
+ $"in the '{configurationSectionName}' configuration section.");
+ }
+ }
+ }
+}
diff --git a/src/Components/Aspire.Microsoft.Azure.Cosmos/AzureCosmosDBSettings.cs b/src/Components/Aspire.Microsoft.Azure.Cosmos/AzureCosmosDBSettings.cs
new file mode 100644
index 00000000000..98b9aaae607
--- /dev/null
+++ b/src/Components/Aspire.Microsoft.Azure.Cosmos/AzureCosmosDBSettings.cs
@@ -0,0 +1,39 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Azure.Core;
+
+namespace Aspire.Microsoft.Azure.Cosmos;
+
+///
+/// The settings relevant to accessing Azure Cosmos DB.
+///
+public sealed class AzureCosmosDBSettings
+{
+ ///
+ /// Gets or sets the connection string of the Azure Cosmos database to connect to.
+ ///
+ public string? ConnectionString { get; set; }
+
+ ///
+ /// Gets or sets a boolean value that indicates whether the OpenTelemetry tracing is enabled or not.
+ /// Enabled by default.
+ ///
+ public bool Tracing { get; set; } = true;
+
+ ///
+ /// A referencing the Azure Cosmos DB Endpoint.
+ /// This is likely to be similar to "https://{account_name}.queue.core.windows.net".
+ ///
+ ///
+ /// Must not contain shared access signature.
+ /// Used along with to establish the connection.
+ ///
+ public Uri? AccountEndpoint { get; set; }
+
+ ///
+ /// Gets or sets the credential used to authenticate to the Azure Cosmos DB endpoint.
+ ///
+ public TokenCredential? Credential { get; set; }
+}
+
diff --git a/src/Components/Aspire.Microsoft.Azure.Cosmos/ConfigurationSchema.json b/src/Components/Aspire.Microsoft.Azure.Cosmos/ConfigurationSchema.json
new file mode 100644
index 00000000000..9ba4f921b4a
--- /dev/null
+++ b/src/Components/Aspire.Microsoft.Azure.Cosmos/ConfigurationSchema.json
@@ -0,0 +1,46 @@
+{
+ "definitions": {
+ "logLevel": {
+ "properties": {
+ "Azure-Cosmos-Operation-Request-Diagnostics": {
+ "$ref": "#/definitions/logLevelThreshold"
+ }
+ }
+ }
+ },
+ "properties": {
+ "Aspire": {
+ "type": "object",
+ "properties": {
+ "Microsoft": {
+ "type": "object",
+ "properties": {
+ "Azure": {
+ "type": "object",
+ "properties": {
+ "Cosmos": {
+ "type": "object",
+ "properties": {
+ "ConnectionString": {
+ "type": "string",
+ "description": "Gets or sets the connection string of the Azure Cosmos DB to connect to. If both are provided, 'ConnectionString' takes precedence over 'AccountEndpoint'."
+ },
+ "AccountEndpoint": {
+ "type": "string",
+ "format": "uri",
+ "description": "Gets or sets the account endpoint of the Azure Cosmos DB to connect to. If both are provided, 'ConnectionString' takes precedence over 'AccountEndpoint'."
+ },
+ "Tracing": {
+ "type": "boolean",
+ "description": "Gets or sets a boolean value that indicates whether the OpenTelemetry tracing is enabled or not."
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/Components/Aspire.Microsoft.Azure.Cosmos/README.md b/src/Components/Aspire.Microsoft.Azure.Cosmos/README.md
new file mode 100644
index 00000000000..2aeefdbafce
--- /dev/null
+++ b/src/Components/Aspire.Microsoft.Azure.Cosmos/README.md
@@ -0,0 +1,135 @@
+# Aspire.Microsoft.Azure.Cosmos library
+
+Registers [CosmosClient](https://learn.microsoft.com/dotnet/api/microsoft.azure.cosmos.cosmosclient) as a singleton in the DI container for connecting to Azure Cosmos DB. Enables corresponding logging and telemetry.
+
+## Getting started
+
+### Prerequisites
+
+- Azure subscription - [create one for free](https://azure.microsoft.com/free/)
+- Azure Cosmos DB account - [create a Cosmos DB account](https://learn.microsoft.com/azure/cosmos-db/nosql/how-to-create-account)
+
+### Install the package
+
+Install the Aspire Microsft Azure Cosmos DB library with [NuGet][nuget]:
+
+```dotnetcli
+dotnet add package Aspire.Microsoft.Azure.Cosmos
+```
+
+## Usage example
+
+In the _Program.cs_ file of your project, call the `AddAzureCosmosDB` extension method to register a `CosmosClient` for use via the dependency injection container. The method takes a connection name parameter.
+
+```csharp
+builder.AddAzureCosmosDB("cosmosConnectionName");
+```
+
+You can then retrieve the `CosmosClient` instance using dependency injection. For example, to retrieve the client from a Web API controller:
+
+```csharp
+private readonly CosmosClient _client;
+
+public ProductsController(CosmosClient client)
+{
+ _client = client;
+}
+```
+
+See the [Azure Cosmos DB documentation](https://learn.microsoft.com/dotnet/api/microsoft.azure.cosmos.cosmosclient) for examples on using the `CosmosClient`.
+
+## Configuration
+
+The Aspire Azure Cosmos DB library provides multiple options to configure the Azure Cosmos DB connection based on the requirements and conventions of your project. Note that either an `AccountEndpoint` or a `ConnectionString` is a required to be supplied.
+
+### Use a connection string
+
+When using a connection string from the `ConnectionStrings` configuration section, you can provide the name of the connection string when calling `builder.AddAzureCosmosDB()`:
+
+```csharp
+builder.AddAzureCosmosDB("cosmosConnectionName");
+```
+
+And then the connection string will be retrieved from the `ConnectionStrings` configuration section. Two connection formats are supported:
+
+#### Account Endpoint
+
+The recommended approach is to use an AccountEndpoint, which works with the `AzureCosmosDBSettings.Credential` property to establish a connection. If no credential is configured, the [DefaultAzureCredential](https://learn.microsoft.com/dotnet/api/azure.identity.defaultazurecredential) is used.
+
+```json
+{
+ "ConnectionStrings": {
+ "cosmosConnectionName": "https://{account_name}.documents.azure.com:443/"
+ }
+}
+```
+
+#### Connection string
+
+Alternatively, an [Azure Cosmos DB connection string](https://learn.microsoft.com/azure/cosmos-db/nosql/how-to-dotnet-get-started#connect-with-a-connection-string) can be used.
+
+```json
+{
+ "ConnectionStrings": {
+ "cosmosConnectionName": "AccountEndpoint=https://{account_name}.documents.azure.com:443/;AccountKey={account_key};"
+ }
+}
+```
+
+### Use configuration providers
+
+The Aspire Microsoft Azure Cosmos DB library supports [Microsoft.Extensions.Configuration](https://learn.microsoft.com/dotnet/api/microsoft.extensions.configuration). It loads the `AzureCosmosDBSettings` and `QueueClientOptions` from configuration by using the `Aspire:Microsoft:Azure:Cosmos` key. Example `appsettings.json` that configures some of the options:
+
+```json
+{
+ "Aspire": {
+ "Microsoft": {
+ "Azure": {
+ "Cosmos": {
+ "Tracing": true,
+ }
+ }
+ }
+ }
+}
+```
+
+### Use inline delegates
+
+You can also pass the `Action configureSettings` delegate to set up some or all the options inline, for example to disable tracing from code:
+
+```csharp
+ builder.AddAzureCosmosDB("cosmosConnectionName", settings => settings.Tracing = false);
+```
+
+You can also setup the [CosmosClientOptions](https://learn.microsoft.com/dotnet/api/microsoft.azure.cosmos.cosmosclientoptions) using the optional `Action configureClientOptions` parameter of the `AddAzureCosmosDB` method. For example, to set the `ApplicationName` "User-Agent" header suffix for all requests issues by this client:
+
+```csharp
+ builder.AddAzureCosmosDB("cosmosConnectionName", configureClientOptions: clientOptions => clientOptions.ApplicationName = "myapp");
+```
+
+## AppHost extensions
+
+In your AppHost project, add a Cosmos DB connection and consume the connection using the following methods:
+
+```csharp
+var cosmosdb = builder.AddAzureCosmosDB("cdb").AddDatabase("cosmosdb");
+
+var myService = builder.AddProject()
+ .WithReference(cosmosdb);
+```
+
+The `AddAzureCosmosDB` method will read connection information from the AppHost's configuration (for example, from "user secrets") under the `ConnectionStrings:cosmosdb` config key. The `WithReference` method passes that connection information into a connection string named `cosmosdb` in the `MyService` project. In the _Program.cs_ file of `MyService`, the connection can be consumed using:
+
+```csharp
+builder.AddAzureCosmosDB("cosmosdb");
+```
+
+## Additional documentation
+
+* https://learn.microsoft.com/azure/cosmos-db/nosql/sdk-dotnet-v3
+* https://github.com/dotnet/aspire/tree/main/src/Components/README.md
+
+## Feedback & contributing
+
+https://github.com/dotnet/aspire
diff --git a/src/Components/Aspire.Microsoft.EntityFrameworkCore.Cosmos/Aspire.Microsoft.EntityFrameworkCore.Cosmos.csproj b/src/Components/Aspire.Microsoft.EntityFrameworkCore.Cosmos/Aspire.Microsoft.EntityFrameworkCore.Cosmos.csproj
new file mode 100644
index 00000000000..913d7e60273
--- /dev/null
+++ b/src/Components/Aspire.Microsoft.EntityFrameworkCore.Cosmos/Aspire.Microsoft.EntityFrameworkCore.Cosmos.csproj
@@ -0,0 +1,24 @@
+
+
+
+ $(NetCurrent)
+ true
+ false
+ false
+ $(ComponentEfCorePackageTags) azure cosmos cosmosdb
+ A Microsoft Azure Cosmos DB provider for Entity Framework Core that integrates with Aspire, including connection pooling, logging, and telemetry.
+ $(SharedDir)AzureCosmosDB_256x.png
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Components/Aspire.Microsoft.EntityFrameworkCore.Cosmos/AspireAzureEFCoreCosmosDBExtensions.cs b/src/Components/Aspire.Microsoft.EntityFrameworkCore.Cosmos/AspireAzureEFCoreCosmosDBExtensions.cs
new file mode 100644
index 00000000000..fa518a0a41f
--- /dev/null
+++ b/src/Components/Aspire.Microsoft.EntityFrameworkCore.Cosmos/AspireAzureEFCoreCosmosDBExtensions.cs
@@ -0,0 +1,132 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics.CodeAnalysis;
+using Aspire.Microsoft.EntityFrameworkCore.Cosmos;
+using Azure.Identity;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using OpenTelemetry.Metrics;
+using OpenTelemetry.Trace;
+
+namespace Microsoft.Extensions.Hosting;
+
+///
+/// Extension methods for configuring EntityFrameworkCore DbContext to Azure Cosmos DB
+///
+public static class AspireAzureEFCoreCosmosDBExtensions
+{
+ private const string DefaultConfigSectionName = "Aspire:Microsoft:EntityFrameworkCore:Cosmos";
+ private const DynamicallyAccessedMemberTypes RequiredByEF = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties;
+
+ ///
+ /// Registers the given as a service in the services provided by the .
+ /// Configures the connection pooling, logging and telemetry for the .
+ ///
+ /// The that needs to be registered.
+ /// The to read config from and add services to.
+ /// A name used to retrieve the connection string from the ConnectionStrings configuration section.
+ /// The name of the database to use within the Azure Cosmos DB account.
+ /// An optional delegate that can be used for customizing settings. It's invoked after the settings are read from the configuration.
+ /// An optional delegate to configure the for the context.
+ /// Thrown if mandatory is null.
+ /// Thrown when mandatory is not provided.
+ public static void AddCosmosDbContext<[DynamicallyAccessedMembers(RequiredByEF)] TContext>(
+ this IHostApplicationBuilder builder,
+ string connectionName,
+ string databaseName,
+ Action? configureSettings = null,
+ Action? configureDbContextOptions = null) where TContext : DbContext
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+
+ var settings = new EntityFrameworkCoreCosmosDBSettings();
+ var typeSpecificSectionName = $"{DefaultConfigSectionName}:{typeof(TContext).Name}";
+ var typeSpecificConfigurationSection = builder.Configuration.GetSection(typeSpecificSectionName);
+ if (typeSpecificConfigurationSection.Exists()) // https://github.com/dotnet/runtime/issues/91380
+ {
+ typeSpecificConfigurationSection.Bind(settings);
+ }
+ else
+ {
+ builder.Configuration.GetSection(DefaultConfigSectionName).Bind(settings);
+ }
+
+ if (builder.Configuration.GetConnectionString(connectionName) is string connectionString)
+ {
+ if (Uri.TryCreate(connectionString, UriKind.Absolute, out var uri))
+ {
+ settings.AccountEndpoint = uri;
+ }
+ else
+ {
+ settings.ConnectionString = connectionString;
+ }
+ }
+ configureSettings?.Invoke(settings);
+
+ if (settings.DbContextPooling)
+ {
+ builder.Services.AddDbContextPool(ConfigureDbContext);
+ }
+ else
+ {
+ builder.Services.AddDbContext(ConfigureDbContext);
+ }
+
+ if (settings.Tracing)
+ {
+ builder.Services.AddOpenTelemetry().WithTracing(tracerProviderBuilder =>
+ {
+ tracerProviderBuilder.AddEntityFrameworkCoreInstrumentation();
+ tracerProviderBuilder.AddSource("Azure.Cosmos.Operation");
+ });
+ }
+
+ if (settings.Metrics)
+ {
+ builder.Services.AddOpenTelemetry().WithMetrics(meterProviderBuilder =>
+ {
+ meterProviderBuilder.AddEventCountersInstrumentation(eventCountersInstrumentationOptions =>
+ {
+ // https://github.com/dotnet/efcore/blob/main/src/EFCore/Infrastructure/EntityFrameworkEventSource.cs#L45
+ eventCountersInstrumentationOptions.AddEventSources("Microsoft.EntityFrameworkCore");
+ });
+ });
+ }
+
+ void ConfigureDbContext(DbContextOptionsBuilder dbContextOptionsBuilder)
+ {
+ if (!string.IsNullOrEmpty(settings.ConnectionString))
+ {
+ dbContextOptionsBuilder.UseCosmos(settings.ConnectionString, databaseName, UseCosmosBody);
+ }
+ else if (settings.AccountEndpoint is not null)
+ {
+ var credential = settings.Credential ?? new DefaultAzureCredential();
+ dbContextOptionsBuilder.UseCosmos(settings.AccountEndpoint.OriginalString, credential, databaseName, UseCosmosBody);
+ }
+ else
+ {
+ throw new InvalidOperationException(
+ $"A DbContext could not be configured. Ensure valid connection information was provided in 'ConnectionStrings:{connectionName}' or either " +
+ $"{nameof(settings.ConnectionString)} or {nameof(settings.AccountEndpoint)} must be provided " +
+ $"in the '{DefaultConfigSectionName}' or '{typeSpecificSectionName}' configuration section.");
+ }
+
+ configureDbContextOptions?.Invoke(dbContextOptionsBuilder);
+ }
+
+ void UseCosmosBody(CosmosDbContextOptionsBuilder builder)
+ {
+ // We don't register logger factory, because there is no need to:
+ // https://learn.microsoft.com/dotnet/api/microsoft.entityframeworkcore.dbcontextoptionsbuilder.useloggerfactory?view=efcore-7.0#remarks
+ if (settings.Region is not null)
+ {
+ builder.Region(settings.Region);
+ }
+ }
+ }
+}
diff --git a/src/Components/Aspire.Microsoft.EntityFrameworkCore.Cosmos/ConfigurationSchema.json b/src/Components/Aspire.Microsoft.EntityFrameworkCore.Cosmos/ConfigurationSchema.json
new file mode 100644
index 00000000000..40dc2ae5b25
--- /dev/null
+++ b/src/Components/Aspire.Microsoft.EntityFrameworkCore.Cosmos/ConfigurationSchema.json
@@ -0,0 +1,80 @@
+{
+ "definitions": {
+ "logLevel": {
+ "properties": {
+ "Azure-Cosmos-Operation-Request-Diagnostics": {
+ "$ref": "#/definitions/logLevelThreshold"
+ },
+ "Microsoft.EntityFrameworkCore": {
+ "$ref": "#/definitions/logLevelThreshold"
+ },
+ "Microsoft.EntityFrameworkCore.ChangeTracking": {
+ "$ref": "#/definitions/logLevelThreshold"
+ },
+ "Microsoft.EntityFrameworkCore.Database": {
+ "$ref": "#/definitions/logLevelThreshold"
+ },
+ "Microsoft.EntityFrameworkCore.Database.Command": {
+ "$ref": "#/definitions/logLevelThreshold"
+ },
+ "Microsoft.EntityFrameworkCore.Infrastructure": {
+ "$ref": "#/definitions/logLevelThreshold"
+ },
+ "Microsoft.EntityFrameworkCore.Query": {
+ "$ref": "#/definitions/logLevelThreshold"
+ }
+ }
+ }
+ },
+ "properties": {
+ "Aspire": {
+ "type": "object",
+ "properties": {
+ "Microsoft": {
+ "type": "object",
+ "properties": {
+ "EntityFrameworkCore": {
+ "type": "object",
+ "properties": {
+ "Cosmos": {
+ "type": "object",
+ "properties": {
+ "AccountEndpoint": {
+ "type": "string",
+ "format": "uri",
+ "description": "Gets or sets the account endpoint of the Azure Cosmos DB account to connect to. Used along with \"Credential\" to establish the connection."
+ },
+ "ConnectionString": {
+ "type": "string",
+ "description": "Gets or sets the connection string of the Azure Cosmos DB account to connect to."
+ },
+ "DbContextPooling": {
+ "type": "boolean",
+ "description": "Gets or sets a boolean value that indicates whether the DbContext will be pooled or explicitly created every time it's requested.",
+ "default": true
+ },
+ "Tracing": {
+ "type": "boolean",
+ "description": "Gets or sets a boolean value that indicates whether the OpenTelemetry tracing is enabled or not.",
+ "default": true
+ },
+ "Metrics": {
+ "type": "boolean",
+ "description": "Gets or sets a boolean value that indicates whether the OpenTelemetry metrics are enabled or not.",
+ "default": true
+ },
+ "Region": {
+ "type": "string",
+ "description": "Gets or sets a string value that indicates what Azure region this client will run in."
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "type": "object"
+}
diff --git a/src/Components/Aspire.Microsoft.EntityFrameworkCore.Cosmos/EntityFrameworkCoreCosmosDBSettings.cs b/src/Components/Aspire.Microsoft.EntityFrameworkCore.Cosmos/EntityFrameworkCoreCosmosDBSettings.cs
new file mode 100644
index 00000000000..320f595ccfb
--- /dev/null
+++ b/src/Components/Aspire.Microsoft.EntityFrameworkCore.Cosmos/EntityFrameworkCoreCosmosDBSettings.cs
@@ -0,0 +1,54 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Azure.Core;
+
+namespace Aspire.Microsoft.EntityFrameworkCore.Cosmos;
+
+///
+/// The settings relevant to accessing Azure Cosmos DB database using EntityFrameworkCore.
+///
+public sealed class EntityFrameworkCoreCosmosDBSettings
+{
+ ///
+ /// The connection string of the Azure Cosmos DB server database to connect to.
+ ///
+ public string? ConnectionString { get; set; }
+
+ ///
+ /// A referencing the Azure Cosmos DB Endpoint.
+ /// This is likely to be similar to "https://{account_name}.queue.core.windows.net".
+ ///
+ ///
+ /// Must not contain shared access signature.
+ /// Used along with to establish the connection.
+ ///
+ public Uri? AccountEndpoint { get; set; }
+
+ ///
+ /// Gets or sets the credential used to authenticate to the Azure Cosmos DB endpoint.
+ ///
+ public TokenCredential? Credential { get; set; }
+
+ ///
+ /// Gets or sets a boolean value that indicates whether the db context will be pooled or explicitly created every time it's requested.
+ ///
+ public bool DbContextPooling { get; set; } = true;
+
+ ///
+ /// Gets or sets a boolean value that indicates whether the Open Telemetry tracing is enabled or not.
+ /// Enabled by default.
+ ///
+ public bool Tracing { get; set; } = true;
+
+ ///
+ /// Gets or sets a boolean value that indicates whether the Open Telemetry metrics are enabled or not.
+ /// Enabled by default.
+ ///
+ public bool Metrics { get; set; } = true;
+
+ ///
+ /// Gets or sets a string value that indicates what Azure region this client will run in.
+ ///
+ public string? Region { get; set; }
+}
diff --git a/src/Components/Aspire.Microsoft.EntityFrameworkCore.Cosmos/README.md b/src/Components/Aspire.Microsoft.EntityFrameworkCore.Cosmos/README.md
new file mode 100644
index 00000000000..f1d90dbaad0
--- /dev/null
+++ b/src/Components/Aspire.Microsoft.EntityFrameworkCore.Cosmos/README.md
@@ -0,0 +1,113 @@
+# Aspire.Microsoft.EntityFrameworkCore.Cosmos library
+
+Registers [EntityFrameworkCore](https://learn.microsoft.com/en-us/ef/core/) [DbContext](https://learn.microsoft.com/dotnet/api/microsoft.entityframeworkcore.dbcontext) in the DI container for connecting to Azure Cosmos DB. Enables connection pooling, logging and telemetry.
+
+## Getting started
+
+### Prerequisites
+
+- CosmosDB database and connection string for accessing the database.
+
+### Install the package
+
+Install the Aspire Microsoft EntityFrameworkCore Cosmos library with [NuGet][nuget]:
+
+```dotnetcli
+dotnet add package Aspire.Microsoft.EntityFrameworkCore.Cosmos
+```
+
+## Usage example
+
+In the _Program.cs_ file of your project, call the `AddCosmosDbContext` extension method to register a `DbContext` for use via the dependency injection container. The method takes a connection name parameter.
+
+```csharp
+builder.AddCosmosDbContext("cosmosdb");
+```
+
+You can then retrieve the `MyDbContext` instance using dependency injection. For example, to retrieve the context from a Web API controller:
+
+```csharp
+private readonly MyDbContext _context;
+
+public ProductsController(MyDbContext context)
+{
+ _context = context;
+}
+```
+
+## Configuration
+
+The Aspire Microsoft EntityFrameworkCore Cosmos component 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 from the `ConnectionStrings` configuration section, you can provide the name of the connection string when calling `builder.AddCosmosDbContext()`:
+
+```csharp
+builder.AddCosmosDbContext("myConnection");
+```
+
+And then the connection string will be retrieved from the `ConnectionStrings` configuration section:
+
+```json
+{
+ "ConnectionStrings": {
+ "myConnection": "AccountEndpoint=https://{account_name}.documents.azure.com:443/;AccountKey={account_key};"
+ }
+}
+```
+
+See the [ConnectionString documentation](https://learn.microsoft.com/azure/cosmos-db/nosql/how-to-dotnet-get-started#connect-with-a-connection-string) for more information.
+
+### Use configuration providers
+
+The Aspire Microsoft EntityFrameworkCore Cosmos component supports [Microsoft.Extensions.Configuration](https://learn.microsoft.com/dotnet/api/microsoft.extensions.configuration). It loads the `EntityFrameworkCoreCosmosDBSettings` from configuration by using the `Aspire:Microsaoft:EntityFrameworkCore:Cosmos` key. Example `appsettings.json` that configures some of the options:
+
+```json
+{
+ "Aspire": {
+ "Microsoft": {
+ "EntityFrameworkCore": {
+ "Cosmos": {
+ "DbContextPooling": true,
+ "Tracing": false
+ }
+ }
+ }
+ }
+}
+```
+
+### Use inline delegates
+
+Also you can pass the `Action configureSettings` delegate to set up some or all the options inline, for example to disable tracing from code:
+
+```csharp
+ builder.AddCosmosDbContext("cosmosdb", settings => settings.Tracing = false);
+```
+
+## AppHost extensions
+
+In your AppHost project, add a Cosmos DB connection and consume the connection using the following methods::
+
+```csharp
+var cosmosdb = builder.AddAzureCosmosDB("cdb").AddDatabase("cosmosdb");
+
+var myService = builder.AddProject()
+ .WithReference(cosmosdb);
+```
+
+The `WithReference` method configures a connection in the `MyService` project named `cosmosdb`. In the _Program.cs_ file of `MyService`, the database connection can be consumed using:
+
+```csharp
+builder.AddCosmosDbContext("cosmosdb");
+```
+
+## Additional documentation
+
+* https://learn.microsoft.com/ef/core/
+* https://github.com/dotnet/aspire/tree/main/src/Components/README.md
+
+## Feedback & contributing
+
+https://github.com/dotnet/aspire
\ No newline at end of file
diff --git a/src/Components/Aspire_Components_Progress.md b/src/Components/Aspire_Components_Progress.md
index 20dbc4a81b0..38c7fc20aaa 100644
--- a/src/Components/Aspire_Components_Progress.md
+++ b/src/Components/Aspire_Components_Progress.md
@@ -6,9 +6,11 @@ As part of the Aspire November preview, we want to include a set of Aspire Compo
| --------------------------------------- | :---------------------------------: | :-----------------------: | :----------------------------------------------------: | :-------------------------: | :-----------------: | :-----------------: | :-----------------: | :-----------------------------: |
| Npgsql | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Npgsql.EntityFrameworkCore.PostgreSQL | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
+| Microsoft.Azure.Cosmos | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
| Microsoft.Data.SqlClient | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ |
+| Microsoft.EntityFramework.Cosmos | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
| Microsoft.EntityFrameworkCore.SqlServer | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
-| Aspire.Azure.Data.Tables | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ |
+| Azure.Data.Tables | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ |
| Azure.Messaging.ServiceBus | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ |
| Azure.Security.KeyVault | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ |
| Azure.Storage.Blobs | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ |
diff --git a/src/Components/Telemetry.md b/src/Components/Telemetry.md
index d3bd8a82194..715f8d8a9f4 100644
--- a/src/Components/Telemetry.md
+++ b/src/Components/Telemetry.md
@@ -47,6 +47,13 @@ Aspire.Azure.Storage.Queues:
- Metric names:
- none (currently not supported by the Azure SDK)
+Aspire.Microsoft.Azure.Cosmos:
+- Log categories:
+ - "Azure-Cosmos-Operation-Request-Diagnostics"
+- Activity source names:
+ - "Azure.Cosmos.Operation"
+- Metric names:
+
Aspire.Microsoft.Data.SqlClient:
- Log categories:
- none (the client does not provide an easy way to integrate it with logger factory)
@@ -71,6 +78,29 @@ Aspire.Microsoft.Data.SqlClient:
- "number-of-stasis-connections"
- "number-of-reclaimed-connections"
+Aspire.Microsoft.EntityFrameworkCore.Cosmos:
+- Log categories:
+ - "Azure-Cosmos-Operation-Request-Diagnostics"
+ - "Microsoft.EntityFrameworkCore.ChangeTracking",
+ - "Microsoft.EntityFrameworkCore.Database.Command",
+ - "Microsoft.EntityFrameworkCore.Infrastructure",
+ - "Microsoft.EntityFrameworkCore.Query",
+- Activity source names:
+ - "Azure.Cosmos.Operation"
+ - "OpenTelemetry.Instrumentation.EntityFrameworkCore"
+- Metric names:
+ - "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"
+
Aspire.Microsoft.EntityFrameworkCore.SqlServer:
- Log categories:
- "Microsoft.EntityFrameworkCore.ChangeTracking"
diff --git a/src/Shared/AzureCosmosDB_256x.png b/src/Shared/AzureCosmosDB_256x.png
new file mode 100644
index 00000000000..ad2850532c6
Binary files /dev/null and b/src/Shared/AzureCosmosDB_256x.png differ
diff --git a/tests/Aspire.Microsoft.Azure.Cosmos.Tests/Aspire.Microsoft.Azure.Cosmos.Tests.csproj b/tests/Aspire.Microsoft.Azure.Cosmos.Tests/Aspire.Microsoft.Azure.Cosmos.Tests.csproj
new file mode 100644
index 00000000000..07a7d23049b
--- /dev/null
+++ b/tests/Aspire.Microsoft.Azure.Cosmos.Tests/Aspire.Microsoft.Azure.Cosmos.Tests.csproj
@@ -0,0 +1,12 @@
+
+
+
+ $(NetCurrent)
+
+
+
+
+
+
+
+
diff --git a/tests/Aspire.Microsoft.Azure.Cosmos.Tests/ConfigurationTests.cs b/tests/Aspire.Microsoft.Azure.Cosmos.Tests/ConfigurationTests.cs
new file mode 100644
index 00000000000..df3284ebf7f
--- /dev/null
+++ b/tests/Aspire.Microsoft.Azure.Cosmos.Tests/ConfigurationTests.cs
@@ -0,0 +1,17 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Xunit;
+
+namespace Aspire.Microsoft.Azure.Cosmos.Tests;
+
+public class ConfigurationTests
+{
+ [Fact]
+ public void ConnectionStringIsNullByDefault()
+ => Assert.Null(new AzureCosmosDBSettings().ConnectionString);
+
+ [Fact]
+ public void TracingIsEnabledByDefault()
+ => Assert.True(new AzureCosmosDBSettings().Tracing);
+}
diff --git a/tests/Aspire.Microsoft.Azure.Cosmos.Tests/ConformanceTests.cs b/tests/Aspire.Microsoft.Azure.Cosmos.Tests/ConformanceTests.cs
new file mode 100644
index 00000000000..4ff5fa82f30
--- /dev/null
+++ b/tests/Aspire.Microsoft.Azure.Cosmos.Tests/ConformanceTests.cs
@@ -0,0 +1,77 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Aspire.Components.ConformanceTests;
+using Microsoft.Azure.Cosmos;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+
+namespace Aspire.Microsoft.Azure.Cosmos.Tests;
+
+public class ConformanceTests : ConformanceTests
+{
+ protected override ServiceLifetime ServiceLifetime => ServiceLifetime.Singleton;
+
+ protected override string ActivitySourceName => "Azure.Cosmos.Operation";
+
+ protected override string[] RequiredLogCategories => Array.Empty();
+
+ protected override void PopulateConfiguration(ConfigurationManager configuration, string? key = null)
+ => configuration.AddInMemoryCollection(new KeyValuePair[1]
+ {
+ new KeyValuePair(CreateConfigKey("Aspire:Microsoft:Azure:Cosmos", key, "ConnectionString"),
+ "AccountEndpoint=https://example.documents.azure.com:443/;AccountKey=fake;")
+ });
+
+ protected override void RegisterComponent(HostApplicationBuilder builder, Action? configure = null, string? key = null)
+ {
+ if (key is null)
+ {
+ builder.AddAzureCosmosDB("cosmosdb", configure);
+ }
+ else
+ {
+ builder.AddKeyedAzureCosmosDB(key, configure);
+ }
+ }
+
+ protected override void SetHealthCheck(AzureCosmosDBSettings options, bool enabled)
+ => throw new NotImplementedException();
+
+ protected override void SetTracing(AzureCosmosDBSettings options, bool enabled)
+ => options.Tracing = enabled;
+
+ protected override void SetMetrics(AzureCosmosDBSettings options, bool enabled)
+ => throw new NotImplementedException();
+
+ protected override string JsonSchemaPath
+ => "src/Components/Aspire.Microsoft.Azure.Cosmos/ConfigurationSchema.json";
+
+ protected override string ValidJsonConfig => """
+ {
+ "Aspire": {
+ "Microsoft": {
+ "Azure": {
+ "Cosmos": {
+ "ConnectionString": "YOUR_CONNECTION_STRING",
+ "Tracing": true
+ }
+ }
+ }
+ }
+ }
+ """;
+
+ protected override (string json, string error)[] InvalidJsonToErrorMessage => new[]
+ {
+ ("""{"Aspire": { "Microsoft":{ "Azure": { "Cosmos": { "AccountEndpoint": 3 }}}}}""", "Value is \"integer\" but should be \"string\""),
+ ("""{"Aspire": { "Microsoft":{ "Azure": { "Cosmos": { "AccountEndpoint": "hello" }}}}}""", "Value does not match format \"uri\"")
+ };
+
+ protected override void TriggerActivity(CosmosClient service)
+ {
+ // TODO: Get rid of GetAwaiter().GetResult()
+ service.ReadAccountAsync().GetAwaiter().GetResult();
+ }
+}
diff --git a/tests/Aspire.Microsoft.EntityFrameworkCore.Cosmos.Tests/Aspire.Microsoft.EntityFrameworkCore.Cosmos.Tests.csproj b/tests/Aspire.Microsoft.EntityFrameworkCore.Cosmos.Tests/Aspire.Microsoft.EntityFrameworkCore.Cosmos.Tests.csproj
new file mode 100644
index 00000000000..6ef388b18b7
--- /dev/null
+++ b/tests/Aspire.Microsoft.EntityFrameworkCore.Cosmos.Tests/Aspire.Microsoft.EntityFrameworkCore.Cosmos.Tests.csproj
@@ -0,0 +1,17 @@
+
+
+
+ $(NetCurrent)
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/Aspire.Microsoft.EntityFrameworkCore.Cosmos.Tests/AspireAzureEfCoreCosmosDBExtensionsTests.cs b/tests/Aspire.Microsoft.EntityFrameworkCore.Cosmos.Tests/AspireAzureEfCoreCosmosDBExtensionsTests.cs
new file mode 100644
index 00000000000..d462195c43f
--- /dev/null
+++ b/tests/Aspire.Microsoft.EntityFrameworkCore.Cosmos.Tests/AspireAzureEfCoreCosmosDBExtensionsTests.cs
@@ -0,0 +1,51 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Aspire.Components.Common.Tests;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Cosmos.Infrastructure.Internal;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Xunit;
+
+namespace Aspire.Microsoft.EntityFrameworkCore.Cosmos.Tests;
+
+public class AspireAzureEfCoreCosmosDBExtensionsTests
+{
+ private const string ConnectionString = "AccountEndpoint=https://fake-account.documents.azure.com:443/;AccountKey=;";
+
+ [Fact]
+ public void CanConfigureDbContextOptions()
+ {
+ var builder = Host.CreateEmptyApplicationBuilder(null);
+ builder.Configuration.AddInMemoryCollection([
+ new KeyValuePair("ConnectionStrings:cosmosConnection", ConnectionString),
+ new KeyValuePair("Aspire:Microsoft:EntityFrameworkCore:Cosmos:Region", "westus"),
+ ]);
+
+ builder.AddCosmosDbContext("cosmosConnection", "databaseName", configureDbContextOptions: optionsBuilder =>
+ {
+ optionsBuilder.UseCosmos(ConnectionString, "databaseName", cosmosBuilder =>
+ {
+ cosmosBuilder.RequestTimeout(TimeSpan.FromSeconds(608));
+ });
+ });
+
+ var host = builder.Build();
+ var context = host.Services.GetRequiredService();
+
+#pragma warning disable EF1001 // Internal EF Core API usage.
+
+ var extension = context.Options.FindExtension();
+ Assert.NotNull(extension);
+
+ // Ensure the RequestTimeout from config size was respected
+ Assert.Equal(TimeSpan.FromSeconds(608), extension.RequestTimeout);
+
+ // Ensure the Region from the lambda was respected
+ Assert.Equal("westus", extension.Region);
+
+#pragma warning restore EF1001 // Internal EF Core API usage.
+ }
+}
diff --git a/tests/Aspire.Microsoft.EntityFrameworkCore.Cosmos.Tests/ConformanceTests_NoPooling.cs b/tests/Aspire.Microsoft.EntityFrameworkCore.Cosmos.Tests/ConformanceTests_NoPooling.cs
new file mode 100644
index 00000000000..374c8117a20
--- /dev/null
+++ b/tests/Aspire.Microsoft.EntityFrameworkCore.Cosmos.Tests/ConformanceTests_NoPooling.cs
@@ -0,0 +1,22 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Aspire.Components.Common.Tests;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+
+namespace Aspire.Microsoft.EntityFrameworkCore.Cosmos.Tests;
+
+public class ConformanceTests_NoPooling : ConformanceTests_Pooling
+{
+ protected override ServiceLifetime ServiceLifetime => ServiceLifetime.Scoped;
+
+ protected override void RegisterComponent(HostApplicationBuilder builder, Action? configure = null, string? key = null)
+ {
+ builder.AddCosmosDbContext("cosmosdb", "TestDatabase", settings =>
+ {
+ settings.DbContextPooling = false;
+ configure?.Invoke(settings);
+ });
+ }
+}
diff --git a/tests/Aspire.Microsoft.EntityFrameworkCore.Cosmos.Tests/ConformanceTests_Pooling.cs b/tests/Aspire.Microsoft.EntityFrameworkCore.Cosmos.Tests/ConformanceTests_Pooling.cs
new file mode 100644
index 00000000000..1ac9540ad66
--- /dev/null
+++ b/tests/Aspire.Microsoft.EntityFrameworkCore.Cosmos.Tests/ConformanceTests_Pooling.cs
@@ -0,0 +1,115 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Aspire.Components.Common.Tests;
+using Aspire.Components.ConformanceTests;
+using Microsoft.DotNet.RemoteExecutor;
+using Microsoft.EntityFrameworkCore.Internal;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Xunit;
+
+namespace Aspire.Microsoft.EntityFrameworkCore.Cosmos.Tests;
+
+public class ConformanceTests_Pooling : ConformanceTests
+{
+ protected override ServiceLifetime ServiceLifetime => ServiceLifetime.Singleton;
+
+ // https://github.com/open-telemetry/opentelemetry-dotnet-contrib/blob/cb5b2193ef9cacc0b9ef699e085022577551bf85/src/OpenTelemetry.Instrumentation.EntityFrameworkCore/Implementation/EntityFrameworkDiagnosticListener.cs#L38
+ protected override string ActivitySourceName => "OpenTelemetry.Instrumentation.EntityFrameworkCore";
+
+ protected override string[] RequiredLogCategories => new string[]
+ {
+ "Microsoft.EntityFrameworkCore.ChangeTracking",
+ "Microsoft.EntityFrameworkCore.Database.Command",
+ "Microsoft.EntityFrameworkCore.Infrastructure",
+ "Microsoft.EntityFrameworkCore.Query",
+ };
+
+ protected override void PopulateConfiguration(ConfigurationManager configuration, string? key = null)
+ => configuration.AddInMemoryCollection(new KeyValuePair[]
+ {
+ new KeyValuePair("Aspire:Microsoft:EntityFrameworkCore:Cosmos:ConnectionString",
+ "Host=fake;Database=catalog"),
+ });
+
+ protected override void RegisterComponent(HostApplicationBuilder builder, Action? configure = null, string? key = null)
+ => builder.AddCosmosDbContext("cosmosdb", "TestDatabase", configure);
+
+ protected override void SetHealthCheck(EntityFrameworkCoreCosmosDBSettings options, bool enabled)
+ => throw new NotImplementedException();
+
+ protected override void SetTracing(EntityFrameworkCoreCosmosDBSettings options, bool enabled)
+ => options.Tracing = enabled;
+
+ protected override void SetMetrics(EntityFrameworkCoreCosmosDBSettings options, bool enabled)
+ => options.Metrics = enabled;
+
+ protected override string JsonSchemaPath
+ => "src/Components/Aspire.Microsoft.EntityFrameworkCore.Cosmos/ConfigurationSchema.json";
+
+ protected override string ValidJsonConfig => """
+ {
+ "Aspire": {
+ "Microsoft": {
+ "EntityFrameworkCore": {
+ "Cosmos": {
+ "ConnectionString": "YOUR_CONNECTION_STRING",
+ "Tracing": true,
+ "Metrics": true
+ }
+ }
+ }
+ }
+ }
+ """;
+
+ protected override (string json, string error)[] InvalidJsonToErrorMessage => new[]
+ {
+ ("""{"Aspire": { "Microsoft":{ "EntityFrameworkCore": { "Cosmos": { "AccountEndpoint": 3 }}}}}""", "Value is \"integer\" but should be \"string\""),
+ ("""{"Aspire": { "Microsoft":{ "EntityFrameworkCore": { "Cosmos": { "AccountEndpoint": "hello" }}}}}""", "Value does not match format \"uri\""),
+ ("""{"Aspire": { "Microsoft":{ "EntityFrameworkCore": { "Cosmos": { "Region": 3 }}}}}""", "Value is \"integer\" but should be \"string\""),
+ };
+
+ protected override void TriggerActivity(TestDbContext service)
+ {
+ if (service.Database.CanConnect())
+ {
+ service.Database.EnsureCreated();
+ }
+ }
+
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "EF1001:Internal EF Core API usage.", Justification = "Required to verify pooling without touching DB")]
+ public void DbContextPoolingRegistersIDbContextPool(bool enabled)
+ {
+ using IHost host = CreateHostWithComponent(options => options.DbContextPooling = enabled);
+
+ IDbContextPool? pool = host.Services.GetService>();
+
+ Assert.Equal(enabled, pool is not null);
+ }
+
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public void DbContextCanBeAlwaysResolved(bool enabled)
+ {
+ using IHost host = CreateHostWithComponent(options => options.DbContextPooling = enabled);
+
+ TestDbContext? dbContext = host.Services.GetService();
+
+ Assert.NotNull(dbContext);
+ }
+
+ [ConditionalFact]
+ public void TracingEnablesTheRightActivitySource()
+ {
+ SkipIfCanNotConnectToServer();
+
+ RemoteExecutor.Invoke(() => ActivitySourceTest(key: null)).Dispose();
+ }
+}