diff --git a/CODEOWNERS b/CODEOWNERS
index 7c7b6ee9..4c1260f6 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -69,6 +69,15 @@
/src/CommunityToolkit.Aspire.Hosting.Dapr**/ @FullStackChef @WhitWaldo @Paule96
/tests/CommunityToolkit.Aspire.Hosting.Dapr**.Tests/ @FullStackChef @WhitWaldo @Paule96
+# CommunityToolkit.Aspire.RavenDB.Client
+# CommunityToolkit.Aspire.Hosting.RavenDB
+
+/examples/ravendb/ @shiranshalom
+/src/CommunityToolkit.Aspire.RavenDB.Client/ @shiranshalom
+/tests/CommunityToolkit.Aspire.RavenDB.Client.Tests/ @shiranshalom
+/src/CommunityToolkit.Aspire.Hosting.RavenDB/ @shiranshalom
+/tests/CommunityToolkit.Aspire.Hosting.RavenDB.Tests/ @shiranshalom
+
# CommunityToolkit.Aspire.Hosting.PostgreSQL.Extensions
/src/CommunityToolkit.Aspire.Hosting.PostgreSQL.Extensions/ @Alirexaa
diff --git a/CommunityToolkit.Aspire.sln b/CommunityToolkit.Aspire.sln
index ac787e54..6b2c05c3 100644
--- a/CommunityToolkit.Aspire.sln
+++ b/CommunityToolkit.Aspire.sln
@@ -265,6 +265,21 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommunityToolkit.Aspire.Hos
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommunityToolkit.Aspire.Hosting.Dapr.ServiceDefaults", "examples\dapr\CommunityToolkit.Aspire.Hosting.Dapr.ServiceDefaults\CommunityToolkit.Aspire.Hosting.Dapr.ServiceDefaults.csproj", "{99441705-4BFA-499F-9897-371238665E38}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Aspire.RavenDB.Client", "src\CommunityToolkit.Aspire.RavenDB.Client\CommunityToolkit.Aspire.RavenDB.Client.csproj", "{11768120-E86C-4464-A68A-6F9BD0999BB9}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Aspire.RavenDB.Client.Tests", "tests\CommunityToolkit.Aspire.RavenDB.Client.Tests\CommunityToolkit.Aspire.RavenDB.Client.Tests.csproj", "{BA416CE7-0C29-4FBA-B31A-327A7ECB56C9}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Aspire.Hosting.RavenDB", "src\CommunityToolkit.Aspire.Hosting.RavenDB\CommunityToolkit.Aspire.Hosting.RavenDB.csproj", "{A7852D8B-BE38-4D95-A8D6-3B6F96F94A5A}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Aspire.Hosting.RavenDB.Tests", "tests\CommunityToolkit.Aspire.Hosting.RavenDB.Tests\CommunityToolkit.Aspire.Hosting.RavenDB.Tests.csproj", "{35B51242-F576-4CCE-BA29-712A80749CB1}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ravendb", "ravendb", "{1C65F967-D5F1-424B-82E9-B8585B6F0BD6}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommunityToolkit.Aspire.Hosting.RavenDB.AppHost", "examples\ravendb\RavenDB.AppHost\CommunityToolkit.Aspire.Hosting.RavenDB.AppHost.csproj", "{28FCB1E2-7460-4FDD-AA63-03162C9F2154}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommunityToolkit.Aspire.Hosting.RavenDB.ServiceDefaults", "examples\ravendb\CommunityToolkit.Aspire.Hosting.RavenDB.ServiceDefaults\CommunityToolkit.Aspire.Hosting.RavenDB.ServiceDefaults.csproj", "{3D076CFF-6482-4126-9F29-C7617E7D2F5B}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommunityToolkit.Aspire.Hosting.RavenDB.ApiService", "examples\ravendb\CommunityToolkit.Aspire.Hosting.RavenDB.ApiService\CommunityToolkit.Aspire.Hosting.RavenDB.ApiService.csproj", "{D214CBF5-D5E4-4641-868D-66B0C5337DD5}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommunityToolkit.Aspire.Hosting.DbGate.Tests", "tests\CommunityToolkit.Aspire.Hosting.DbGate.Tests\CommunityToolkit.Aspire.Hosting.DbGate.Tests.csproj", "{BDAF7D27-C600-4419-9782-CF15BA5272E9}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "postgres-ext", "postgres-ext", "{204BB8D8-04E3-4FE5-BB08-E793BF532F2F}"
@@ -721,6 +736,34 @@ Global
{99441705-4BFA-499F-9897-371238665E38}.Debug|Any CPU.Build.0 = Debug|Any CPU
{99441705-4BFA-499F-9897-371238665E38}.Release|Any CPU.ActiveCfg = Release|Any CPU
{99441705-4BFA-499F-9897-371238665E38}.Release|Any CPU.Build.0 = Release|Any CPU
+ {11768120-E86C-4464-A68A-6F9BD0999BB9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {11768120-E86C-4464-A68A-6F9BD0999BB9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {11768120-E86C-4464-A68A-6F9BD0999BB9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {11768120-E86C-4464-A68A-6F9BD0999BB9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {BA416CE7-0C29-4FBA-B31A-327A7ECB56C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {BA416CE7-0C29-4FBA-B31A-327A7ECB56C9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {BA416CE7-0C29-4FBA-B31A-327A7ECB56C9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {BA416CE7-0C29-4FBA-B31A-327A7ECB56C9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A7852D8B-BE38-4D95-A8D6-3B6F96F94A5A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A7852D8B-BE38-4D95-A8D6-3B6F96F94A5A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A7852D8B-BE38-4D95-A8D6-3B6F96F94A5A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A7852D8B-BE38-4D95-A8D6-3B6F96F94A5A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {35B51242-F576-4CCE-BA29-712A80749CB1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {35B51242-F576-4CCE-BA29-712A80749CB1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {35B51242-F576-4CCE-BA29-712A80749CB1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {35B51242-F576-4CCE-BA29-712A80749CB1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {28FCB1E2-7460-4FDD-AA63-03162C9F2154}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {28FCB1E2-7460-4FDD-AA63-03162C9F2154}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {28FCB1E2-7460-4FDD-AA63-03162C9F2154}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {28FCB1E2-7460-4FDD-AA63-03162C9F2154}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3D076CFF-6482-4126-9F29-C7617E7D2F5B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3D076CFF-6482-4126-9F29-C7617E7D2F5B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3D076CFF-6482-4126-9F29-C7617E7D2F5B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3D076CFF-6482-4126-9F29-C7617E7D2F5B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D214CBF5-D5E4-4641-868D-66B0C5337DD5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D214CBF5-D5E4-4641-868D-66B0C5337DD5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D214CBF5-D5E4-4641-868D-66B0C5337DD5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D214CBF5-D5E4-4641-868D-66B0C5337DD5}.Release|Any CPU.Build.0 = Release|Any CPU
{BDAF7D27-C600-4419-9782-CF15BA5272E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BDAF7D27-C600-4419-9782-CF15BA5272E9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BDAF7D27-C600-4419-9782-CF15BA5272E9}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -890,6 +933,14 @@ Global
{D2DDEA96-4A7E-496B-AFBE-69A133156C5F} = {E3C2B4B7-B3B0-4E7F-A975-A6C7FD926792}
{5ADBE907-7E0B-4AD7-9073-C032C4183914} = {E3C2B4B7-B3B0-4E7F-A975-A6C7FD926792}
{99441705-4BFA-499F-9897-371238665E38} = {E3C2B4B7-B3B0-4E7F-A975-A6C7FD926792}
+ {11768120-E86C-4464-A68A-6F9BD0999BB9} = {414151D4-7009-4E78-A5C6-D99EBD1E67D1}
+ {BA416CE7-0C29-4FBA-B31A-327A7ECB56C9} = {899F0713-7FC6-4750-BAFC-AC650B35B453}
+ {A7852D8B-BE38-4D95-A8D6-3B6F96F94A5A} = {414151D4-7009-4E78-A5C6-D99EBD1E67D1}
+ {35B51242-F576-4CCE-BA29-712A80749CB1} = {899F0713-7FC6-4750-BAFC-AC650B35B453}
+ {1C65F967-D5F1-424B-82E9-B8585B6F0BD6} = {8519CC01-1370-47C8-AD94-B0F326B1563F}
+ {28FCB1E2-7460-4FDD-AA63-03162C9F2154} = {1C65F967-D5F1-424B-82E9-B8585B6F0BD6}
+ {3D076CFF-6482-4126-9F29-C7617E7D2F5B} = {1C65F967-D5F1-424B-82E9-B8585B6F0BD6}
+ {D214CBF5-D5E4-4641-868D-66B0C5337DD5} = {1C65F967-D5F1-424B-82E9-B8585B6F0BD6}
{BDAF7D27-C600-4419-9782-CF15BA5272E9} = {899F0713-7FC6-4750-BAFC-AC650B35B453}
{204BB8D8-04E3-4FE5-BB08-E793BF532F2F} = {8519CC01-1370-47C8-AD94-B0F326B1563F}
{356853EE-2C47-429C-B6CF-F3F76B6FFD91} = {899F0713-7FC6-4750-BAFC-AC650B35B453}
diff --git a/Directory.Packages.props b/Directory.Packages.props
index f440a813..fab4ba51 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -69,6 +69,9 @@
+
+
+
diff --git a/README.md b/README.md
index 34421955..1ab168f8 100644
--- a/README.md
+++ b/README.md
@@ -36,6 +36,8 @@ This repository contains the source code for the .NET Aspire Community Toolkit,
| - **Learn More**: [`Microsoft.EntityFrameworkCore.Sqlite`][sqlite-ef-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.Microsoft.EntityFrameworkCore.Sqlite][sqlite-ef-shields]][sqlite-ef-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Microsoft.EntityFrameworkCore.Sqlite][sqlite-ef-shields-preview]][sqlite-ef-nuget-preview] | An Aspire client integration for the Microsoft.EntityFrameworkCore.Sqlite NuGet package. |
| - **Learn More**: [`Hosting.Dapr`][dapr-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.Hosting.Dapr][dapr-shields]][dapr-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.Dapr][dapr-shields-preview]][dapr-nuget-preview] | An Aspire hosting integration for Dapr. |
| - **Learn More**: [`Hosting.Dapr.AzureRedis`][dapr-azureredis-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.Hosting.Dapr.AzureRedis][dapr-azureredis-shields]][dapr-azureredis-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.Dapr.AzureRedis][dapr-azureredis-shields-preview]][dapr-azureredis-nuget-preview] | An extension for the Dapr hosting integration for using Dapr with Azure Redis cache. |
+| - **Learn More**: [`Hosting.RavenDB`][ravendb-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.Hosting.RavenDB][ravendb-shields]][ravendb-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.RavenDB][ravendb-shields-preview]][ravendb-nuget-preview] | An Aspire integration leveraging the [RavenDB](https://ravendb.net/) container. |
+| - **Learn More**: [`RavenDB.Client`][ravendb-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.RavenDB.Client][ravendb-client-shields]][ravendb-client-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.RavenDB.Client][ravendb-client-shields-preview]][ravendb-client-nuget-preview] | An Aspire client integration for the [RavenDB.Client](https://www.nuget.org/packages/RavenDB.client) package. |
| - **Learn More**: [`Hosting.GoFeatureFlag`][go-feature-flag-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.Hosting.GoFeatureFlag][go-feature-flag-shields]][go-feature-flag-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.Hosting.GoFeatureFlag][go-feature-flag-shields-preview]][go-feature-flag-nuget-preview] | An Aspire hosting integration leveraging the [GoFeatureFlag](https://gofeatureflag.org/) container. |
| - **Learn More**: [`GoFeatureFlag`][go-feature-flag-integration-docs]
- Stable 📦: [![CommunityToolkit.Aspire.GoFeatureFlag][go-feature-flag-client-shields]][go-feature-flag-client-nuget]
- Preview 📦: [![CommunityToolkit.Aspire.GoFeatureFlag][go-feature-flag-client-shields-preview]][go-feature-flag-client-nuget-preview] | An Aspire client integration for the [GoFeatureFlag](https://github.com/open-feature/dotnet-sdk-contrib/tree/main/src/OpenFeature.Contrib.Providers.GOFeatureFlag) package. |
@@ -184,6 +186,15 @@ This project is supported by the [.NET Foundation](https://dotnetfoundation.org)
[dapr-azureredis-nuget]: https://nuget.org/packages/CommunityToolkit.Aspire.Hosting.Dapr.AzureRedis/
[dapr-azureredis-shields-preview]: https://img.shields.io/nuget/vpre/CommunityToolkit.Aspire.Hosting.Dapr.AzureRedis?label=nuget%20(preview)
[dapr-azureredis-nuget-preview]: https://nuget.org/packages/CommunityToolkit.Aspire.Hosting.Dapr.AzureRedis/absoluteLatest
+[ravendb-integration-docs]: https://learn.microsoft.com/dotnet/aspire/community-toolkit/ravendb
+[ravendb-shields]: https://img.shields.io/nuget/v/CommunityToolkit.Aspire.Hosting.RavenDB
+[ravendb-nuget]: https://nuget.org/packages/CommunityToolkit.Aspire.Hosting.RavenDB/
+[ravendb-shields-preview]: https://img.shields.io/nuget/vpre/CommunityToolkit.Aspire.Hosting.RavenDB?label=nuget%20(preview)
+[ravendb-nuget-preview]: https://nuget.org/packages/CommunityToolkit.Aspire.Hosting.RavenDB/absoluteLatest
+[ravendb-client-shields]: https://img.shields.io/nuget/v/CommunityToolkit.Aspire.RavenDB.Client
+[ravendb-client-nuget]: https://nuget.org/packages/CommunityToolkit.Aspire.RavenDB.Client/
+[ravendb-client-shields-preview]: https://img.shields.io/nuget/vpre/CommunityToolkit.Aspire.RavenDB.Client?label=nuget%20(preview)
+[ravendb-client-nuget-preview]: https://nuget.org/packages/CommunityToolkit.Aspire.RavenDB.Client/absoluteLatest
[go-feature-flag-integration-docs]: https://learn.microsoft.com/dotnet/aspire/community-toolkit/hosting-go-feature-flag
[go-feature-flag-shields]: https://img.shields.io/nuget/v/CommunityToolkit.Aspire.Hosting.GoFeatureFlag
[go-feature-flag-nuget]: https://nuget.org/packages/CommunityToolkit.Aspire.Hosting.GoFeatureFlag/
diff --git a/examples/ravendb/CommunityToolkit.Aspire.Hosting.RavenDB.ApiService/CommunityToolkit.Aspire.Hosting.RavenDB.ApiService.csproj b/examples/ravendb/CommunityToolkit.Aspire.Hosting.RavenDB.ApiService/CommunityToolkit.Aspire.Hosting.RavenDB.ApiService.csproj
new file mode 100644
index 00000000..e1650205
--- /dev/null
+++ b/examples/ravendb/CommunityToolkit.Aspire.Hosting.RavenDB.ApiService/CommunityToolkit.Aspire.Hosting.RavenDB.ApiService.csproj
@@ -0,0 +1,13 @@
+
+
+
+ enable
+ enable
+
+
+
+
+
+
+
+
diff --git a/examples/ravendb/CommunityToolkit.Aspire.Hosting.RavenDB.ApiService/Program.cs b/examples/ravendb/CommunityToolkit.Aspire.Hosting.RavenDB.ApiService/Program.cs
new file mode 100644
index 00000000..3827cde5
--- /dev/null
+++ b/examples/ravendb/CommunityToolkit.Aspire.Hosting.RavenDB.ApiService/Program.cs
@@ -0,0 +1,46 @@
+using Raven.Client.Documents;
+
+var builder = WebApplication.CreateBuilder(args);
+
+builder.AddServiceDefaults();
+builder.AddRavenDBClient(connectionName: "ravendb", configureSettings: settings =>
+{
+ settings.CreateDatabase = true;
+ settings.DatabaseName = "ravenDatabase";
+});
+
+var app = builder.Build();
+
+app.MapGet("/create", async (IDocumentStore documentStore) =>
+{
+ using var session = documentStore.OpenAsyncSession();
+ var company = new Company
+ {
+ Name = "RavenDB",
+ Phone = "(26) 642-7012",
+ Fax = "(26) 642-7012"
+ };
+
+ await session.StoreAsync(company, "companies/ravendb");
+ await session.SaveChangesAsync();
+});
+
+app.MapGet("/get", async (IDocumentStore documentStore) =>
+{
+ using var session = documentStore.OpenAsyncSession();
+ var company = await session.LoadAsync("companies/ravendb");
+ return company;
+});
+
+app.MapDefaultEndpoints();
+
+app.Run();
+
+
+public class Company
+{
+ public string? Id { get; set; }
+ public string? Name { get; set; }
+ public string? Phone { get; set; }
+ public string? Fax { get; set; }
+}
diff --git a/examples/ravendb/CommunityToolkit.Aspire.Hosting.RavenDB.ApiService/Properties/launchSettings.json b/examples/ravendb/CommunityToolkit.Aspire.Hosting.RavenDB.ApiService/Properties/launchSettings.json
new file mode 100644
index 00000000..4528202f
--- /dev/null
+++ b/examples/ravendb/CommunityToolkit.Aspire.Hosting.RavenDB.ApiService/Properties/launchSettings.json
@@ -0,0 +1,12 @@
+{
+ "profiles": {
+ "CommunityToolkit.Aspire.Hosting.RavenDB.ApiService": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "applicationUrl": "http://localhost:54649"
+ }
+ }
+}
\ No newline at end of file
diff --git a/examples/ravendb/CommunityToolkit.Aspire.Hosting.RavenDB.ApiService/RavenDB.ApiService.http b/examples/ravendb/CommunityToolkit.Aspire.Hosting.RavenDB.ApiService/RavenDB.ApiService.http
new file mode 100644
index 00000000..e04f558d
--- /dev/null
+++ b/examples/ravendb/CommunityToolkit.Aspire.Hosting.RavenDB.ApiService/RavenDB.ApiService.http
@@ -0,0 +1,6 @@
+@ApiService_HostAddress = http://localhost:54649
+
+GET {{ApiService_HostAddress}}/
+Accept: application/json
+
+###
diff --git a/examples/ravendb/CommunityToolkit.Aspire.Hosting.RavenDB.ServiceDefaults/CommunityToolkit.Aspire.Hosting.RavenDB.ServiceDefaults.csproj b/examples/ravendb/CommunityToolkit.Aspire.Hosting.RavenDB.ServiceDefaults/CommunityToolkit.Aspire.Hosting.RavenDB.ServiceDefaults.csproj
new file mode 100644
index 00000000..c9a4399a
--- /dev/null
+++ b/examples/ravendb/CommunityToolkit.Aspire.Hosting.RavenDB.ServiceDefaults/CommunityToolkit.Aspire.Hosting.RavenDB.ServiceDefaults.csproj
@@ -0,0 +1,21 @@
+
+
+
+ enable
+ enable
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/ravendb/CommunityToolkit.Aspire.Hosting.RavenDB.ServiceDefaults/Extensions.cs b/examples/ravendb/CommunityToolkit.Aspire.Hosting.RavenDB.ServiceDefaults/Extensions.cs
new file mode 100644
index 00000000..9b608dc4
--- /dev/null
+++ b/examples/ravendb/CommunityToolkit.Aspire.Hosting.RavenDB.ServiceDefaults/Extensions.cs
@@ -0,0 +1,116 @@
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Diagnostics.HealthChecks;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Diagnostics.HealthChecks;
+using Microsoft.Extensions.Logging;
+using OpenTelemetry;
+using OpenTelemetry.Metrics;
+using OpenTelemetry.Trace;
+
+namespace Microsoft.Extensions.Hosting;
+// Adds common .NET Aspire services: service discovery, resilience, health checks, and OpenTelemetry.
+// This project should be referenced by each service project in your solution.
+// To learn more about using this project, see https://aka.ms/dotnet/aspire/service-defaults
+public static class Extensions
+{
+ public static IHostApplicationBuilder AddServiceDefaults(this IHostApplicationBuilder builder)
+ {
+ builder.ConfigureOpenTelemetry();
+
+ builder.AddDefaultHealthChecks();
+
+ builder.Services.AddServiceDiscovery();
+
+ builder.Services.ConfigureHttpClientDefaults(http =>
+ {
+ // Turn on resilience by default
+ http.AddStandardResilienceHandler();
+
+ // Turn on service discovery by default
+ http.AddServiceDiscovery();
+ });
+
+ // Uncomment the following to restrict the allowed schemes for service discovery.
+ // builder.Services.Configure(options =>
+ // {
+ // options.AllowedSchemes = ["https"];
+ // });
+
+ return builder;
+ }
+
+ public static IHostApplicationBuilder ConfigureOpenTelemetry(this IHostApplicationBuilder builder)
+ {
+ builder.Logging.AddOpenTelemetry(logging =>
+ {
+ logging.IncludeFormattedMessage = true;
+ logging.IncludeScopes = true;
+ });
+
+ builder.Services.AddOpenTelemetry()
+ .WithMetrics(metrics =>
+ {
+ metrics.AddAspNetCoreInstrumentation()
+ .AddHttpClientInstrumentation()
+ .AddRuntimeInstrumentation();
+ })
+ .WithTracing(tracing =>
+ {
+ tracing.AddAspNetCoreInstrumentation()
+ // Uncomment the following line to enable gRPC instrumentation (requires the OpenTelemetry.Instrumentation.GrpcNetClient package)
+ //.AddGrpcClientInstrumentation()
+ .AddHttpClientInstrumentation();
+ });
+
+ builder.AddOpenTelemetryExporters();
+
+ return builder;
+ }
+
+ private static IHostApplicationBuilder AddOpenTelemetryExporters(this IHostApplicationBuilder builder)
+ {
+ var useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]);
+
+ if (useOtlpExporter)
+ {
+ builder.Services.AddOpenTelemetry().UseOtlpExporter();
+ }
+
+ // Uncomment the following lines to enable the Azure Monitor exporter (requires the Azure.Monitor.OpenTelemetry.AspNetCore package)
+ //if (!string.IsNullOrEmpty(builder.Configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"]))
+ //{
+ // builder.Services.AddOpenTelemetry()
+ // .UseAzureMonitor();
+ //}
+
+ return builder;
+ }
+
+ public static IHostApplicationBuilder AddDefaultHealthChecks(this IHostApplicationBuilder builder)
+ {
+ builder.Services.AddHealthChecks()
+ // Add a default liveness check to ensure app is responsive
+ .AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]);
+
+ return builder;
+ }
+
+ public static WebApplication MapDefaultEndpoints(this WebApplication app)
+ {
+ // Adding health checks endpoints to applications in non-development environments has security implications.
+ // See https://aka.ms/dotnet/aspire/healthchecks for details before enabling these endpoints in non-development environments.
+ if (app.Environment.IsDevelopment())
+ {
+ // All health checks must pass for app to be considered ready to accept traffic after starting
+ app.MapHealthChecks("/health");
+
+ // Only health checks tagged with the "live" tag must pass for app to be considered alive
+ app.MapHealthChecks("/alive", new HealthCheckOptions
+ {
+ Predicate = r => r.Tags.Contains("live")
+ });
+ }
+
+ return app;
+ }
+}
diff --git a/examples/ravendb/RavenDB.AppHost/CommunityToolkit.Aspire.Hosting.RavenDB.AppHost.csproj b/examples/ravendb/RavenDB.AppHost/CommunityToolkit.Aspire.Hosting.RavenDB.AppHost.csproj
new file mode 100644
index 00000000..a2481892
--- /dev/null
+++ b/examples/ravendb/RavenDB.AppHost/CommunityToolkit.Aspire.Hosting.RavenDB.AppHost.csproj
@@ -0,0 +1,22 @@
+
+
+
+
+ Exe
+ enable
+ enable
+ true
+ f39fb70f-21f3-4af9-89b4-3062ff4431e6
+ false
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/ravendb/RavenDB.AppHost/Program.cs b/examples/ravendb/RavenDB.AppHost/Program.cs
new file mode 100644
index 00000000..0a5c4689
--- /dev/null
+++ b/examples/ravendb/RavenDB.AppHost/Program.cs
@@ -0,0 +1,14 @@
+using CommunityToolkit.Aspire.Hosting.RavenDB;
+using Projects;
+
+var builder = DistributedApplication.CreateBuilder(args);
+
+var serverSettings = RavenDBServerSettings.Unsecured();
+var ravendb = builder.AddRavenDB("ravendb", serverSettings);
+ravendb.AddDatabase("ravenDatabase");
+
+builder.AddProject("apiservice")
+ .WithReference(ravendb)
+ .WaitFor(ravendb);
+
+builder.Build().Run();
diff --git a/examples/ravendb/RavenDB.AppHost/Properties/launchSettings.json b/examples/ravendb/RavenDB.AppHost/Properties/launchSettings.json
new file mode 100644
index 00000000..b2707245
--- /dev/null
+++ b/examples/ravendb/RavenDB.AppHost/Properties/launchSettings.json
@@ -0,0 +1,13 @@
+{
+ "profiles": {
+ "RavenDB.AppHost": {
+ "commandName": "Project",
+ "environmentVariables": {
+ "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "\"\"",
+ "DISABLE_OTLP": "true",
+ "ASPNETCORE_URLS": "http://localhost:5000",
+ "ASPIRE_ALLOW_UNSECURED_TRANSPORT": "true"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/CommunityToolkit.Aspire.Hosting.RavenDB/CommunityToolkit.Aspire.Hosting.RavenDB.csproj b/src/CommunityToolkit.Aspire.Hosting.RavenDB/CommunityToolkit.Aspire.Hosting.RavenDB.csproj
new file mode 100644
index 00000000..3b23c520
--- /dev/null
+++ b/src/CommunityToolkit.Aspire.Hosting.RavenDB/CommunityToolkit.Aspire.Hosting.RavenDB.csproj
@@ -0,0 +1,17 @@
+
+
+
+ An Aspire integration leveraging the RavenDB container.
+ hosting ravendb
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/CommunityToolkit.Aspire.Hosting.RavenDB/HealthChecksExtensions.cs b/src/CommunityToolkit.Aspire.Hosting.RavenDB/HealthChecksExtensions.cs
new file mode 100644
index 00000000..049ec3b7
--- /dev/null
+++ b/src/CommunityToolkit.Aspire.Hosting.RavenDB/HealthChecksExtensions.cs
@@ -0,0 +1,111 @@
+using HealthChecks.RavenDB;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Diagnostics.HealthChecks;
+using System.Data.Common;
+
+namespace CommunityToolkit.Aspire.Hosting.RavenDB;
+
+internal static class HealthChecksExtensions
+{
+ private const string NAME = "ravendb";
+
+ ///
+ /// Add a health check for RavenDB server services.
+ ///
+ /// The .
+ /// A factory to build the connection string to use.
+ /// The health check name. Optional. If null the type name 'ravendb' will be used for the name.
+ /// that should be reported when the health check fails. Optional. If null then
+ /// the default status of will be reported.
+ /// A list of tags that can be used to filter sets of health checks. Optional.
+ /// An optional representing the timeout of the check.
+ /// The specified .
+ public static IHealthChecksBuilder AddRavenDB(
+ this IHealthChecksBuilder builder,
+ Func connectionStringFactory,
+ string? name = default,
+ HealthStatus? failureStatus = default,
+ IEnumerable? tags = default,
+ TimeSpan? timeout = default)
+ {
+ return builder.Add(new HealthCheckRegistration(
+ name ?? NAME,
+ sp =>
+ {
+ var connectionString = ValidateConnectionString(connectionStringFactory, sp);
+ return new RavenDBHealthCheck(new RavenDBOptions { Urls = new[] { connectionString } });
+ },
+ failureStatus,
+ tags,
+ timeout));
+ }
+
+ ///
+ /// Add a health check for RavenDB database services.
+ ///
+ /// The .
+ /// A factory to build the connection string to use.
+ /// The database name to check.
+ /// The health check name. Optional. If null the type name 'ravendb' will be used for the name.
+ /// that should be reported when the health check fails. Optional. If null then
+ /// the default status of will be reported.
+ /// A list of tags that can be used to filter sets of health checks. Optional.
+ /// An optional representing the timeout of the check.
+ /// The specified .
+ public static IHealthChecksBuilder AddRavenDB(
+ this IHealthChecksBuilder builder,
+ Func connectionStringFactory,
+ string databaseName,
+ string? name = default,
+ HealthStatus? failureStatus = default,
+ IEnumerable? tags = default,
+ TimeSpan? timeout = default)
+ {
+ return builder.Add(new HealthCheckRegistration(
+ name ?? NAME,
+ sp =>
+ {
+ var connectionString = ValidateConnectionString(connectionStringFactory, sp);
+ return new RavenDBHealthCheck(new RavenDBOptions
+ {
+ Urls = new[] { connectionString },
+ Database = databaseName
+ });
+ },
+ failureStatus,
+ tags,
+ timeout));
+ }
+
+ ///
+ /// Validates that the connection string is not null or empty.
+ ///
+ /// The factory to generate the connection string.
+ /// The service provider instance.
+ /// A valid, non-empty connection string.
+ /// Thrown if the connection string is null or empty.
+ private static string ValidateConnectionString(Func connectionStringFactory, IServiceProvider serviceProvider)
+ {
+ var connectionString = connectionStringFactory(serviceProvider);
+ if (string.IsNullOrWhiteSpace(connectionString))
+ {
+ throw new InvalidOperationException("Failed to generate a valid RavenDB connection string. The result cannot be null or empty.");
+ }
+
+ var connectionBuilder = new DbConnectionStringBuilder
+ {
+ ConnectionString = connectionString
+ };
+
+ if (connectionBuilder.TryGetValue("URL", out var url) && url is string serverUrl)
+ {
+ connectionString = serverUrl;
+ }
+ else
+ {
+ throw new InvalidOperationException("Connection string is unavailable");
+ }
+
+ return connectionString;
+ }
+}
diff --git a/src/CommunityToolkit.Aspire.Hosting.RavenDB/PublicAPI.Shipped.txt b/src/CommunityToolkit.Aspire.Hosting.RavenDB/PublicAPI.Shipped.txt
new file mode 100644
index 00000000..7dc5c581
--- /dev/null
+++ b/src/CommunityToolkit.Aspire.Hosting.RavenDB/PublicAPI.Shipped.txt
@@ -0,0 +1 @@
+#nullable enable
diff --git a/src/CommunityToolkit.Aspire.Hosting.RavenDB/PublicAPI.Unshipped.txt b/src/CommunityToolkit.Aspire.Hosting.RavenDB/PublicAPI.Unshipped.txt
new file mode 100644
index 00000000..1b06c631
--- /dev/null
+++ b/src/CommunityToolkit.Aspire.Hosting.RavenDB/PublicAPI.Unshipped.txt
@@ -0,0 +1,42 @@
+#nullable enable
+Aspire.Hosting.ApplicationModel.RavenDBServerResource
+Aspire.Hosting.ApplicationModel.RavenDBServerResource.RavenDBServerResource(string! name, bool isSecured) -> void
+Aspire.Hosting.ApplicationModel.RavenDBServerResource.PrimaryEndpoint.get -> Aspire.Hosting.ApplicationModel.EndpointReference!
+Aspire.Hosting.ApplicationModel.RavenDBServerResource.ConnectionStringExpression.get -> Aspire.Hosting.ApplicationModel.ReferenceExpression!
+Aspire.Hosting.ApplicationModel.RavenDBServerResource.Databases.get -> System.Collections.Generic.IReadOnlyDictionary!
+Aspire.Hosting.RavenDBBuilderExtensions
+static Aspire.Hosting.RavenDBBuilderExtensions.AddDatabase(this Aspire.Hosting.ApplicationModel.IResourceBuilder! builder, string! name, string? databaseName = null) -> Aspire.Hosting.ApplicationModel.IResourceBuilder!
+static Aspire.Hosting.RavenDBBuilderExtensions.AddRavenDB(this Aspire.Hosting.IDistributedApplicationBuilder! builder, string! name) -> Aspire.Hosting.ApplicationModel.IResourceBuilder!
+static Aspire.Hosting.RavenDBBuilderExtensions.AddRavenDB(this Aspire.Hosting.IDistributedApplicationBuilder! builder, string! name, bool secured, System.Collections.Generic.Dictionary! environmentVariables, int? port = null) -> Aspire.Hosting.ApplicationModel.IResourceBuilder!
+static Aspire.Hosting.RavenDBBuilderExtensions.AddRavenDB(this Aspire.Hosting.IDistributedApplicationBuilder! builder, string! name, CommunityToolkit.Aspire.Hosting.RavenDB.RavenDBServerSettings! serverSettings) -> Aspire.Hosting.ApplicationModel.IResourceBuilder!
+static Aspire.Hosting.RavenDBBuilderExtensions.WithDataBindMount(this Aspire.Hosting.ApplicationModel.IResourceBuilder! builder, string! source, bool isReadOnly = false) -> Aspire.Hosting.ApplicationModel.IResourceBuilder!
+static Aspire.Hosting.RavenDBBuilderExtensions.WithDataVolume(this Aspire.Hosting.ApplicationModel.IResourceBuilder! builder, string? name = null, bool isReadOnly = false) -> Aspire.Hosting.ApplicationModel.IResourceBuilder!
+Aspire.Hosting.ApplicationModel.RavenDBDatabaseResource
+Aspire.Hosting.ApplicationModel.RavenDBDatabaseResource.RavenDBDatabaseResource(string! name, string! databaseName, Aspire.Hosting.ApplicationModel.RavenDBServerResource! parent) -> void
+Aspire.Hosting.ApplicationModel.RavenDBDatabaseResource.Parent.get -> Aspire.Hosting.ApplicationModel.RavenDBServerResource!
+Aspire.Hosting.ApplicationModel.RavenDBDatabaseResource.DatabaseName.get -> string!
+Aspire.Hosting.ApplicationModel.RavenDBDatabaseResource.ConnectionStringExpression.get -> Aspire.Hosting.ApplicationModel.ReferenceExpression!
+CommunityToolkit.Aspire.Hosting.RavenDB.RavenDBServerSettings
+CommunityToolkit.Aspire.Hosting.RavenDB.RavenDBSecuredServerSettings
+CommunityToolkit.Aspire.Hosting.RavenDB.LicensingOptions
+CommunityToolkit.Aspire.Hosting.RavenDB.SetupMode
+CommunityToolkit.Aspire.Hosting.RavenDB.SetupMode.None = 0 -> CommunityToolkit.Aspire.Hosting.RavenDB.SetupMode
+CommunityToolkit.Aspire.Hosting.RavenDB.SetupMode.LetsEncrypt = 1 -> CommunityToolkit.Aspire.Hosting.RavenDB.SetupMode
+CommunityToolkit.Aspire.Hosting.RavenDB.SetupMode.Secured = 2 -> CommunityToolkit.Aspire.Hosting.RavenDB.SetupMode
+CommunityToolkit.Aspire.Hosting.RavenDB.SetupMode.Unsecured = 3 -> CommunityToolkit.Aspire.Hosting.RavenDB.SetupMode
+CommunityToolkit.Aspire.Hosting.RavenDB.RavenDBServerSettings.RavenDBServerSettings() -> void
+CommunityToolkit.Aspire.Hosting.RavenDB.RavenDBServerSettings.ServerUrl.get -> string?
+CommunityToolkit.Aspire.Hosting.RavenDB.RavenDBServerSettings.ServerUrl.set -> void
+CommunityToolkit.Aspire.Hosting.RavenDB.RavenDBServerSettings.SetupMode.get -> CommunityToolkit.Aspire.Hosting.RavenDB.SetupMode
+CommunityToolkit.Aspire.Hosting.RavenDB.RavenDBServerSettings.LicensingOptions.get -> CommunityToolkit.Aspire.Hosting.RavenDB.LicensingOptions?
+static CommunityToolkit.Aspire.Hosting.RavenDB.RavenDBServerSettings.Secured(string! domainUrl, string! certificatePath, string? certificatePassword = null, string? serverUrl = null) -> CommunityToolkit.Aspire.Hosting.RavenDB.RavenDBServerSettings!
+static CommunityToolkit.Aspire.Hosting.RavenDB.RavenDBServerSettings.SecuredWithLetsEncrypt(string! domainUrl, string! certificatePath, string? certificatePassword = null, string? serverUrl = null) -> CommunityToolkit.Aspire.Hosting.RavenDB.RavenDBServerSettings!
+static CommunityToolkit.Aspire.Hosting.RavenDB.RavenDBServerSettings.Unsecured() -> CommunityToolkit.Aspire.Hosting.RavenDB.RavenDBServerSettings!
+CommunityToolkit.Aspire.Hosting.RavenDB.RavenDBServerSettings.WithLicense(string! license, bool eulaAccepted = true) -> void
+CommunityToolkit.Aspire.Hosting.RavenDB.RavenDBSecuredServerSettings.RavenDBSecuredServerSettings(string! certificatePath, string? certificatePassword, string! publicServerUrl) -> void
+CommunityToolkit.Aspire.Hosting.RavenDB.RavenDBSecuredServerSettings.CertificatePath.get -> string!
+CommunityToolkit.Aspire.Hosting.RavenDB.RavenDBSecuredServerSettings.CertificatePassword.get -> string?
+CommunityToolkit.Aspire.Hosting.RavenDB.RavenDBSecuredServerSettings.PublicServerUrl.get -> string!
+CommunityToolkit.Aspire.Hosting.RavenDB.LicensingOptions.LicensingOptions(string! license, bool eulaAccepted = true) -> void
+CommunityToolkit.Aspire.Hosting.RavenDB.LicensingOptions.License.get -> string!
+CommunityToolkit.Aspire.Hosting.RavenDB.LicensingOptions.EulaAccepted.get -> bool
\ No newline at end of file
diff --git a/src/CommunityToolkit.Aspire.Hosting.RavenDB/README.md b/src/CommunityToolkit.Aspire.Hosting.RavenDB/README.md
new file mode 100644
index 00000000..cab29840
--- /dev/null
+++ b/src/CommunityToolkit.Aspire.Hosting.RavenDB/README.md
@@ -0,0 +1,33 @@
+# CommunityToolkit.Aspire.Hosting.RavenDB library
+
+Provides extension methods and resource definitions for a .NET Aspire AppHost to configure a RavenDB resource.
+
+## Getting started
+
+### Install the package
+
+In your AppHost project, install the .NET Aspire RavenDB Hosting library with [NuGet](https://www.nuget.org):
+
+```dotnetcli
+dotnet add package CommunityToolkit.Aspire.Hosting.RavenDB
+```
+
+## Usage example
+
+Then, in the _Program.cs_ file of `AppHost`, add a RavenDB resource and consume the connection using the following methods:
+
+```csharp
+var db = builder.AddRavenDB("ravendb").AddDatabase("mydb");
+
+var myService = builder.AddProject()
+ .WithReference(db);
+```
+
+## Additional documentation
+
+
+https://learn.microsoft.com/dotnet/aspire/community-toolkit/ravendb
+
+## Feedback & contributing
+
+https://github.com/CommunityToolkit/Aspire
diff --git a/src/CommunityToolkit.Aspire.Hosting.RavenDB/RavenDBBuilderExtensions.cs b/src/CommunityToolkit.Aspire.Hosting.RavenDB/RavenDBBuilderExtensions.cs
new file mode 100644
index 00000000..45bc8b71
--- /dev/null
+++ b/src/CommunityToolkit.Aspire.Hosting.RavenDB/RavenDBBuilderExtensions.cs
@@ -0,0 +1,219 @@
+using Aspire.Hosting.ApplicationModel;
+using Aspire.Hosting.Utils;
+using CommunityToolkit.Aspire.Hosting.RavenDB;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Aspire.Hosting;
+
+///
+/// Provides extension methods for adding RavenDB resources to an .
+///
+public static class RavenDBBuilderExtensions
+{
+ ///
+ /// Adds a RavenDB server resource to the application model. A container is used for local development.
+ /// This overload simplifies the configuration by creating an unsecured RavenDB server resource with default settings.
+ ///
+ ///
+ ///
+ /// Note: When using this method, a valid RavenDB license must be provided as an environment variable
+ /// before calling the and methods.
+ /// You can set the license by calling:
+ ///
+ /// builder.WithEnvironment("RAVEN_License", "{your license}");
+ ///
+ ///
+ ///
+ /// The to which the resource is added.
+ /// The name of the RavenDB server resource.
+ /// A resource builder for the newly added RavenDB server resource.
+ /// Thrown if is null.
+ public static IResourceBuilder AddRavenDB(this IDistributedApplicationBuilder builder, [ResourceName] string name)
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+
+ return builder.AddRavenDB(name, RavenDBServerSettings.Unsecured());
+ }
+
+ ///
+ /// Adds a RavenDB server resource to the application model. A container is used for local development.
+ /// This version of the package defaults to the tag of the container image.
+ /// This overload simplifies configuration by accepting a object to specify server settings.
+ ///
+ /// The
+ /// The name of the RavenDB server resource.
+ /// An object of type containing configuration details for the RavenDB server,
+ /// such as whether the server should use HTTPS, RavenDB license and other relevant settings.
+ /// A resource builder for the newly added RavenDB server resource.
+ /// Thrown when the connection string cannot be retrieved during configuration.
+ /// Thrown when the connection string is unavailable.
+ public static IResourceBuilder AddRavenDB(this IDistributedApplicationBuilder builder,
+ [ResourceName] string name,
+ RavenDBServerSettings serverSettings)
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+
+ var environmentVariables = GetEnvironmentVariablesFromServerSettings(serverSettings);
+ return builder.AddRavenDB(name, secured: serverSettings is RavenDBSecuredServerSettings, environmentVariables);
+ }
+
+ ///
+ /// Adds a RavenDB server resource to the application model. A container is used for local development.
+ /// This version of the package defaults to the tag of the container image.
+ ///
+ /// The
+ /// The name of the RavenDB server resource.
+ /// Indicates whether the server connection should be secured (HTTPS). Defaults to false.
+ /// The environment variables to configure the RavenDB server.
+ /// Optional port for the server. If not provided, defaults to the container's internal port (8080).
+ /// A resource builder for the newly added RavenDB server resource.
+ /// Thrown when the connection string cannot be retrieved during configuration.
+ /// Thrown when the connection string is unavailable.
+ public static IResourceBuilder AddRavenDB(this IDistributedApplicationBuilder builder,
+ [ResourceName] string name,
+ bool secured,
+ Dictionary environmentVariables,
+ int? port = null)
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+ ArgumentNullException.ThrowIfNull(name);
+
+ var serverResource = new RavenDBServerResource(name, secured);
+
+ string? connectionString = null;
+ builder.Eventing.Subscribe(serverResource, async (@event, ct) =>
+ {
+ connectionString = await serverResource.ConnectionStringExpression.GetValueAsync(ct).ConfigureAwait(false);
+
+ if (connectionString is null)
+ throw new DistributedApplicationException($"ConnectionStringAvailableEvent was published for the '{serverResource.Name}' resource but the connection string was null.");
+ });
+
+ var healthCheckKey = $"{name}_check";
+ builder.Services.AddHealthChecks()
+ .AddRavenDB(sp => connectionString ?? throw new InvalidOperationException("Connection string is unavailable"),
+ name: healthCheckKey);
+
+ return builder
+ .AddResource(serverResource)
+ .WithEndpoint(port: port, targetPort: secured ? 443 : 8080, scheme: serverResource.PrimaryEndpointName, name: serverResource.PrimaryEndpointName, isProxied: false)
+ .WithImage(RavenDBContainerImageTags.Image, RavenDBContainerImageTags.Tag)
+ .WithImageRegistry(RavenDBContainerImageTags.Registry)
+ .WithEnvironment(context => ConfigureEnvironmentVariables(context, environmentVariables))
+ .WithHealthCheck(healthCheckKey);
+ }
+
+ private static Dictionary GetEnvironmentVariablesFromServerSettings(RavenDBServerSettings serverSettings)
+ {
+ var environmentVariables = new Dictionary
+ {
+ { "RAVEN_Setup_Mode", serverSettings.SetupMode.ToString() }
+ };
+
+ if (serverSettings.LicensingOptions is not null)
+ {
+ environmentVariables.TryAdd("RAVEN_License_Eula_Accepted", serverSettings.LicensingOptions.EulaAccepted.ToString());
+ environmentVariables.TryAdd("RAVEN_License", serverSettings.LicensingOptions.License);
+ }
+
+ if (serverSettings.ServerUrl is not null)
+ environmentVariables.TryAdd("RAVEN_ServerUrl", serverSettings.ServerUrl);
+
+ if (serverSettings is RavenDBSecuredServerSettings securedServerSettings)
+ {
+ environmentVariables.TryAdd("RAVEN_PublicServerUrl", securedServerSettings.PublicServerUrl);
+ environmentVariables.TryAdd("RAVEN_Security_Certificate_Path", securedServerSettings.CertificatePath);
+
+ if (securedServerSettings.CertificatePassword is not null)
+ environmentVariables.TryAdd("RAVEN_Security_Certificate_Password", securedServerSettings.CertificatePassword);
+ }
+
+ return environmentVariables;
+ }
+
+ private static void ConfigureEnvironmentVariables(EnvironmentCallbackContext context, Dictionary? environmentVariables = null)
+ {
+ if (environmentVariables is null)
+ {
+ context.EnvironmentVariables.TryAdd("RAVEN_Setup_Mode", "None");
+ context.EnvironmentVariables.TryAdd("RAVEN_Security_UnsecuredAccessAllowed", "PrivateNetwork");
+ return;
+ }
+
+ foreach (var environmentVariable in environmentVariables)
+ context.EnvironmentVariables.TryAdd(environmentVariable.Key, environmentVariable.Value);
+ }
+
+ ///
+ /// Adds a database resource to an existing RavenDB server resource.
+ ///
+ /// The resource builder for the RavenDB server.
+ /// The name of the database resource.
+ /// The name of the database to create/add. Defaults to the same name as the resource if not provided.
+ /// A resource builder for the newly added RavenDB database resource.
+ /// Thrown when the connection string cannot be retrieved during configuration.
+ /// Thrown when the connection string is unavailable.
+ public static IResourceBuilder AddDatabase(this IResourceBuilder builder,
+ [ResourceName] string name,
+ string? databaseName = null)
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+ ArgumentNullException.ThrowIfNull(name);
+
+ // Use the resource name as the database name if it's not provided
+ databaseName ??= name;
+
+ builder.Resource.AddDatabase(name, databaseName);
+ var databaseResource = new RavenDBDatabaseResource(name, databaseName, builder.Resource);
+
+ string? connectionString = null;
+
+ builder.ApplicationBuilder.Eventing.Subscribe(databaseResource, async (@event, ct) =>
+ {
+ connectionString = await databaseResource.ConnectionStringExpression.GetValueAsync(ct).ConfigureAwait(false);
+
+ if (connectionString is null)
+ throw new DistributedApplicationException($"ConnectionStringAvailableEvent was published for the '{databaseResource.Name}' resource but the connection string was null.");
+ });
+
+ var healthCheckKey = $"{name}_check";
+ builder.ApplicationBuilder.Services.AddHealthChecks()
+ .AddRavenDB(sp => connectionString ?? throw new InvalidOperationException("Connection string is unavailable"),
+ databaseName: databaseName,
+ name: healthCheckKey);
+
+ return builder.ApplicationBuilder
+ .AddResource(databaseResource);
+ }
+
+ ///
+ /// Adds a bind mount for the data folder to a RavenDB container resource.
+ ///
+ /// The resource builder for the RavenDB server.
+ /// The source directory on the host to mount into the container.
+ /// Indicates whether the bind mount should be read-only. Defaults to false.
+ /// The for the RavenDB server resource.
+ public static IResourceBuilder WithDataBindMount(this IResourceBuilder builder, string source, bool isReadOnly = false)
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+ ArgumentNullException.ThrowIfNull(source);
+
+ return builder.WithBindMount(source, "/var/lib/ravendb/data", isReadOnly);
+ }
+
+ ///
+ /// Adds a named volume for the data folder to a RavenDB container resource.
+ ///
+ /// The resource builder for the RavenDB server.
+ /// Optional name for the volume. Defaults to a generated name if not provided.
+ /// Indicates whether the volume should be read-only. Defaults to false.
+ /// The for the RavenDB server resource.
+ public static IResourceBuilder WithDataVolume(this IResourceBuilder builder, string? name = null, bool isReadOnly = false)
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+
+#pragma warning disable CTASPIRE001
+ return builder.WithVolume(name ?? VolumeNameGenerator.CreateVolumeName(builder, "data"), "/var/lib/ravendb/data", isReadOnly);
+#pragma warning restore CTASPIRE001
+ }
+}
diff --git a/src/CommunityToolkit.Aspire.Hosting.RavenDB/RavenDBContainerImageTags.cs b/src/CommunityToolkit.Aspire.Hosting.RavenDB/RavenDBContainerImageTags.cs
new file mode 100644
index 00000000..e640095f
--- /dev/null
+++ b/src/CommunityToolkit.Aspire.Hosting.RavenDB/RavenDBContainerImageTags.cs
@@ -0,0 +1,7 @@
+namespace CommunityToolkit.Aspire.Hosting.RavenDB;
+internal static class RavenDBContainerImageTags
+{
+ public const string Registry = "docker.io";
+ public const string Image = "ravendb/ravendb";
+ public const string Tag = "6.2-latest";
+}
diff --git a/src/CommunityToolkit.Aspire.Hosting.RavenDB/RavenDBDatabaseResource.cs b/src/CommunityToolkit.Aspire.Hosting.RavenDB/RavenDBDatabaseResource.cs
new file mode 100644
index 00000000..fd45acec
--- /dev/null
+++ b/src/CommunityToolkit.Aspire.Hosting.RavenDB/RavenDBDatabaseResource.cs
@@ -0,0 +1,29 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+
+namespace Aspire.Hosting.ApplicationModel;
+
+///
+/// A resource that represents a RavenDB database. This is a child resource of a .
+///
+public class RavenDBDatabaseResource(string name, string databaseName, RavenDBServerResource parent) : Resource(ThrowIfNull(name)), IResourceWithParent, IResourceWithConnectionString
+{
+ ///
+ /// Gets the parent RavenDB server resource associated with this database.
+ ///
+ public RavenDBServerResource Parent { get; } = ThrowIfNull(parent);
+
+ ///
+ /// Gets the name of the database.
+ ///
+ public string DatabaseName { get; } = ThrowIfNull(databaseName);
+
+ ///
+ /// Gets the connection string expression for the RavenDB database, derived from the parent server's connection string.
+ ///
+ public ReferenceExpression ConnectionStringExpression =>
+ ReferenceExpression.Create($"{Parent};Database={DatabaseName}");
+
+ private static T ThrowIfNull([NotNull] T? argument, [CallerArgumentExpression(nameof(argument))] string? paramName = null)
+ => argument ?? throw new ArgumentNullException(paramName);
+}
diff --git a/src/CommunityToolkit.Aspire.Hosting.RavenDB/RavenDBServerResource.cs b/src/CommunityToolkit.Aspire.Hosting.RavenDB/RavenDBServerResource.cs
new file mode 100644
index 00000000..9514e5c5
--- /dev/null
+++ b/src/CommunityToolkit.Aspire.Hosting.RavenDB/RavenDBServerResource.cs
@@ -0,0 +1,49 @@
+namespace Aspire.Hosting.ApplicationModel;
+
+///
+/// A resource that represents a RavenDB container.
+///
+public class RavenDBServerResource(string name, bool isSecured) : ContainerResource(name), IResourceWithConnectionString
+{
+ ///
+ /// Indicates whether the server connection is secured (HTTPS) or not (HTTP).
+ ///
+ private bool IsSecured { get; } = isSecured;
+
+ ///
+ /// Gets the protocol used for the primary endpoint, based on the security setting ("http" or "https").
+ ///
+ internal string PrimaryEndpointName => IsSecured ? "https" : "http";
+
+ private EndpointReference? _primaryEndpoint;
+
+ ///
+ /// Gets the primary endpoint for the RavenDB server.
+ ///
+ public EndpointReference PrimaryEndpoint => _primaryEndpoint ??= new(this, PrimaryEndpointName);
+
+ ///
+ /// Gets the connection string expression for the RavenDB server,
+ /// formatted as "http(s)://{Host}:{Port}" depending on the security setting.
+ ///
+ public ReferenceExpression ConnectionStringExpression => ReferenceExpression.Create(
+ $"URL={(IsSecured ? "https://" : "http://")}{PrimaryEndpoint.Property(EndpointProperty.Host)}:{PrimaryEndpoint.Property(EndpointProperty.Port)}");
+
+ private readonly Dictionary _databases = new();
+
+ ///
+ /// Gets a read-only dictionary of databases associated with this server resource.
+ /// The key represents the resource name, and the value represents the database name.
+ ///
+ public IReadOnlyDictionary Databases => _databases;
+
+ ///
+ /// Adds a database to the resource.
+ ///
+ /// The name of the resource to associate with the database.
+ /// The name of the database to add.
+ internal void AddDatabase(string name, string databaseName)
+ {
+ _databases.TryAdd(name, databaseName);
+ }
+}
diff --git a/src/CommunityToolkit.Aspire.Hosting.RavenDB/RavenDBServerSettings.cs b/src/CommunityToolkit.Aspire.Hosting.RavenDB/RavenDBServerSettings.cs
new file mode 100644
index 00000000..e722d7e9
--- /dev/null
+++ b/src/CommunityToolkit.Aspire.Hosting.RavenDB/RavenDBServerSettings.cs
@@ -0,0 +1,143 @@
+namespace CommunityToolkit.Aspire.Hosting.RavenDB;
+
+///
+/// Represents the settings for configuring a RavenDB server resource.
+///
+public class RavenDBServerSettings
+{
+ ///
+ /// The iternal URL for the RavenDB server.
+ /// If not specified, the container resource will automatically assign a random URL.
+ ///
+ public string? ServerUrl { get; set; }
+
+ ///
+ /// The setup mode for the server. This determines whether the server is secured, uses Let's Encrypt, or is unsecured.
+ ///
+ public SetupMode SetupMode { get; private set; }
+
+ ///
+ /// Gets the licensing options configured for the server.
+ ///
+ public LicensingOptions? LicensingOptions { get; private set; }
+
+ ///
+ /// Protected constructor to allow inheritance but prevent direct instantiation.
+ ///
+ protected RavenDBServerSettings() { }
+
+ ///
+ /// Creates an unsecured RavenDB server settings object with default settings.
+ ///
+ public static RavenDBServerSettings Unsecured() => new RavenDBServerSettings { SetupMode = SetupMode.None };
+
+ ///
+ /// Creates a secured RavenDB server settings object with the specified configuration.
+ ///
+ /// The public domain URL for the server.
+ /// The path to the certificate file.
+ /// The password for the certificate file, if required. Optional.
+ /// The optional server URL.
+ public static RavenDBServerSettings Secured(string domainUrl, string certificatePath,
+ string? certificatePassword = null, string? serverUrl = null)
+ {
+ return new RavenDBSecuredServerSettings(certificatePath, certificatePassword, domainUrl)
+ {
+ SetupMode = SetupMode.Secured,
+ ServerUrl = serverUrl
+ };
+ }
+
+ ///
+ /// Creates a secured RavenDB server settings object with the specified configuration.
+ ///
+ /// The public domain URL for the server.
+ /// The path to the certificate file.
+ /// The password for the certificate file, if required. Optional.
+ /// The optional server URL.
+ public static RavenDBServerSettings SecuredWithLetsEncrypt(string domainUrl, string certificatePath,
+ string? certificatePassword = null, string? serverUrl = null)
+ {
+ return new RavenDBSecuredServerSettings(certificatePath, certificatePassword, domainUrl)
+ {
+ SetupMode = SetupMode.LetsEncrypt,
+ ServerUrl = serverUrl
+ };
+ }
+
+ ///
+ /// Configures licensing options for the RavenDB server.
+ ///
+ /// The license string for the RavenDB server.
+ /// Indicates whether the End User License Agreement (EULA) has been accepted. Defaults to true.
+ public void WithLicense(string license, bool eulaAccepted = true)
+ {
+ LicensingOptions = new LicensingOptions(license, eulaAccepted);
+ }
+}
+
+///
+/// Represents secured settings for a RavenDB server, including certificate information and a public server URL.
+///
+public sealed class RavenDBSecuredServerSettings(string certificatePath, string? certificatePassword, string publicServerUrl) : RavenDBServerSettings
+{
+ ///
+ /// The path to the certificate file.
+ ///
+ public string CertificatePath { get; } = certificatePath;
+
+ ///
+ /// The password for the certificate file, if required.
+ ///
+ public string? CertificatePassword { get; } = certificatePassword;
+
+ ///
+ /// The public server URL (domain) that the secured RavenDB server will expose.
+ ///
+ public string PublicServerUrl { get; } = publicServerUrl;
+}
+
+///
+/// Represents the setup modes for configuring a RavenDB server.
+///
+public enum SetupMode
+{
+ ///
+ /// No specific setup mode is applied.
+ ///
+ None,
+
+ ///
+ /// The server is secured using Let's Encrypt.
+ ///
+ LetsEncrypt,
+
+ ///
+ /// The server is secured using a provided SSL certificate.
+ ///
+ Secured,
+
+ ///
+ /// The server is unsecured.
+ ///
+ Unsecured
+}
+
+///
+/// Represents licensing options for a RavenDB server.
+///
+public sealed class LicensingOptions(string license, bool eulaAccepted = true)
+{
+ ///
+ /// RavenDB license string.
+ ///
+ public string License { get; } = license;
+
+ ///
+ /// Indicates whether the End User License Agreement (EULA) has been accepted.
+ /// Defaults to true.
+ /// By setting EulaAccepted=true, you agree to the terms and conditions outlined at
+ /// https://ravendb.net/legal.
+ ///
+ public bool EulaAccepted { get; } = eulaAccepted;
+}
\ No newline at end of file
diff --git a/src/CommunityToolkit.Aspire.RavenDB.Client/CommunityToolkit.Aspire.RavenDB.Client.csproj b/src/CommunityToolkit.Aspire.RavenDB.Client/CommunityToolkit.Aspire.RavenDB.Client.csproj
new file mode 100644
index 00000000..458a9e9b
--- /dev/null
+++ b/src/CommunityToolkit.Aspire.RavenDB.Client/CommunityToolkit.Aspire.RavenDB.Client.csproj
@@ -0,0 +1,14 @@
+
+
+
+ A .NET Aspire client integration for the RavenDB.Client library.
+ client ravendb
+
+
+
+
+
+
+
+
+
diff --git a/src/CommunityToolkit.Aspire.RavenDB.Client/HealthChecksExtensions.cs b/src/CommunityToolkit.Aspire.RavenDB.Client/HealthChecksExtensions.cs
new file mode 100644
index 00000000..64be4d67
--- /dev/null
+++ b/src/CommunityToolkit.Aspire.RavenDB.Client/HealthChecksExtensions.cs
@@ -0,0 +1,29 @@
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Diagnostics.HealthChecks;
+using Microsoft.Extensions.Hosting;
+
+namespace CommunityToolkit.Aspire.RavenDB.Client;
+
+internal static class HealthChecksExtensions
+{
+ ///
+ /// Adds a HealthCheckRegistration if one hasn't already been added to the builder.
+ ///
+ public static void TryAddHealthCheck(this IHostApplicationBuilder builder, HealthCheckRegistration healthCheckRegistration)
+ {
+ builder.TryAddHealthCheck(healthCheckRegistration.Name, hcBuilder => hcBuilder.Add(healthCheckRegistration));
+ }
+
+ ///
+ /// Invokes the action if the given hasn't already been added to the builder.
+ ///
+ public static void TryAddHealthCheck(this IHostApplicationBuilder builder, string name, Action addHealthCheck)
+ {
+ var healthCheckKey = $"Aspire.HealthChecks.{name}";
+ if (!builder.Properties.ContainsKey(healthCheckKey))
+ {
+ builder.Properties[healthCheckKey] = true;
+ addHealthCheck(builder.Services.AddHealthChecks());
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/CommunityToolkit.Aspire.RavenDB.Client/PublicAPI.Shipped.txt b/src/CommunityToolkit.Aspire.RavenDB.Client/PublicAPI.Shipped.txt
new file mode 100644
index 00000000..7dc5c581
--- /dev/null
+++ b/src/CommunityToolkit.Aspire.RavenDB.Client/PublicAPI.Shipped.txt
@@ -0,0 +1 @@
+#nullable enable
diff --git a/src/CommunityToolkit.Aspire.RavenDB.Client/PublicAPI.Unshipped.txt b/src/CommunityToolkit.Aspire.RavenDB.Client/PublicAPI.Unshipped.txt
new file mode 100644
index 00000000..ae519889
--- /dev/null
+++ b/src/CommunityToolkit.Aspire.RavenDB.Client/PublicAPI.Unshipped.txt
@@ -0,0 +1,28 @@
+#nullable enable
+Microsoft.Extensions.Hosting.RavenDBClientExtension
+static Microsoft.Extensions.Hosting.RavenDBClientExtension.AddKeyedRavenDBClient(this Microsoft.Extensions.Hosting.IHostApplicationBuilder! builder, object! serviceKey, CommunityToolkit.Aspire.RavenDB.Client.RavenDBClientSettings! settings) -> void
+static Microsoft.Extensions.Hosting.RavenDBClientExtension.AddKeyedRavenDBClient(this Microsoft.Extensions.Hosting.IHostApplicationBuilder! builder, object! serviceKey, string! connectionName, System.Action? configureSettings = null) -> void
+static Microsoft.Extensions.Hosting.RavenDBClientExtension.AddRavenDBClient(this Microsoft.Extensions.Hosting.IHostApplicationBuilder! builder, CommunityToolkit.Aspire.RavenDB.Client.RavenDBClientSettings! settings) -> void
+static Microsoft.Extensions.Hosting.RavenDBClientExtension.AddRavenDBClient(this Microsoft.Extensions.Hosting.IHostApplicationBuilder! builder, string! connectionName, System.Action? configureSettings = null) -> void
+CommunityToolkit.Aspire.RavenDB.Client.RavenDBClientSettings
+CommunityToolkit.Aspire.RavenDB.Client.RavenDBClientSettings.RavenDBClientSettings() -> void
+CommunityToolkit.Aspire.RavenDB.Client.RavenDBClientSettings.Urls.get -> string![]?
+CommunityToolkit.Aspire.RavenDB.Client.RavenDBClientSettings.Urls.set -> void
+CommunityToolkit.Aspire.RavenDB.Client.RavenDBClientSettings.CertificatePath.get -> string?
+CommunityToolkit.Aspire.RavenDB.Client.RavenDBClientSettings.CertificatePath.set -> void
+CommunityToolkit.Aspire.RavenDB.Client.RavenDBClientSettings.CertificatePassword.get -> string?
+CommunityToolkit.Aspire.RavenDB.Client.RavenDBClientSettings.CertificatePassword.set -> void
+CommunityToolkit.Aspire.RavenDB.Client.RavenDBClientSettings.Certificate.get -> System.Security.Cryptography.X509Certificates.X509Certificate2?
+CommunityToolkit.Aspire.RavenDB.Client.RavenDBClientSettings.Certificate.set -> void
+CommunityToolkit.Aspire.RavenDB.Client.RavenDBClientSettings.DatabaseName.get -> string?
+CommunityToolkit.Aspire.RavenDB.Client.RavenDBClientSettings.DatabaseName.set -> void
+CommunityToolkit.Aspire.RavenDB.Client.RavenDBClientSettings.CreateDatabase.get -> bool
+CommunityToolkit.Aspire.RavenDB.Client.RavenDBClientSettings.CreateDatabase.set -> void
+CommunityToolkit.Aspire.RavenDB.Client.RavenDBClientSettings.ModifyDocumentStore.get -> System.Action?
+CommunityToolkit.Aspire.RavenDB.Client.RavenDBClientSettings.ModifyDocumentStore.set -> void
+CommunityToolkit.Aspire.RavenDB.Client.RavenDBClientSettings.DisableHealthChecks.get -> bool
+CommunityToolkit.Aspire.RavenDB.Client.RavenDBClientSettings.DisableHealthChecks.set -> void
+CommunityToolkit.Aspire.RavenDB.Client.RavenDBClientSettings.DisableTracing.get -> bool
+CommunityToolkit.Aspire.RavenDB.Client.RavenDBClientSettings.DisableTracing.set -> void
+CommunityToolkit.Aspire.RavenDB.Client.RavenDBClientSettings.HealthCheckTimeout.get -> int?
+CommunityToolkit.Aspire.RavenDB.Client.RavenDBClientSettings.HealthCheckTimeout.set -> void
diff --git a/src/CommunityToolkit.Aspire.RavenDB.Client/README.md b/src/CommunityToolkit.Aspire.RavenDB.Client/README.md
new file mode 100644
index 00000000..aa858538
--- /dev/null
+++ b/src/CommunityToolkit.Aspire.RavenDB.Client/README.md
@@ -0,0 +1,141 @@
+# CommunityToolkit.Aspire.RavenDB.Client library
+
+Registers `IDocumentStore` and the associated `IDocumentSession` and `IAsyncDocumentSession` instances in the DI container for connecting to a RavenDB database. Additionally, it enables health checks, metrics, logging, and telemetry.
+
+## Getting started
+
+### Prerequisites
+
+- RavenDB database and connection string for accessing the database or a running RavenDB server instance with its connection details, such as the server's URL and a valid certificate if required.
+
+_**Note:**
+RavenDB allows creating an `IDocumentStore` without a defined database. In such cases, `IDocumentSession` and `IAsyncDocumentSession` will not be available. This library also supports creating a new RavenDB database. However, if you intend to connect to an existing RavenDB database, ensure the database exists and you have its connection details._
+
+### Install the package
+
+Install the `CommunityToolkit.Aspire.RavenDB.Client` library with [NuGet](https://www.nuget.org):
+```dotnetcli
+dotnet add package CommunityToolkit.Aspire.RavenDB.Client
+```
+*To be added once the package is published.*
+
+## Usage example
+
+In the _Program.cs_ file of your project, call the `AddRavenDBClient` extension method to register a `IDocumentStore` for use via the dependency injection container. The method takes a connection name parameter.
+
+```csharp
+builder.AddRavenDBClient("ravendb");
+```
+
+You can then retrieve a `IDocumentStore` instance using dependency injection, for example:
+
+```csharp
+public class MyService
+{
+ private readonly IDocumentStore _documentStore;
+ public MyService(IDocumentStore documentStore)
+ {
+ _documentStore = documentStore;
+ }
+
+ // Your logic here
+}
+```
+
+## Configuration
+
+The .NET Aspire RavenDB Client component provides multiple options to configure the server 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.AddRavenDBClient()`:
+
+```csharp
+builder.AddRavenDBClient("ravendb");
+```
+
+And then the connection string will be retrieved from the `ConnectionStrings` configuration section:
+
+```json
+{
+ "ConnectionStrings": {
+ "ravendb": "URL=http://localhost:8080;Database=ravenDatabase"
+ }
+}
+```
+
+### Use configuration providers
+
+The .NET Aspire RavenDB Client component supports [Microsoft.Extensions.Configuration](https://learn.microsoft.com/dotnet/api/microsoft.extensions.configuration). It loads the `RavenDBClientSettings` from configuration by using the `Aspire:RavenDB:Client` key.
+
+### Use inline delegates
+
+Also you can pass the `Action configureSettings` delegate to set up some or all the options inline, for example to set the database name and certificate from code:
+
+```csharp
+builder.AddRavenDBClient("ravendb", settings =>
+{
+ settings.DatabaseName = "ravenDatabase";
+ settings.Certificate = ravenCertificate;
+});
+```
+
+### Use RavenDBClientSettings Class
+
+The RavenDBClientSettings class simplifies configuration by allowing you to specify:
+- URLs of your RavenDB nodes.
+- Database name to connect to or create.
+- Certificate details (via `CertificatePath` and `CertificatePassword` or `Certificate`).
+- Optional actions to modify the `IDocumentStore`.
+
+Example for creating a new database on a local unsecured RavenDB server:
+
+```csharp
+var settings = new RavenDBClientSettings(new[] { “http://127.0.0.1:8080” }, “NorthWind”)
+{
+ CreateDatabase = true;
+};
+builder.AddRavenDBClient(settings);
+```
+
+You can also configure:
+- `DisableHealthChecks` to disable health checks.
+- `HealthCheckTimeout` to set the timeout for health checks.
+- `DisableTracing` to disable `OpenTelemetry` tracing.
+
+## AppHost extensions
+
+### Install the CommunityToolkit.Aspire.Hosting.RavenDB Library
+
+Install the `CommunityToolkit.Aspire.Hosting.RavenDB` library with [NuGet](https://www.nuget.org):
+```dotnetcli
+dotnet add package CommunityToolkit.Aspire.Hosting.RavenDB
+```
+*To be added once the package is published.*
+
+### Usage in AppHost
+
+In your AppHost's _Program.cs_ file, register a RavenDB server resource and consume the connection using the following methods:
+
+```csharp
+var elasticsearch = builder.AddRavenDB("ravendb");
+
+var myService = builder.AddProject()
+ .WithReference(ravendb);
+```
+
+The `WithReference` method configures a connection in the `MyService` project named `ravendb`. In the _Program.cs_ file of `MyService`, the RavenDB connection can be consumed using:
+
+```csharp
+builder.AddRavenDBClient("ravendb");
+```
+
+## Additional Documentation
+
+- https://ravendb.net/docs/article-page/6.2/csharp
+- https://github.com/ravendb/ravendb
+- https://learn.microsoft.com/dotnet/aspire/community-toolkit/ravendb
+
+## Feedback & Contributing
+
+https://github.com/CommunityToolkit/Aspire
\ No newline at end of file
diff --git a/src/CommunityToolkit.Aspire.RavenDB.Client/RavenDBClientExtension.cs b/src/CommunityToolkit.Aspire.RavenDB.Client/RavenDBClientExtension.cs
new file mode 100644
index 00000000..c9e2e61a
--- /dev/null
+++ b/src/CommunityToolkit.Aspire.RavenDB.Client/RavenDBClientExtension.cs
@@ -0,0 +1,302 @@
+using CommunityToolkit.Aspire.RavenDB.Client;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Raven.Client.Documents;
+using Raven.Client.Documents.Session;
+using Raven.Client.ServerWide;
+using Raven.Client.ServerWide.Operations;
+using System.Data.Common;
+
+namespace Microsoft.Extensions.Hosting;
+
+///
+/// Extension methods for connecting RavenDB database.
+///
+public static class RavenDBClientExtension
+{
+ private const string ActivityNameSource = "RavenDB.Client.DiagnosticSources";
+
+ private const string DefaultConfigSectionName = "Aspire:RavenDB:Client";
+
+ ///
+ /// Registers and the associated and
+ /// instances for connecting to an existing or new RavenDB database with RavenDB.Client.
+ ///
+ /// The used to add services.
+ /// The name used to retrieve the connection string from the "ConnectionStrings" configuration section.
+ /// An optional delegate that can be used for customizing options. It is invoked after the settings are read from the configuration.
+ /// Notes:
+ ///
+ /// - Reads the configuration from "Aspire:RavenDB:Client" section.
+ /// - The is registered as a singleton, meaning a single instance is shared throughout the application's lifetime,
+ /// while and are registered per request to ensure short-lived session instances for each use.
+ ///
+ ///
+ public static void AddRavenDBClient(
+ this IHostApplicationBuilder builder,
+ string connectionName,
+ Action? configureSettings = null)
+ {
+ var settings = GetRavenDBClientSettings(builder, connectionName, configureSettings);
+
+ builder.AddRavenDBClientInternal(settings);
+ }
+
+ ///
+ /// Registers and the associated and
+ /// instances for connecting to an existing or new RavenDB database with RavenDB.Client, identified by a unique service key.
+ ///
+ /// The used to add services.
+ /// A unique key that identifies this instance of the RavenDB client service.
+ /// The name used to retrieve the connection string from the "ConnectionStrings" configuration section.
+ /// An optional delegate that can be used for customizing options. It is invoked after the settings are read from the configuration.
+ /// Notes:
+ ///
+ /// - Reads the configuration from "Aspire:RavenDB:Client" section.
+ /// - The is registered as a singleton, meaning a single instance is shared throughout the application's lifetime,
+ /// while and are registered per request to ensure short-lived session instances for each use.
+ ///
+ ///
+ public static void AddKeyedRavenDBClient(
+ this IHostApplicationBuilder builder,
+ object serviceKey,
+ string connectionName,
+ Action? configureSettings = null)
+ {
+ var settings = GetRavenDBClientSettings(builder, connectionName, configureSettings);
+
+ builder.AddRavenDBClientInternal(settings, serviceKey);
+ }
+
+ ///
+ /// Registers and the associated and
+ /// instances for connecting to an existing or new RavenDB database with RavenDB.Client.
+ ///
+ /// The used to add services.
+ /// The settings required to configure the .
+ /// Notes:
+ ///
+ /// - If is not specified and is set to 'false',
+ /// and will not be registered.
+ /// - The is registered as a singleton, meaning a single instance is shared throughout the application's lifetime,
+ /// while and are registered per request to ensure short-lived session instances for each use.
+ ///
+ ///
+ public static void AddRavenDBClient(
+ this IHostApplicationBuilder builder,
+ RavenDBClientSettings settings)
+ {
+ builder.AddRavenDBClientInternal(settings);
+ }
+
+ ///
+ /// Registers and the associated and
+ /// instances for connecting to an existing or new RavenDB database with RavenDB.Client, identified by a unique service key.
+ ///
+ /// The used to add services.
+ /// A unique key that identifies this instance of the RavenDB client service.
+ /// The settings required to configure the .
+ /// Notes:
+ ///
+ /// - If is not specified and is set to 'false',
+ /// and will not be registered.
+ /// - The is registered as a singleton, meaning a single instance is shared throughout the application's lifetime,
+ /// while and are registered per request to ensure short-lived session instances for each use.
+ ///
+ ///
+ public static void AddKeyedRavenDBClient(
+ this IHostApplicationBuilder builder,
+ object serviceKey,
+ RavenDBClientSettings settings)
+ {
+ builder.AddRavenDBClientInternal(settings, serviceKey);
+ }
+
+ private static void AddRavenDBClientInternal(
+ this IHostApplicationBuilder builder,
+ RavenDBClientSettings settings,
+ object? serviceKey = null)
+ {
+ ValidateSettings(builder, settings);
+
+ var documentStore = CreateRavenClient(settings);
+
+ if (serviceKey is null)
+ {
+ builder
+ .Services
+ .AddSingleton(documentStore);
+ }
+ else
+ {
+ builder
+ .Services
+ .AddKeyedSingleton(serviceKey, documentStore);
+ }
+
+ builder.AddRavenDocumentSession(documentStore, serviceKey);
+
+ if (!settings.DisableTracing)
+ {
+ builder.Services.AddOpenTelemetry()
+ .WithTracing(tracing =>
+ {
+ tracing.AddSource(ActivityNameSource);
+ });
+ }
+
+ builder.AddHealthCheck(
+ serviceKey is null ? "RavenDB.Client" : $"RavenDB.Client_{serviceKey}",
+ settings);
+ }
+
+ private static void AddRavenDocumentSession(
+ this IHostApplicationBuilder builder,
+ IDocumentStore documentStore,
+ object? serviceKey)
+ {
+ if (string.IsNullOrWhiteSpace(documentStore.Database))
+ return;
+
+ // AddTransient creates new instance per request/usage which is ideal for document sessions
+
+ if (serviceKey is null)
+ {
+ builder.Services.AddTransient(provider =>
+ provider.CreateDocumentSession(documentStore));
+
+ builder.Services.AddTransient(provider =>
+ provider.CreateAsyncDocumentSession(documentStore));
+
+ return;
+ }
+
+ builder.Services.AddKeyedTransient(serviceKey,
+ (sp, _) => sp.CreateDocumentSession(documentStore));
+
+ builder.Services.AddKeyedTransient(serviceKey,
+ (sp, _) => sp.CreateAsyncDocumentSession(documentStore));
+ }
+
+ private static void AddHealthCheck(
+ this IHostApplicationBuilder builder,
+ string healthCheckName,
+ RavenDBClientSettings settings)
+ {
+ if (settings.DisableHealthChecks)
+ return;
+
+ builder.TryAddHealthCheck(
+ healthCheckName,
+ healthCheck => healthCheck.AddRavenDB(options =>
+ {
+ options.Database = settings.DatabaseName;
+ options.Urls = settings.Urls!;
+ options.Certificate = settings.GetCertificate();
+ },
+ healthCheckName,
+ null,
+ null,
+ settings.HealthCheckTimeout > 0 ? TimeSpan.FromMilliseconds(settings.HealthCheckTimeout.Value) : null));
+ }
+
+ private static IDocumentStore CreateRavenClient(RavenDBClientSettings ravenDbClientSettings)
+ {
+ var documentStore = new DocumentStore()
+ {
+ Urls = ravenDbClientSettings.Urls,
+ Database = ravenDbClientSettings.DatabaseName,
+ Certificate = ravenDbClientSettings.GetCertificate()
+ };
+
+ ravenDbClientSettings.ModifyDocumentStore?.Invoke(documentStore);
+
+ documentStore.Initialize();
+
+ if (ravenDbClientSettings.CreateDatabase)
+ {
+ var databaseRecord = documentStore.Maintenance.Server.Send(new GetDatabaseRecordOperation(ravenDbClientSettings.DatabaseName));
+ if (databaseRecord == null)
+ {
+ var newDatabaseRecord = new DatabaseRecord(ravenDbClientSettings.DatabaseName);
+ documentStore.Maintenance.Server.Send(new CreateDatabaseOperation(newDatabaseRecord));
+ }
+ }
+
+ return documentStore;
+ }
+
+ private static IDocumentSession CreateDocumentSession(this IServiceProvider provider,
+ IDocumentStore documentStore) => documentStore.OpenSession();
+
+ private static IAsyncDocumentSession CreateAsyncDocumentSession(this IServiceProvider provider,
+ IDocumentStore documentStore) => documentStore.OpenAsyncSession();
+
+ private static RavenDBClientSettings GetRavenDBClientSettings(this IHostApplicationBuilder builder,
+ string connectionName,
+ Action? configureSettings)
+ {
+ var configSection = builder.Configuration.GetSection(DefaultConfigSectionName);
+ var namedConfigSection = configSection.GetSection(connectionName);
+
+ var settings = new RavenDBClientSettings();
+ configSection.Bind(settings);
+ namedConfigSection.Bind(settings);
+
+ var connectionString = builder.Configuration.GetConnectionString(connectionName);
+ if (string.IsNullOrEmpty(connectionString) == false)
+ {
+ var connectionBuilder = new DbConnectionStringBuilder
+ {
+ ConnectionString = connectionString
+ };
+
+ if (connectionBuilder.TryGetValue("URL", out var url) && url is string serverUrl)
+ {
+ settings.Urls = new[] { serverUrl };
+ }
+
+ if (connectionBuilder.TryGetValue("Database", out var database) && database is string databaseName)
+ {
+ settings.DatabaseName = databaseName;
+ }
+ }
+
+ configureSettings?.Invoke(settings);
+
+ return settings;
+ }
+
+ private static void ValidateSettings(
+ IHostApplicationBuilder builder,
+ RavenDBClientSettings settings)
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+
+ if (settings.Urls is null || settings.Urls.Length == 0)
+ throw new ArgumentNullException(nameof(settings.Urls), "At least one connection URL must be provided.");
+
+ if (settings.CreateDatabase && string.IsNullOrWhiteSpace(settings.DatabaseName))
+ throw new ArgumentNullException(nameof(settings.DatabaseName), "A database name must be specified in 'RavenDBClientSettings.DatabaseName' " +
+ "when 'RavenDBClientSettings.CreateDatabase' is set to true.");
+
+ foreach (var url in settings.Urls)
+ {
+ if (IsValidUrl(url, out var uri) == false)
+ throw new ArgumentException($"The provided URL '{url}' is invalid. Please provide a valid HTTP or HTTPS URL.");
+
+ if (uri!.Scheme == Uri.UriSchemeHttps)
+ {
+ if (string.IsNullOrEmpty(settings.CertificatePath) && settings.Certificate == null)
+ throw new ArgumentNullException(nameof(settings.Certificate), "A valid certificate must be provided in 'RavenDBClientSettings.Certificate' " +
+ "or a certificate path in 'RavenDBClientSettings.CertificatePath' when using HTTPS.");
+ }
+ }
+
+ bool IsValidUrl(string url, out Uri? uriResult)
+ {
+ return Uri.TryCreate(url, UriKind.Absolute, out uriResult)
+ && (uriResult.Scheme == Uri.UriSchemeHttp || uriResult.Scheme == Uri.UriSchemeHttps);
+ }
+ }
+}
diff --git a/src/CommunityToolkit.Aspire.RavenDB.Client/RavenDBClientSettings.cs b/src/CommunityToolkit.Aspire.RavenDB.Client/RavenDBClientSettings.cs
new file mode 100644
index 00000000..a923c990
--- /dev/null
+++ b/src/CommunityToolkit.Aspire.RavenDB.Client/RavenDBClientSettings.cs
@@ -0,0 +1,84 @@
+using Raven.Client.Documents;
+using System.Security.Cryptography.X509Certificates;
+
+namespace CommunityToolkit.Aspire.RavenDB.Client;
+
+///
+/// Provides the client configuration settings for connecting to a RavenDB database.
+///
+public sealed class RavenDBClientSettings
+{
+ ///
+ /// The URLs of the RavenDB server nodes.
+ ///
+ public string[]? Urls { get; set; }
+
+ ///
+ /// The path to the certificate file.
+ ///
+ public string? CertificatePath { get; set; }
+
+ ///
+ /// The password for the certificate.
+ ///
+ public string? CertificatePassword { get; set; }
+
+ ///
+ /// The certificate for RavenDB server.
+ ///
+ public X509Certificate2? Certificate { get; set; }
+
+ ///
+ /// The name of the database to connect to.
+ ///
+ public string? DatabaseName { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether a new database should be created if it does not already exist.
+ /// If set to and a database with the specified name already exists, the existing database will be used.
+ /// The default value is .
+ ///
+ public bool CreateDatabase { get; set; }
+
+ ///
+ /// Action that allows modifications of the .
+ ///
+ public Action? ModifyDocumentStore { get; set; }
+
+ ///
+ /// Gets or sets a boolean value that indicates whether RavenDB health check is disabled or not.
+ /// The default value is .
+ ///
+ public bool DisableHealthChecks { get; set; }
+
+ ///
+ /// Gets or sets the timeout in milliseconds for the RavenDB health check.
+ ///
+ public int? HealthCheckTimeout { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether OpenTelemetry tracing is disabled.
+ /// The default value is .
+ ///
+ public bool DisableTracing { get; set; }
+
+ ///
+ /// Retrieves the used for authentication, if a certificate path is specified.
+ ///
+ /// An instance if the is specified;
+ /// otherwise, .
+ internal X509Certificate2? GetCertificate()
+ {
+ if (Certificate != null)
+ return Certificate;
+
+ if (string.IsNullOrEmpty(CertificatePath))
+ {
+ return null;
+ }
+
+#pragma warning disable SYSLIB0057
+ return new X509Certificate2(CertificatePath, CertificatePassword);
+#pragma warning restore SYSLIB0057
+ }
+}
diff --git a/tests/CommunityToolkit.Aspire.Hosting.RavenDB.Tests/AddRavenDBTests.cs b/tests/CommunityToolkit.Aspire.Hosting.RavenDB.Tests/AddRavenDBTests.cs
new file mode 100644
index 00000000..6dda5317
--- /dev/null
+++ b/tests/CommunityToolkit.Aspire.Hosting.RavenDB.Tests/AddRavenDBTests.cs
@@ -0,0 +1,136 @@
+using Aspire.Hosting;
+
+namespace CommunityToolkit.Aspire.Hosting.RavenDB.Tests;
+
+public class AddRavenDBTests
+{
+ [Fact]
+ public void AddRavenServerResource()
+ {
+ var appBuilder = DistributedApplication.CreateBuilder();
+ appBuilder.AddRavenDB("ravenServer");
+ using var app = appBuilder.Build();
+
+ var appModel = app.Services.GetRequiredService();
+
+ var serverResource = Assert.Single(appModel.Resources.OfType());
+ Assert.Equal("ravenServer", serverResource.Name);
+ }
+
+ [Fact]
+ public void AddRavenServerAndDatabaseResource()
+ {
+ var appBuilder = DistributedApplication.CreateBuilder();
+ appBuilder.AddRavenDB("ravenServer").AddDatabase(name: "ravenDatabase", databaseName: "TestDatabase");
+ using var app = appBuilder.Build();
+
+ var appModel = app.Services.GetRequiredService();
+
+ var serverResource = Assert.Single(appModel.Resources.OfType());
+ var databaseResource = Assert.Single(appModel.Resources.OfType());
+
+ Assert.Equal("ravenServer", serverResource.Name);
+ Assert.Equal("ravenDatabase", databaseResource.Name);
+ Assert.Equal("TestDatabase", databaseResource.DatabaseName);
+ Assert.True(serverResource.Databases.TryGetValue("ravenDatabase", out var databaseName));
+ Assert.Equal("TestDatabase", databaseName);
+ }
+
+ [Fact]
+ public void VerifyNonDefaultImageTag()
+ {
+ var tag = "windows-latest-lts";
+
+ var builder = DistributedApplication.CreateBuilder();
+ builder.AddRavenDB("raven").WithImageTag(tag);
+
+ using var app = builder.Build();
+
+ var appModel = app.Services.GetRequiredService();
+
+ var resource = Assert.Single(appModel.Resources.OfType());
+
+ Assert.True(resource.TryGetAnnotationsOfType(out var annotations));
+ var annotation = Assert.Single(annotations);
+ Assert.NotNull(annotation.Tag);
+ Assert.Equal(tag, annotation.Tag);
+ }
+
+ [Fact]
+ public void VerifyDefaultPort()
+ {
+ var builder = DistributedApplication.CreateBuilder();
+ builder.AddRavenDB("raven");
+
+ using var app = builder.Build();
+
+ var appModel = app.Services.GetRequiredService();
+
+ var resource = Assert.Single(appModel.Resources.OfType());
+
+ var endpoint = Assert.Single(resource.Annotations.OfType());
+
+ Assert.Equal(8080, endpoint.TargetPort);
+ }
+
+ [Fact]
+ public void SpecifiedDataVolumeNameIsUsed()
+ {
+ var builder = DistributedApplication.CreateBuilder();
+ _ = builder.AddRavenDB("raven").WithDataVolume("data");
+
+ using var app = builder.Build();
+
+ var appModel = app.Services.GetRequiredService();
+
+ var resource = Assert.Single(appModel.Resources.OfType());
+
+ Assert.True(resource.TryGetAnnotationsOfType(out var annotations));
+
+ var annotation = Assert.Single(annotations);
+
+ Assert.Equal("data", annotation.Source);
+ }
+
+ [Theory]
+ [InlineData("data")]
+ [InlineData(null)]
+ public void CorrectTargetPathOnVolumeMount(string? volumeName)
+ {
+ var builder = DistributedApplication.CreateBuilder();
+ _ = builder.AddRavenDB("raven").WithDataVolume(volumeName);
+
+ using var app = builder.Build();
+
+ var appModel = app.Services.GetRequiredService();
+
+ var resource = Assert.Single(appModel.Resources.OfType());
+
+ Assert.True(resource.TryGetAnnotationsOfType(out var annotations));
+
+ var annotation = Assert.Single(annotations);
+
+ Assert.Equal("/var/lib/ravendb/data", annotation.Target);
+ }
+
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public void ReadOnlyVolumeMount(bool isReadOnly)
+ {
+ var builder = DistributedApplication.CreateBuilder();
+ _ = builder.AddRavenDB("raven").WithDataVolume(isReadOnly: isReadOnly);
+
+ using var app = builder.Build();
+
+ var appModel = app.Services.GetRequiredService();
+
+ var resource = Assert.Single(appModel.Resources.OfType());
+
+ Assert.True(resource.TryGetAnnotationsOfType(out var annotations));
+
+ var annotation = Assert.Single(annotations);
+
+ Assert.Equal(isReadOnly, annotation.IsReadOnly);
+ }
+}
diff --git a/tests/CommunityToolkit.Aspire.Hosting.RavenDB.Tests/AppHostTests.cs b/tests/CommunityToolkit.Aspire.Hosting.RavenDB.Tests/AppHostTests.cs
new file mode 100644
index 00000000..7279cac9
--- /dev/null
+++ b/tests/CommunityToolkit.Aspire.Hosting.RavenDB.Tests/AppHostTests.cs
@@ -0,0 +1,70 @@
+using Aspire.Components.Common.Tests;
+using CommunityToolkit.Aspire.Testing;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Hosting;
+using Raven.Client.Documents;
+
+namespace CommunityToolkit.Aspire.Hosting.RavenDB.Tests;
+
+[RequiresDocker]
+public class AppHostTests(AspireIntegrationTestFixture fixture) : IClassFixture>
+{
+ [Fact]
+ public async Task TestAppHost()
+ {
+ using var cancellationToken = new CancellationTokenSource();
+ cancellationToken.CancelAfter(TimeSpan.FromMinutes(5));
+
+ var connectionName = "ravendb";
+ var databaseName = "ravenDatabase";
+
+ await fixture.ResourceNotificationService.WaitForResourceAsync(connectionName, KnownResourceStates.Running, cancellationToken.Token).WaitAsync(TimeSpan.FromMinutes(5), cancellationToken.Token);
+
+ var endpoint = fixture.GetEndpoint(connectionName, "http");
+ Assert.NotNull(endpoint);
+ Assert.False(string.IsNullOrWhiteSpace(endpoint.OriginalString));
+ Assert.True(endpoint.Scheme == Uri.UriSchemeHttp);
+
+ var appModel = fixture.App.Services.GetRequiredService();
+ var serverResource = Assert.Single(appModel.Resources.OfType());
+ var dbResource = Assert.Single(appModel.Resources.OfType());
+
+ var serverConnectionString = await serverResource.ConnectionStringExpression.GetValueAsync(cancellationToken.Token);
+ Assert.False(string.IsNullOrWhiteSpace(serverConnectionString));
+ Assert.Contains(endpoint.OriginalString, serverConnectionString);
+ Assert.Equal(databaseName, dbResource.DatabaseName);
+
+ var databaseConnectionString = await dbResource.ConnectionStringExpression.GetValueAsync(cancellationToken.Token);
+ Assert.False(string.IsNullOrWhiteSpace(databaseConnectionString));
+ Assert.Equal($"URL={endpoint.OriginalString};Database={databaseName}", databaseConnectionString);
+ Assert.Equal(databaseName, dbResource.DatabaseName);
+
+ // Create RavenDB Client
+
+ var clientBuilder = Host.CreateApplicationBuilder();
+ clientBuilder.Configuration.AddInMemoryCollection([
+ new KeyValuePair($"ConnectionStrings:{connectionName}", databaseConnectionString)
+ ]);
+
+ clientBuilder.AddRavenDBClient(connectionName: connectionName, configureSettings: settings =>
+ {
+ settings.CreateDatabase = true;
+ });
+ var host = clientBuilder.Build();
+
+ using var documentStore = host.Services.GetRequiredService();
+
+ using (var session = documentStore.OpenAsyncSession())
+ {
+ await session.StoreAsync(new { Id = "Test/1", Name = "Test Document" }, cancellationToken.Token);
+ await session.SaveChangesAsync(cancellationToken.Token);
+ }
+
+ using (var session = documentStore.OpenAsyncSession())
+ {
+ var doc = await session.LoadAsync("Test/1", cancellationToken.Token);
+ Assert.NotNull(doc);
+ Assert.Equal("Test Document", doc.Name.ToString());
+ }
+ }
+}
diff --git a/tests/CommunityToolkit.Aspire.Hosting.RavenDB.Tests/CommunityToolkit.Aspire.Hosting.RavenDB.Tests.csproj b/tests/CommunityToolkit.Aspire.Hosting.RavenDB.Tests/CommunityToolkit.Aspire.Hosting.RavenDB.Tests.csproj
new file mode 100644
index 00000000..c13e6aa6
--- /dev/null
+++ b/tests/CommunityToolkit.Aspire.Hosting.RavenDB.Tests/CommunityToolkit.Aspire.Hosting.RavenDB.Tests.csproj
@@ -0,0 +1,15 @@
+
+
+
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/CommunityToolkit.Aspire.RavenDB.Client.Tests/AspireRavenDBExtensionsTests.cs b/tests/CommunityToolkit.Aspire.RavenDB.Client.Tests/AspireRavenDBExtensionsTests.cs
new file mode 100644
index 00000000..dedebb75
--- /dev/null
+++ b/tests/CommunityToolkit.Aspire.RavenDB.Client.Tests/AspireRavenDBExtensionsTests.cs
@@ -0,0 +1,377 @@
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Diagnostics.HealthChecks;
+using Microsoft.Extensions.Hosting;
+using Raven.Client.Documents;
+using Raven.Client.Documents.Session;
+using Raven.Client.Http;
+
+namespace CommunityToolkit.Aspire.RavenDB.Client.Tests;
+
+public class AspireRavenDBExtensionsTests(RavenDbServerFixture serverFixture) : IClassFixture
+{
+ private const string DefaultConnectionName = "ravendb";
+ private string DefaultConnectionString => serverFixture.ConnectionString ??
+ throw new InvalidOperationException("The server was not initialized.");
+
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public void AddKeyedRavenDbClient_ReadsFromConnectionStringsCorrectly(bool shouldCreateDatabase)
+ {
+ var builder = CreateBuilder();
+
+ string? databaseName = null;
+ Action? configSettings = null;
+ if (shouldCreateDatabase)
+ {
+ databaseName = Guid.NewGuid().ToString("N");
+ configSettings = clientSettings =>
+ {
+ clientSettings.DatabaseName = databaseName;
+ clientSettings.CreateDatabase = true;
+ };
+ }
+
+ builder.AddKeyedRavenDBClient(serviceKey: DefaultConnectionName, connectionName: DefaultConnectionName, configureSettings: configSettings);
+ using var host = builder.Build();
+
+ using var documentStore = host.Services.GetRequiredKeyedService(DefaultConnectionName);
+ Assert.Equal(DefaultConnectionString, documentStore.Urls[0]);
+
+ if (shouldCreateDatabase)
+ {
+ Assert.Equal(databaseName, documentStore.Database);
+
+ using var session = host.Services.GetRequiredKeyedService(DefaultConnectionName);
+ Assert.NotNull(session);
+ }
+ }
+
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public void AddRavenDbClientWithSettingsShouldWork(bool shouldCreateDatabase)
+ {
+ var builder = CreateBuilder();
+
+ string? databaseName = null;
+ if (shouldCreateDatabase)
+ databaseName = Guid.NewGuid().ToString("N");
+
+ var settings = GetDefaultSettings(databaseName, shouldCreateDatabase);
+
+ builder.AddRavenDBClient(settings: settings);
+ using var host = builder.Build();
+
+ using var documentStore = host.Services.GetRequiredService();
+ Assert.Equal(DefaultConnectionString, documentStore.Urls[0]);
+
+ if (shouldCreateDatabase)
+ {
+ Assert.Equal(databaseName, documentStore.Database);
+
+ using var session = host.Services.GetRequiredService();
+ Assert.NotNull(session);
+ }
+ }
+
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public void AddKeyedRavenDbClientWithSettingsShouldWork(bool shouldCreateDatabase)
+ {
+ var builder = CreateBuilder();
+
+ string? databaseName = null;
+ if (shouldCreateDatabase)
+ databaseName = Guid.NewGuid().ToString("N");
+
+ var settings = GetDefaultSettings(databaseName, shouldCreateDatabase);
+
+ builder.AddKeyedRavenDBClient(serviceKey: DefaultConnectionName, settings: settings);
+ using var host = builder.Build();
+
+ using var documentStore = host.Services.GetRequiredKeyedService(DefaultConnectionName);
+ Assert.Equal(DefaultConnectionString, documentStore.Urls[0]);
+
+ if (shouldCreateDatabase)
+ {
+ Assert.Equal(databaseName, documentStore.Database);
+
+ using var session = host.Services.GetRequiredKeyedService(DefaultConnectionName);
+ using var asyncSession = host.Services.GetRequiredKeyedService(DefaultConnectionName);
+ Assert.NotNull(session);
+ Assert.NotNull(asyncSession);
+ }
+ }
+
+ [Fact]
+ public void AddKeyedRavenDbClientAndOpen2SessionShouldNotBeEqual()
+ {
+ var databaseName = Guid.NewGuid().ToString("N");
+
+ var builder = CreateBuilder();
+ builder.AddKeyedRavenDBClient(serviceKey: DefaultConnectionName, connectionName: DefaultConnectionName, configureSettings: clientSettings =>
+ {
+ clientSettings.DatabaseName = databaseName;
+ clientSettings.CreateDatabase = true;
+ });
+ using var host = builder.Build();
+
+ using var documentStore = host.Services.GetRequiredKeyedService(DefaultConnectionName);
+
+ using var session1 = host.Services.GetRequiredKeyedService(DefaultConnectionName);
+ using var session2 = host.Services.GetRequiredKeyedService(DefaultConnectionName);
+
+ Assert.NotEqual(session1, session2);
+ }
+
+ [Fact]
+ public void AddRavenDbClientAndOpen2SessionShouldNotBeEqual()
+ {
+ var databaseName = Guid.NewGuid().ToString("N");
+
+ var builder = CreateBuilder();
+ builder.AddRavenDBClient(connectionName: DefaultConnectionName, configureSettings: clientSettings =>
+ {
+ clientSettings.DatabaseName = databaseName;
+ clientSettings.CreateDatabase = true;
+ });
+ using var host = builder.Build();
+
+ using var documentStore = host.Services.GetRequiredService();
+
+ using var session1 = host.Services.GetRequiredService();
+ using var session2 = host.Services.GetRequiredService();
+
+ Assert.NotEqual(session1, session2);
+ }
+
+ [Fact]
+ public void AddKeyedRavenDbClientThenGetRequiredServiceShouldThrow()
+ {
+ var databaseName = Guid.NewGuid().ToString("N");
+
+ var builder = CreateBuilder();
+ builder.AddKeyedRavenDBClient(serviceKey: DefaultConnectionName, connectionName: DefaultConnectionName, configureSettings: settings => settings.DatabaseName = databaseName);
+ using var host = builder.Build();
+
+ Assert.Throws(() => host.Services.GetRequiredService());
+ }
+
+ [Fact]
+ public void AddKeyedRavenDbClientShouldWork_Create2NewDatabases()
+ {
+ var databaseName1 = Guid.NewGuid().ToString("N");
+ var databaseName2 = Guid.NewGuid().ToString("N");
+
+ var connectionName1 = Guid.NewGuid().ToString("N");
+ var connectionName2 = Guid.NewGuid().ToString("N");
+
+ IEnumerable> config =
+ [
+ new KeyValuePair($"ConnectionStrings:{connectionName1}", $"URL={DefaultConnectionString}"),
+ new KeyValuePair($"ConnectionStrings:{connectionName2}", $"URL={DefaultConnectionString}")
+ ];
+ var builder = CreateBuilder(config);
+
+ builder.AddKeyedRavenDBClient(serviceKey: connectionName1, connectionName: connectionName1, configureSettings: clientSettings =>
+ {
+ clientSettings.DatabaseName = databaseName1;
+ clientSettings.CreateDatabase = true;
+ });
+ builder.AddKeyedRavenDBClient(serviceKey: connectionName2, connectionName: connectionName2, configureSettings: clientSettings =>
+ {
+ clientSettings.DatabaseName = databaseName2;
+ clientSettings.CreateDatabase = true;
+ });
+
+ using var host = builder.Build();
+
+ using var documentStore1 = host.Services.GetRequiredKeyedService(connectionName1);
+ using var documentStore2 = host.Services.GetRequiredKeyedService(connectionName2);
+
+ Assert.NotNull(documentStore1);
+ Assert.NotNull(documentStore2);
+
+ using var session1 = host.Services.GetRequiredKeyedService(connectionName1);
+ using var session2 = host.Services.GetRequiredKeyedService(connectionName2);
+
+ Assert.Equal(DefaultConnectionString, documentStore1.Urls[0]);
+ Assert.Equal(DefaultConnectionString, documentStore2.Urls[0]);
+
+ Assert.Equal(databaseName1, documentStore1.Database);
+ Assert.Equal(databaseName2, documentStore2.Database);
+
+ Assert.NotEqual(session1, session2);
+ }
+
+ [Fact]
+ public async Task AddRavenDbClient_HealthCheckShouldBeRegisteredWhenEnabled()
+ {
+ var databaseName = Guid.NewGuid().ToString("N");
+
+ var builder = CreateBuilder();
+
+ builder.AddRavenDBClient(connectionName: DefaultConnectionName, configureSettings: clientSettings =>
+ {
+ clientSettings.CreateDatabase = true;
+ clientSettings.DatabaseName = databaseName;
+ });
+
+ using var host = builder.Build();
+
+ var healthCheckService = host.Services.GetRequiredService();
+
+ var healthCheckReport = await healthCheckService.CheckHealthAsync();
+
+ var healthCheckName = "RavenDB.Client";
+
+ Assert.Contains(healthCheckReport.Entries, x => x.Key == healthCheckName);
+ }
+
+ [Fact]
+ public void AddRavenDbClient_HealthCheckShouldNotBeRegisteredWhenDisabled()
+ {
+ var databaseName = Guid.NewGuid().ToString("N");
+
+ var builder = CreateBuilder();
+
+ builder.AddRavenDBClient(connectionName: DefaultConnectionName, configureSettings: clientSettings =>
+ {
+ clientSettings.CreateDatabase = true;
+ clientSettings.DatabaseName = databaseName;
+ clientSettings.DisableHealthChecks = true;
+ });
+
+ using var host = builder.Build();
+
+ var healthCheckService = host.Services.GetService();
+
+ Assert.Null(healthCheckService);
+ }
+
+ [Fact]
+ public async Task AddKeyedRavenDbClient_HealthCheckShouldBeRegisteredWhenEnabled()
+ {
+ var databaseName = Guid.NewGuid().ToString("N");
+
+ var builder = CreateBuilder();
+
+ builder.AddKeyedRavenDBClient(serviceKey: DefaultConnectionName, connectionName: DefaultConnectionName, configureSettings: clientSettings =>
+ {
+ clientSettings.CreateDatabase = true;
+ clientSettings.DatabaseName = databaseName;
+ });
+
+ using var host = builder.Build();
+
+ var healthCheckService = host.Services.GetRequiredService();
+
+ var healthCheckReport = await healthCheckService.CheckHealthAsync();
+
+ var healthCheckName = $"RavenDB.Client_{DefaultConnectionName}";
+
+ Assert.Contains(healthCheckReport.Entries, x => x.Key == healthCheckName);
+ }
+
+ [Fact]
+ public void AddKeyedRavenDbClient_HealthCheckShouldNotBeRegisteredWhenDisabled()
+ {
+ var databaseName = Guid.NewGuid().ToString("N");
+
+ var builder = CreateBuilder();
+
+ builder.AddKeyedRavenDBClient(serviceKey: DefaultConnectionName, connectionName: DefaultConnectionName, configureSettings: clientSettings =>
+ {
+ clientSettings.CreateDatabase = true;
+ clientSettings.DatabaseName = databaseName;
+ clientSettings.DisableHealthChecks = true;
+ });
+
+ using var host = builder.Build();
+
+ var healthCheckService = host.Services.GetService();
+
+ Assert.Null(healthCheckService);
+ }
+
+ [Fact]
+ public void AddRavenDbClient_ModifyDocumentStore()
+ {
+ var databaseName = Guid.NewGuid().ToString("N");
+
+ var builder = CreateBuilder();
+
+ builder.AddRavenDBClient(connectionName: DefaultConnectionName, configureSettings: clientSettings =>
+ {
+ clientSettings.CreateDatabase = true;
+ clientSettings.DatabaseName = databaseName;
+ clientSettings.ModifyDocumentStore = store =>
+ {
+ store.Conventions.ReadBalanceBehavior = ReadBalanceBehavior.RoundRobin;
+ store.Conventions.MaxNumberOfRequestsPerSession = 99;
+ };
+ });
+
+ using var host = builder.Build();
+
+ using var documentStore = host.Services.GetRequiredService();
+
+ Assert.NotNull(documentStore);
+ Assert.Equal(databaseName, documentStore.Database);
+ Assert.Equal(ReadBalanceBehavior.RoundRobin, documentStore.Conventions.ReadBalanceBehavior);
+ Assert.Equal(99, documentStore.Conventions.MaxNumberOfRequestsPerSession);
+ }
+
+ [Fact]
+ public void AddKeyedRavenDbClient_ModifyDocumentStore()
+ {
+ var databaseName = Guid.NewGuid().ToString("N");
+
+ var builder = CreateBuilder();
+
+ builder.AddKeyedRavenDBClient(serviceKey: DefaultConnectionName, connectionName: DefaultConnectionName, configureSettings: clientSettings =>
+ {
+ clientSettings.CreateDatabase = true;
+ clientSettings.DatabaseName = databaseName;
+ clientSettings.ModifyDocumentStore = store =>
+ {
+ store.Conventions.ReadBalanceBehavior = ReadBalanceBehavior.RoundRobin;
+ store.Conventions.MaxNumberOfRequestsPerSession = 99;
+ };
+ });
+
+ using var host = builder.Build();
+
+ using var documentStore = host.Services.GetRequiredKeyedService(DefaultConnectionName);
+
+ Assert.NotNull(documentStore);
+ Assert.Equal(databaseName, documentStore.Database);
+ Assert.Equal(ReadBalanceBehavior.RoundRobin, documentStore.Conventions.ReadBalanceBehavior);
+ Assert.Equal(99, documentStore.Conventions.MaxNumberOfRequestsPerSession);
+ }
+
+ private HostApplicationBuilder CreateBuilder(IEnumerable>? config = null)
+ {
+ var builder = Host.CreateEmptyApplicationBuilder(null);
+
+ builder.Configuration.AddInMemoryCollection(config ?? GetDefaultConfiguration());
+ return builder;
+ }
+
+ private IEnumerable> GetDefaultConfiguration() =>
+ [
+ new KeyValuePair($"ConnectionStrings:{DefaultConnectionName}", $"URL={DefaultConnectionString}")
+ ];
+
+ private RavenDBClientSettings GetDefaultSettings(string? databaseName = null, bool shouldCreateDatabase = true)
+ {
+ return new RavenDBClientSettings
+ {
+ Urls = new[] { DefaultConnectionString },
+ DatabaseName = databaseName,
+ CreateDatabase = shouldCreateDatabase
+ };
+ }
+}
diff --git a/tests/CommunityToolkit.Aspire.RavenDB.Client.Tests/CommunityToolkit.Aspire.RavenDB.Client.Tests.csproj b/tests/CommunityToolkit.Aspire.RavenDB.Client.Tests/CommunityToolkit.Aspire.RavenDB.Client.Tests.csproj
new file mode 100644
index 00000000..b4528f84
--- /dev/null
+++ b/tests/CommunityToolkit.Aspire.RavenDB.Client.Tests/CommunityToolkit.Aspire.RavenDB.Client.Tests.csproj
@@ -0,0 +1,18 @@
+
+
+
+ Exe
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/CommunityToolkit.Aspire.RavenDB.Client.Tests/RavenDbServerFixture.cs b/tests/CommunityToolkit.Aspire.RavenDB.Client.Tests/RavenDbServerFixture.cs
new file mode 100644
index 00000000..2df45c92
--- /dev/null
+++ b/tests/CommunityToolkit.Aspire.RavenDB.Client.Tests/RavenDbServerFixture.cs
@@ -0,0 +1,42 @@
+using Raven.Client.Documents;
+using Raven.Embedded;
+
+namespace CommunityToolkit.Aspire.RavenDB.Client.Tests;
+
+public sealed class RavenDbServerFixture : IAsyncLifetime, IDisposable
+{
+ public EmbeddedServer Server { get; } = EmbeddedServer.Instance;
+ public IDocumentStore? Store { get; private set; }
+ public string? ConnectionString { get; private set; }
+
+ public async Task InitializeAsync()
+ {
+ Server.StartServer();
+
+ var uri = await Server.GetServerUriAsync();
+ var serverUrl = uri.OriginalString;
+ ConnectionString = serverUrl;
+
+ Store = new DocumentStore { Urls = new[] { serverUrl } };
+ }
+
+ public void CreateDatabase(string databaseName) =>
+ CreateDatabase(new DatabaseOptions(databaseName));
+
+ public void CreateDatabase(DatabaseOptions options)
+ {
+ Store = Server.GetDocumentStore(options);
+ }
+
+ public Task DisposeAsync()
+ {
+ Dispose();
+ return Task.CompletedTask;
+ }
+
+ public void Dispose()
+ {
+ Store?.Dispose();
+ Server.Dispose();
+ }
+}