From ac8c560a2d4421e44187ed6ae14f7a1298abc0e2 Mon Sep 17 00:00:00 2001
From: Eric Erhardt <eric.erhardt@microsoft.com>
Date: Thu, 2 Nov 2023 16:17:51 -0500
Subject: [PATCH] Azure cosmosdb support in aspire (#359) (#669)

* Initial addition of CosmosDB support, based on SqlServer

* Remove Healthchecks support from CosmosDB EF Component

* Cleanup connection string handling in Cosmos EF

* Cleanup connection string handling in Cosmos component

* Update CosmosDB package to get OTel support

* Use the parent name for the connection

* Udpate manifest strings

* Add CosmosDB components to Progress and Telemetry

* Rename CosmosDB components to Aspire.Azure.Data.Cosmos[.EntityFrameworkCore]

* Rename options -> settings

* Rename Cosmos Components to follow naming guidelines

* Update to CosmosDB preview package and pin to get OpenTelemetry support

* Update comments and add Keyed DI to Aspire.Microsoft.Azure.Cosmos

* Add log categories to Cosmos Component schemas

* Add basic support for CosmosClientOptions (no IConfiguration binding yet)

* Remove healthchecks support from CosmosDB Component

* Add README for Aspire.Microsoft.Azure.Cosmos

* Add README for Aspire.Microsoft.EntityFrameworkCore.Cosmos, and rename a couple of things

* Update config schema to be nested for Aspire.Microsoft.EntityFramework.Cosmos and Aspire.Microsoft.Azure.Cosmos

* Rename AzureDataCosmosSettings -> AzureCosmosDBSettings

* Update Aspire_Components_Progress.md

* Add PackageTags, Descriptions, and Icons

* Add AccountEndpoint to ConfigurationScheama.json

* Fix DB context builder config

* Add xml doc comments for CosmosDB hosting methods and types

* Move Cosmos DB hosting to Aspire.Hosting.Azure

* Update manifest type names

* Respond to PR feedback

Co-authored-by: Kevin Pilch <me@pilchie.com>
---
 Aspire.sln                                    |  28 ++++
 Directory.Packages.props                      |   4 +-
 ...smosDBCloudApplicationBuilderExtensions.cs |  60 ++++++++
 .../AzureCosmosDBConnectionResource.cs        |  21 +++
 .../AzureCosmosDatabaseResource.cs            |  29 ++++
 .../Aspire.Microsoft.Azure.Cosmos.csproj      |  20 +++
 .../AspireAzureCosmosDBExtensions.cs          | 126 ++++++++++++++++
 .../AzureCosmosDBSettings.cs                  |  39 +++++
 .../ConfigurationSchema.json                  |  46 ++++++
 .../Aspire.Microsoft.Azure.Cosmos/README.md   | 135 ++++++++++++++++++
 ...icrosoft.EntityFrameworkCore.Cosmos.csproj |  24 ++++
 .../AspireAzureEFCoreCosmosDBExtensions.cs    | 132 +++++++++++++++++
 .../ConfigurationSchema.json                  |  80 +++++++++++
 .../EntityFrameworkCoreCosmosDBSettings.cs    |  54 +++++++
 .../README.md                                 | 113 +++++++++++++++
 src/Components/Aspire_Components_Progress.md  |   4 +-
 src/Components/Telemetry.md                   |  30 ++++
 src/Shared/AzureCosmosDB_256x.png             | Bin 0 -> 20762 bytes
 ...Aspire.Microsoft.Azure.Cosmos.Tests.csproj |  12 ++
 .../ConfigurationTests.cs                     |  17 +++
 .../ConformanceTests.cs                       |  77 ++++++++++
 ...ft.EntityFrameworkCore.Cosmos.Tests.csproj |  17 +++
 ...spireAzureEfCoreCosmosDBExtensionsTests.cs |  51 +++++++
 .../ConformanceTests_NoPooling.cs             |  22 +++
 .../ConformanceTests_Pooling.cs               | 115 +++++++++++++++
 25 files changed, 1254 insertions(+), 2 deletions(-)
 create mode 100644 src/Aspire.Hosting.Azure/AzureCosmosDBCloudApplicationBuilderExtensions.cs
 create mode 100644 src/Aspire.Hosting.Azure/AzureCosmosDBConnectionResource.cs
 create mode 100644 src/Aspire.Hosting.Azure/AzureCosmosDatabaseResource.cs
 create mode 100644 src/Components/Aspire.Microsoft.Azure.Cosmos/Aspire.Microsoft.Azure.Cosmos.csproj
 create mode 100644 src/Components/Aspire.Microsoft.Azure.Cosmos/AspireAzureCosmosDBExtensions.cs
 create mode 100644 src/Components/Aspire.Microsoft.Azure.Cosmos/AzureCosmosDBSettings.cs
 create mode 100644 src/Components/Aspire.Microsoft.Azure.Cosmos/ConfigurationSchema.json
 create mode 100644 src/Components/Aspire.Microsoft.Azure.Cosmos/README.md
 create mode 100644 src/Components/Aspire.Microsoft.EntityFrameworkCore.Cosmos/Aspire.Microsoft.EntityFrameworkCore.Cosmos.csproj
 create mode 100644 src/Components/Aspire.Microsoft.EntityFrameworkCore.Cosmos/AspireAzureEFCoreCosmosDBExtensions.cs
 create mode 100644 src/Components/Aspire.Microsoft.EntityFrameworkCore.Cosmos/ConfigurationSchema.json
 create mode 100644 src/Components/Aspire.Microsoft.EntityFrameworkCore.Cosmos/EntityFrameworkCoreCosmosDBSettings.cs
 create mode 100644 src/Components/Aspire.Microsoft.EntityFrameworkCore.Cosmos/README.md
 create mode 100644 src/Shared/AzureCosmosDB_256x.png
 create mode 100644 tests/Aspire.Microsoft.Azure.Cosmos.Tests/Aspire.Microsoft.Azure.Cosmos.Tests.csproj
 create mode 100644 tests/Aspire.Microsoft.Azure.Cosmos.Tests/ConfigurationTests.cs
 create mode 100644 tests/Aspire.Microsoft.Azure.Cosmos.Tests/ConformanceTests.cs
 create mode 100644 tests/Aspire.Microsoft.EntityFrameworkCore.Cosmos.Tests/Aspire.Microsoft.EntityFrameworkCore.Cosmos.Tests.csproj
 create mode 100644 tests/Aspire.Microsoft.EntityFrameworkCore.Cosmos.Tests/AspireAzureEfCoreCosmosDBExtensionsTests.cs
 create mode 100644 tests/Aspire.Microsoft.EntityFrameworkCore.Cosmos.Tests/ConformanceTests_NoPooling.cs
 create mode 100644 tests/Aspire.Microsoft.EntityFrameworkCore.Cosmos.Tests/ConformanceTests_Pooling.cs

diff --git a/Aspire.sln b/Aspire.sln
index 5647600b1a..f48ad76f5a 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 59dad36f96..a5b99920fb 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -14,6 +14,7 @@
     <PackageVersion Include="Azure.Security.KeyVault.Secrets" Version="4.5.0" />
     <PackageVersion Include="Azure.Storage.Blobs" Version="12.18.0" />
     <PackageVersion Include="Azure.Storage.Queues" Version="12.16.0" />
+    <PackageVersion Include="Microsoft.Azure.Cosmos" Version="3.36.0-preview" />
     <PackageVersion Include="Microsoft.Extensions.Azure" Version="1.7.1" />
     <!-- Azure Management SDK for .NET dependencies -->
     <PackageVersion Include="Azure.ResourceManager.KeyVault" Version="1.2.0-beta.2" />
@@ -44,6 +45,7 @@
     <!-- sql client dependencies -->
     <PackageVersion Include="Microsoft.Data.SqlClient" Version="5.1.1" />
     <!-- efcore dependencies -->
+    <PackageVersion Include="Microsoft.EntityFrameworkCore.Cosmos" Version="$(EfCoreVersion)" />
     <PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="$(EfCoreVersion)" />
     <PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="$(EfCoreVersion)" />
     <PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="$(EfCoreVersion)" />
@@ -100,4 +102,4 @@
     <PackageVersion Include="Microsoft.Signed.Wix" Version="1.0.0-v3.14.0.5722" />
     <PackageVersion Include="Microsoft.DotNet.Build.Tasks.Installers" Version="8.0.0-beta.23371.1" />
   </ItemGroup>
-</Project>
+</Project>
\ 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 0000000000..3d307a2ff7
--- /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;
+
+/// <summary>
+/// Provides extension methods for adding Azure Cosmos DB resources to an <see cref="IDistributedApplicationBuilder"/>.
+/// </summary>
+public static class AzureCosmosDBCloudApplicationBuilderExtensions
+{
+    /// <summary>
+    /// Adds an Azure Cosmos DB connection to the application model.
+    /// </summary>
+    /// <param name="builder">The <see cref="IDistributedApplicationBuilder"/>.</param>
+    /// <param name="name">The name of the resource. This name will be used as the connection string name when referenced in a dependency.</param>
+    /// <param name="connectionString">The connection string.</param>
+    /// <returns>A reference to the <see cref="IResourceBuilder{AzureCosmosDatabaseResource}"/>.</returns>
+    public static IResourceBuilder<AzureCosmosDBConnectionResource> 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);
+    }
+
+    /// <summary>
+    /// Adds an Azure Cosmos DB database to a <see cref="IResourceBuilder{AzureCosmosDatabaseResource}"/>.
+    /// </summary>
+    /// <param name="builder">The <see cref="IDistributedApplicationBuilder"/>.</param>
+    /// <param name="name">The name of the resource. This name will be used as the connection string name when referenced in a dependency.</param>
+    /// <returns>A reference to the <see cref="IResourceBuilder{AzureCosmosDatabaseResource}"/>.</returns>
+    public static IResourceBuilder<AzureCosmosDatabaseResource> AddDatabase(this IResourceBuilder<AzureCosmosDBConnectionResource> 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 0000000000..53b1f2381e
--- /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;
+
+/// <summary>
+/// Represents a connection to an Azure Cosmos DB account.
+/// </summary>
+/// <param name="name">The resource name.</param>
+/// <param name="connectionString">The connection string to use to connect.</param>
+public class AzureCosmosDBConnectionResource(string name, string? connectionString)
+    : Resource(name), IResourceWithConnectionString
+{
+    /// <summary>
+    /// Gets the connection string to use for this database.
+    /// </summary>
+    /// <returns>The connection string to use for this database.</returns>
+    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 0000000000..96cc0feb4b
--- /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;
+
+/// <summary>
+/// Represents an Azure Cosmos DB database.
+/// </summary>
+/// <param name="name">The database name.</param>
+/// <param name="parent">The parent <see cref="AzureCosmosDBConnectionResource"/>.</param>
+public class AzureCosmosDatabaseResource(string name, AzureCosmosDBConnectionResource parent)
+    : Resource(name), IResourceWithParent<AzureCosmosDBConnectionResource>, IResourceWithConnectionString
+{
+    /// <summary>
+    /// Gets the parent <see cref="AzureCosmosDBConnectionResource"/>.
+    /// </summary>
+    public AzureCosmosDBConnectionResource Parent { get; } = parent;
+
+    /// <summary>
+    /// Gets the connection string to use for this database.
+    /// </summary>
+    /// <returns>The connection string to use for this database.</returns>
+    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 0000000000..e41dd94948
--- /dev/null
+++ b/src/Components/Aspire.Microsoft.Azure.Cosmos/Aspire.Microsoft.Azure.Cosmos.csproj
@@ -0,0 +1,20 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>$(NetCurrent)</TargetFramework>
+    <IsPackable>true</IsPackable>
+    <EnableConfigurationBindingGenerator>false</EnableConfigurationBindingGenerator>
+    <IsAotCompatible>false</IsAotCompatible>
+    <PackageTags>$(ComponentAzurePackageTags) cosmos cosmosdb data database db</PackageTags>
+    <Description>A client for Azure Cosmos DB that integrates with Aspire, including logging and telemetry.</Description>
+    <PackageIconFullPath>$(SharedDir)AzureCosmosDB_256x.png</PackageIconFullPath>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Azure.Identity" />
+    <PackageReference Include="Microsoft.Azure.Cosmos" />
+    <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" />
+    <PackageReference Include="OpenTelemetry.Extensions.Hosting" />
+  </ItemGroup>
+
+</Project>
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 0000000000..cfe43cbc4e
--- /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;
+
+/// <summary>
+/// Azure CosmosDB extension
+/// </summary>
+public static class AspireAzureCosmosDBExtensions
+{
+    private const string DefaultConfigSectionName = "Aspire:Microsoft:Azure:Cosmos";
+
+    /// <summary>
+    /// Registers <see cref="CosmosClient" /> as a singleton in the services provided by the <paramref name="builder"/>.
+    /// Configures logging and telemetry for the <see cref="CosmosClient" />.
+    /// </summary>
+    /// <param name="builder">The <see cref="IHostApplicationBuilder" /> to read config from and add services to.</param>
+    /// <param name="connectionName">The connection name to use to find a connection string.</param>
+    /// <param name="configureSettings">An optional method that can be used for customizing the <see cref="AzureCosmosDBSettings"/>. It's invoked after the settings are read from the configuration.</param>
+    /// <param name="configureClientOptions">An optional method that can be used for customizing the <see cref="CosmosClientOptions"/>.</param>
+    /// <remarks>Reads the configuration from "Aspire:Microsoft:Azure:Cosmos" section.</remarks>
+    /// <exception cref="InvalidOperationException">If required ConnectionString is not provided in configuration section</exception>
+    public static void AddAzureCosmosDB(
+        this IHostApplicationBuilder builder,
+        string connectionName,
+        Action<AzureCosmosDBSettings>? configureSettings = null,
+        Action<CosmosClientOptions>? configureClientOptions = null)
+    {
+        AddAzureCosmosDB(builder, DefaultConfigSectionName, configureSettings, configureClientOptions, connectionName, serviceKey: null);
+    }
+
+    /// <summary>
+    /// Registers <see cref="CosmosClient" /> as a singleton for given <paramref name="name" /> in the services provided by the <paramref name="builder"/>.
+    /// Configures logging and telemetry for the <see cref="CosmosClient" />.
+    /// </summary>
+    /// <param name="builder">The <see cref="IHostApplicationBuilder" /> to read config from and add services to.</param>
+    /// <param name="name">The name of the component, which is used as the <see cref="ServiceDescriptor.ServiceKey"/> of the service and also to retrieve the connection string from the ConnectionStrings configuration section.</param>
+    /// <param name="configureSettings">An optional method that can be used for customizing the <see cref="AzureCosmosDBSettings"/>. It's invoked after the settings are read from the configuration.</param>
+    /// <param name="configureClientOptions">An optional method that can be used for customizing the <see cref="CosmosClientOptions"/>.</param>
+    /// <remarks>Reads the configuration from "Aspire:Microsoft:Azure:Cosmos:{name}" section.</remarks>
+    /// <exception cref="InvalidOperationException">If required ConnectionString is not provided in configuration section</exception>
+    public static void AddKeyedAzureCosmosDB(
+        this IHostApplicationBuilder builder,
+        string name,
+        Action<AzureCosmosDBSettings>? configureSettings = null,
+        Action<CosmosClientOptions>? configureClientOptions = null)
+    {
+        AddAzureCosmosDB(builder, $"{DefaultConfigSectionName}:{name}", configureSettings, configureClientOptions, connectionName: name, serviceKey: name);
+    }
+
+    private static void AddAzureCosmosDB(
+        this IHostApplicationBuilder builder,
+        string configurationSectionName,
+        Action<AzureCosmosDBSettings>? configureSettings,
+        Action<CosmosClientOptions>? 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 0000000000..98b9aaae60
--- /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;
+
+/// <summary>
+/// The settings relevant to accessing Azure Cosmos DB.
+/// </summary>
+public sealed class AzureCosmosDBSettings
+{
+    /// <summary>
+    /// Gets or sets the connection string of the Azure Cosmos database to connect to.
+    /// </summary>
+    public string? ConnectionString { get; set; }
+
+    /// <summary>
+    /// <para>Gets or sets a boolean value that indicates whether the OpenTelemetry tracing is enabled or not.</para>
+    /// <para>Enabled by default.</para>
+    /// </summary>
+    public bool Tracing { get; set; } = true;
+
+    /// <summary>
+    /// A <see cref="Uri"/> referencing the Azure Cosmos DB Endpoint.
+    /// This is likely to be similar to "https://{account_name}.queue.core.windows.net".
+    /// </summary>
+    /// <remarks>
+    /// Must not contain shared access signature.
+    /// Used along with <see cref="Credential"/> to establish the connection.
+    /// </remarks>
+    public Uri? AccountEndpoint { get; set; }
+
+    /// <summary>
+    /// Gets or sets the credential used to authenticate to the Azure Cosmos DB endpoint.
+    /// </summary>
+    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 0000000000..9ba4f921b4
--- /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 0000000000..2aeefdbafc
--- /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<AzureCosmosDBSettings> 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<CosmosClientOptions> 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<Projects.MyService>()
+                       .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 0000000000..913d7e6027
--- /dev/null
+++ b/src/Components/Aspire.Microsoft.EntityFrameworkCore.Cosmos/Aspire.Microsoft.EntityFrameworkCore.Cosmos.csproj
@@ -0,0 +1,24 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>$(NetCurrent)</TargetFramework>
+    <IsPackable>true</IsPackable>
+    <EnableConfigurationBindingGenerator>false</EnableConfigurationBindingGenerator>
+    <IsAotCompatible>false</IsAotCompatible>
+    <PackageTags>$(ComponentEfCorePackageTags) azure cosmos cosmosdb </PackageTags>
+    <Description>A Microsoft Azure Cosmos DB provider for Entity Framework Core that integrates with Aspire, including connection pooling, logging, and telemetry.</Description>
+    <PackageIconFullPath>$(SharedDir)AzureCosmosDB_256x.png</PackageIconFullPath>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Azure.Identity" />
+    <PackageReference Include="Microsoft.Azure.Cosmos" />
+    <PackageReference Include="Microsoft.EntityFrameworkCore.Cosmos" />
+    <PackageReference Include="Microsoft.Extensions.Configuration.Binder" />
+    <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" />
+    <PackageReference Include="OpenTelemetry.Extensions.Hosting" />
+    <PackageReference Include="OpenTelemetry.Instrumentation.EntityFrameworkCore" />
+    <PackageReference Include="OpenTelemetry.Instrumentation.EventCounters" />
+  </ItemGroup>
+
+</Project>
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 0000000000..fa518a0a41
--- /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;
+
+/// <summary>
+/// Extension methods for configuring EntityFrameworkCore DbContext to Azure Cosmos DB
+/// </summary>
+public static class AspireAzureEFCoreCosmosDBExtensions
+{
+    private const string DefaultConfigSectionName = "Aspire:Microsoft:EntityFrameworkCore:Cosmos";
+    private const DynamicallyAccessedMemberTypes RequiredByEF = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties;
+
+    /// <summary>
+    /// Registers the given <see cref="DbContext" /> as a service in the services provided by the <paramref name="builder"/>.
+    /// Configures the connection pooling, logging and telemetry for the <see cref="DbContext" />.
+    /// </summary>
+    /// <typeparam name="TContext">The <see cref="DbContext" /> that needs to be registered.</typeparam>
+    /// <param name="builder">The <see cref="IHostApplicationBuilder" /> to read config from and add services to.</param>
+    /// <param name="connectionName">A name used to retrieve the connection string from the ConnectionStrings configuration section.</param>
+    /// <param name="databaseName">The name of the database to use within the Azure Cosmos DB account.</param>
+    /// <param name="configureSettings">An optional delegate that can be used for customizing settings. It's invoked after the settings are read from the configuration.</param>
+    /// <param name="configureDbContextOptions">An optional delegate to configure the <see cref="DbContextOptions"/> for the context.</param>
+    /// <exception cref="ArgumentNullException">Thrown if mandatory <paramref name="builder"/> is null.</exception>
+    /// <exception cref="InvalidOperationException">Thrown when mandatory <see cref="EntityFrameworkCoreCosmosDBSettings.ConnectionString"/> is not provided.</exception>
+    public static void AddCosmosDbContext<[DynamicallyAccessedMembers(RequiredByEF)] TContext>(
+        this IHostApplicationBuilder builder,
+        string connectionName,
+        string databaseName,
+        Action<EntityFrameworkCoreCosmosDBSettings>? configureSettings = null,
+        Action<DbContextOptionsBuilder>? 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<TContext>(ConfigureDbContext);
+        }
+        else
+        {
+            builder.Services.AddDbContext<TContext>(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 0000000000..40dc2ae5b2
--- /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 0000000000..320f595ccf
--- /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;
+
+/// <summary>
+/// The settings relevant to accessing Azure Cosmos DB database using EntityFrameworkCore.
+/// </summary>
+public sealed class EntityFrameworkCoreCosmosDBSettings
+{
+    /// <summary>
+    /// The connection string of the Azure Cosmos DB server database to connect to.
+    /// </summary>
+    public string? ConnectionString { get; set; }
+
+    /// <summary>
+    /// A <see cref="Uri"/> referencing the Azure Cosmos DB Endpoint.
+    /// This is likely to be similar to "https://{account_name}.queue.core.windows.net".
+    /// </summary>
+    /// <remarks>
+    /// Must not contain shared access signature.
+    /// Used along with <see cref="Credential"/> to establish the connection.
+    /// </remarks>
+    public Uri? AccountEndpoint { get; set; }
+
+    /// <summary>
+    /// Gets or sets the credential used to authenticate to the Azure Cosmos DB endpoint.
+    /// </summary>
+    public TokenCredential? Credential { get; set; }
+
+    /// <summary>
+    /// Gets or sets a boolean value that indicates whether the db context will be pooled or explicitly created every time it's requested.
+    /// </summary>
+    public bool DbContextPooling { get; set; } = true;
+
+    /// <summary>
+    /// <para>Gets or sets a boolean value that indicates whether the Open Telemetry tracing is enabled or not.</para>
+    /// <para>Enabled by default.</para>
+    /// </summary>
+    public bool Tracing { get; set; } = true;
+
+    /// <summary>
+    /// <para>Gets or sets a boolean value that indicates whether the Open Telemetry metrics are enabled or not.</para>
+    /// <para>Enabled by default.</para>
+    /// </summary>
+    public bool Metrics { get; set; } = true;
+
+    /// <summary>
+    /// Gets or sets a string value that indicates what Azure region this client will run in.
+    /// </summary>
+    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 0000000000..f1d90dbaad
--- /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<MyDbContext>("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<MyDbContext>("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<EntityFrameworkCoreCosmosDBSettings> configureSettings` delegate to set up some or all the options inline, for example to disable tracing from code:
+
+```csharp
+    builder.AddCosmosDbContext<MyDbContext>("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<Projects.MyService>()
+                       .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<MyDbContext>("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 20dbc4a81b..38c7fc20aa 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 d3bd8a8219..715f8d8a9f 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 0000000000000000000000000000000000000000..ad2850532c66c8eba547f3509ecf14607df2b593
GIT binary patch
literal 20762
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
LdZJvTWEuHC>9Ku0

literal 0
HcmV?d00001

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 0000000000..07a7d23049
--- /dev/null
+++ b/tests/Aspire.Microsoft.Azure.Cosmos.Tests/Aspire.Microsoft.Azure.Cosmos.Tests.csproj
@@ -0,0 +1,12 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>$(NetCurrent)</TargetFramework>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\..\src\Components\Aspire.Microsoft.Azure.Cosmos\Aspire.Microsoft.Azure.Cosmos.csproj" />
+    <ProjectReference Include="..\Aspire.Components.Common.Tests\Aspire.Components.Common.Tests.csproj" />
+  </ItemGroup>
+
+</Project>
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 0000000000..df3284ebf7
--- /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 0000000000..4ff5fa82f3
--- /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<CosmosClient, AzureCosmosDBSettings>
+{
+    protected override ServiceLifetime ServiceLifetime => ServiceLifetime.Singleton;
+
+    protected override string ActivitySourceName => "Azure.Cosmos.Operation";
+
+    protected override string[] RequiredLogCategories => Array.Empty<string>();
+
+    protected override void PopulateConfiguration(ConfigurationManager configuration, string? key = null)
+        => configuration.AddInMemoryCollection(new KeyValuePair<string, string?>[1]
+        {
+            new KeyValuePair<string, string?>(CreateConfigKey("Aspire:Microsoft:Azure:Cosmos", key, "ConnectionString"),
+                "AccountEndpoint=https://example.documents.azure.com:443/;AccountKey=fake;")
+        });
+
+    protected override void RegisterComponent(HostApplicationBuilder builder, Action<AzureCosmosDBSettings>? 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 0000000000..6ef388b18b
--- /dev/null
+++ b/tests/Aspire.Microsoft.EntityFrameworkCore.Cosmos.Tests/Aspire.Microsoft.EntityFrameworkCore.Cosmos.Tests.csproj
@@ -0,0 +1,17 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>$(NetCurrent)</TargetFramework>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <!-- this file is included as a link, as both components rely on different versions of EF (for now) -->
+    <Compile Include="..\Aspire.Npgsql.EntityFrameworkCore.PostgreSQL.Tests\TestDbContext.cs" Link="TestDbContext.cs" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\..\src\Components\Aspire.Microsoft.EntityFrameworkCore.Cosmos\Aspire.Microsoft.EntityFrameworkCore.Cosmos.csproj" />
+    <ProjectReference Include="..\Aspire.Components.Common.Tests\Aspire.Components.Common.Tests.csproj" />
+  </ItemGroup>
+
+</Project>
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 0000000000..d462195c43
--- /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=<fake-key>;";
+
+    [Fact]
+    public void CanConfigureDbContextOptions()
+    {
+        var builder = Host.CreateEmptyApplicationBuilder(null);
+        builder.Configuration.AddInMemoryCollection([
+            new KeyValuePair<string, string?>("ConnectionStrings:cosmosConnection", ConnectionString),
+            new KeyValuePair<string, string?>("Aspire:Microsoft:EntityFrameworkCore:Cosmos:Region", "westus"),
+        ]);
+
+        builder.AddCosmosDbContext<TestDbContext>("cosmosConnection", "databaseName", configureDbContextOptions: optionsBuilder =>
+        {
+            optionsBuilder.UseCosmos(ConnectionString, "databaseName", cosmosBuilder =>
+            {
+                cosmosBuilder.RequestTimeout(TimeSpan.FromSeconds(608));
+            });
+        });
+
+        var host = builder.Build();
+        var context = host.Services.GetRequiredService<TestDbContext>();
+
+#pragma warning disable EF1001 // Internal EF Core API usage.
+
+        var extension = context.Options.FindExtension<CosmosOptionsExtension>();
+        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 0000000000..374c8117a2
--- /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<EntityFrameworkCoreCosmosDBSettings>? configure = null, string? key = null)
+    {
+        builder.AddCosmosDbContext<TestDbContext>("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 0000000000..1ac9540ad6
--- /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<TestDbContext, EntityFrameworkCoreCosmosDBSettings>
+{
+    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<string, string?>[]
+        {
+            new KeyValuePair<string, string?>("Aspire:Microsoft:EntityFrameworkCore:Cosmos:ConnectionString",
+                "Host=fake;Database=catalog"),
+        });
+
+    protected override void RegisterComponent(HostApplicationBuilder builder, Action<EntityFrameworkCoreCosmosDBSettings>? configure = null, string? key = null)
+        => builder.AddCosmosDbContext<TestDbContext>("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<TestDbContext>? pool = host.Services.GetService<IDbContextPool<TestDbContext>>();
+
+        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<TestDbContext>();
+
+        Assert.NotNull(dbContext);
+    }
+
+    [ConditionalFact]
+    public void TracingEnablesTheRightActivitySource()
+    {
+        SkipIfCanNotConnectToServer();
+
+        RemoteExecutor.Invoke(() => ActivitySourceTest(key: null)).Dispose();
+    }
+}