Skip to content

Commit 22c77d9

Browse files
committed
feat: add mongodb container and connection support
1 parent 69937e2 commit 22c77d9

13 files changed

+506
-1
lines changed

Aspire.sln

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.MySqlConnector.Tests
158158
EndProject
159159
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestProject.IntegrationServiceA", "tests\testproject\TestProject.IntegrationServiceA\TestProject.IntegrationServiceA.csproj", "{DCF2D47A-921A-4900-B5B2-CF97B3531CE8}"
160160
EndProject
161+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aspire.MongoDB.Driver", "src\Components\Aspire.MongoDB.Driver\Aspire.MongoDB.Driver.csproj", "{20A5A907-A135-4735-B4BF-E13514F360E3}"
162+
EndProject
163+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aspire.MongoDB.Driver.Tests", "tests\Aspire.MongoDB.Driver.Tests\Aspire.MongoDB.Driver.Tests.csproj", "{E592E447-BA3C-44FA-86C1-EBEDC864A644}"
164+
EndProject
161165
Global
162166
GlobalSection(SolutionConfigurationPlatforms) = preSolution
163167
Debug|Any CPU = Debug|Any CPU
@@ -424,6 +428,14 @@ Global
424428
{DCF2D47A-921A-4900-B5B2-CF97B3531CE8}.Debug|Any CPU.Build.0 = Debug|Any CPU
425429
{DCF2D47A-921A-4900-B5B2-CF97B3531CE8}.Release|Any CPU.ActiveCfg = Release|Any CPU
426430
{DCF2D47A-921A-4900-B5B2-CF97B3531CE8}.Release|Any CPU.Build.0 = Release|Any CPU
431+
{20A5A907-A135-4735-B4BF-E13514F360E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
432+
{20A5A907-A135-4735-B4BF-E13514F360E3}.Debug|Any CPU.Build.0 = Debug|Any CPU
433+
{20A5A907-A135-4735-B4BF-E13514F360E3}.Release|Any CPU.ActiveCfg = Release|Any CPU
434+
{20A5A907-A135-4735-B4BF-E13514F360E3}.Release|Any CPU.Build.0 = Release|Any CPU
435+
{E592E447-BA3C-44FA-86C1-EBEDC864A644}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
436+
{E592E447-BA3C-44FA-86C1-EBEDC864A644}.Debug|Any CPU.Build.0 = Debug|Any CPU
437+
{E592E447-BA3C-44FA-86C1-EBEDC864A644}.Release|Any CPU.ActiveCfg = Release|Any CPU
438+
{E592E447-BA3C-44FA-86C1-EBEDC864A644}.Release|Any CPU.Build.0 = Release|Any CPU
427439
EndGlobalSection
428440
GlobalSection(SolutionProperties) = preSolution
429441
HideSolutionNode = FALSE
@@ -497,6 +509,8 @@ Global
497509
{165411FE-755E-4869-A756-F87F455860AC} = {4981B3A5-4AFD-4191-BF7D-8692D9783D60}
498510
{CA283D7F-EB95-4353-B196-C409965D2B42} = {27381127-6C45-4B4C-8F18-41FF48DFE4B2}
499511
{C8079F06-304F-49B1-A0C1-45AA3782A923} = {4981B3A5-4AFD-4191-BF7D-8692D9783D60}
512+
{20A5A907-A135-4735-B4BF-E13514F360E3} = {27381127-6C45-4B4C-8F18-41FF48DFE4B2}
513+
{E592E447-BA3C-44FA-86C1-EBEDC864A644} = {4981B3A5-4AFD-4191-BF7D-8692D9783D60}
500514
{DCF2D47A-921A-4900-B5B2-CF97B3531CE8} = {975F6F41-B455-451D-A312-098DE4A167B6}
501515
EndGlobalSection
502516
GlobalSection(ExtensibilityGlobals) = postSolution

Directory.Packages.props

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
<PackageVersion Include="JsonSchema.Net" Version="5.3.1" />
7272
<PackageVersion Include="Microsoft.FluentUI.AspNetCore.Components" Version="4.0.0" />
7373
<PackageVersion Include="Microsoft.FluentUI.AspNetCore.Components.Icons" Version="4.0.0" />
74+
<PackageVersion Include="MongoDB.Driver" Version="2.22.0" />
7475
<PackageVersion Include="MySqlConnector.DependencyInjection" Version="2.3.1" />
7576
<PackageVersion Include="Npgsql.DependencyInjection" Version="8.0.0-rc.2" />
7677
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.0-rc.2" />
@@ -99,4 +100,4 @@
99100
<PackageVersion Include="Microsoft.Signed.Wix" Version="1.0.0-v3.14.0.5722" />
100101
<PackageVersion Include="Microsoft.DotNet.Build.Tasks.Installers" Version="8.0.0-beta.23564.4" />
101102
</ItemGroup>
102-
</Project>
103+
</Project>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Aspire.Hosting.ApplicationModel;
5+
6+
/// <summary>
7+
/// Represents a MongoDb resource that requires a connection string.
8+
/// </summary>
9+
public interface IMongoDbResource : IResourceWithConnectionString
10+
{
11+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Net.Sockets;
5+
using System.Text.Json;
6+
using Aspire.Hosting.ApplicationModel;
7+
8+
namespace Aspire.Hosting.MongoDb;
9+
10+
/// <summary>
11+
/// Provides extension methods for adding MongoDB resources to an <see cref="IDistributedApplicationBuilder"/>.
12+
/// </summary>
13+
public static class MongoDbBuilderExtensions
14+
{
15+
private const int DefaultContainerPort = 27017;
16+
private const string DefaultPassword = "password";
17+
private const string PasswordEnvVarName = "MONGO_INITDB_ROOT_PASSWORD";
18+
19+
/// <summary>
20+
/// Adds a MongoDB container to the application model. The default image is "mongo" and the tag is "latest".
21+
/// </summary>
22+
/// <returns></returns>
23+
/// <param name="builder">The <see cref="IDistributedApplicationBuilder"/>.</param>
24+
/// <param name="name">The name of the resource. This name will be used as the connection string name when referenced in a dependency.</param>
25+
/// <param name="port">The host port for MongoDB.</param>
26+
/// <param name="password">The password for the MongoDB root user. Defaults to a 'password' password.</param>
27+
/// <returns>A reference to the <see cref="IResourceBuilder{MongoDbContainerResource}"/>.</returns>
28+
public static IResourceBuilder<MongoDbContainerResource> AddMongoDbContainer(
29+
this IDistributedApplicationBuilder builder,
30+
string name,
31+
int port = DefaultContainerPort,
32+
string password = DefaultPassword)
33+
{
34+
var mongoDbContainer = new MongoDbContainerResource(name, password);
35+
36+
return builder
37+
.AddResource(mongoDbContainer)
38+
.WithAnnotation(new ManifestPublishingCallbackAnnotation(WriteMongoDbContainerToManifest))
39+
.WithAnnotation(new ServiceBindingAnnotation(ProtocolType.Tcp, port: port, containerPort: DefaultContainerPort)) // Internal port is always 27017.
40+
.WithAnnotation(new ContainerImageAnnotation { Image = "mongo", Tag = "latest" })
41+
.WithEnvironment(PasswordEnvVarName, () => mongoDbContainer.Password);
42+
}
43+
44+
/// <summary>
45+
/// Adds a MongoDB connection to the application model. Connection strings can also be read from the connection string section of the configuration using the name of the resource.
46+
/// </summary>
47+
/// <param name="builder">The <see cref="IDistributedApplicationBuilder"/>.</param>
48+
/// <param name="name">The name of the resource. This name will be used as the connection string name when referenced in a dependency.</param>
49+
/// <param name="connectionString">The MongoDB connection string (optional).</param>
50+
/// <returns>A reference to the <see cref="IResourceBuilder{MongoDbConnectionResource}"/>.</returns>
51+
public static IResourceBuilder<MongoDbConnectionResource> AddMongoDbConnection(this IDistributedApplicationBuilder builder, string name, string? connectionString = null)
52+
{
53+
var mongoDbConnection = new MongoDbConnectionResource(name, connectionString);
54+
55+
return builder
56+
.AddResource(mongoDbConnection)
57+
.WithAnnotation(new ManifestPublishingCallbackAnnotation((json) => WriteMongoDbConnectionToManifest(json, mongoDbConnection)));
58+
}
59+
60+
/// <summary>
61+
/// Adds a MongoDB database to the application model.
62+
/// </summary>
63+
/// <param name="builder">The MongoDB server resource builder.</param>
64+
/// <param name="name">The name of the resource. This name will be used as the connection string name when referenced in a dependency.</param>
65+
/// <returns>A reference to the <see cref="IResourceBuilder{MongoDbDatabaseResource}"/>.</returns>
66+
public static IResourceBuilder<MongoDbDatabaseResource> AddDatabase(this IResourceBuilder<MongoDbContainerResource> builder, string name)
67+
{
68+
var mongoDbDatabase = new MongoDbDatabaseResource(name, builder.Resource);
69+
70+
return builder.ApplicationBuilder
71+
.AddResource(mongoDbDatabase)
72+
.WithAnnotation(new ManifestPublishingCallbackAnnotation(
73+
(json) => WriteMongoDbDatabaseToManifest(json, mongoDbDatabase)));
74+
}
75+
76+
private static void WriteMongoDbContainerToManifest(Utf8JsonWriter jsonWriter)
77+
{
78+
jsonWriter.WriteString("type", "mongodb.server.v0");
79+
}
80+
81+
private static void WriteMongoDbConnectionToManifest(Utf8JsonWriter jsonWriter, MongoDbConnectionResource mongoDbConnection)
82+
{
83+
jsonWriter.WriteString("type", "mongodb.connection.v0");
84+
jsonWriter.WriteString("connectionString", mongoDbConnection.GetConnectionString());
85+
}
86+
87+
private static void WriteMongoDbDatabaseToManifest(Utf8JsonWriter json, MongoDbDatabaseResource mongoDbDatabase)
88+
{
89+
json.WriteString("type", "mongodb.database.v0");
90+
json.WriteString("parent", mongoDbDatabase.Parent.Name);
91+
}
92+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Aspire.Hosting.ApplicationModel;
5+
6+
/// <summary>
7+
/// A resource that represents a MongoDb connection.
8+
/// </summary>
9+
/// <param name="name">The name of the resource.</param>
10+
/// <param name="connectionString">The MongoDb connection string.</param>
11+
public class MongoDbConnectionResource(string name, string? connectionString) : Resource(name), IMySqlResource
12+
{
13+
private readonly string? _connectionString = connectionString;
14+
15+
/// <summary>
16+
/// Gets the connection string for the MongoDb server.
17+
/// </summary>
18+
/// <returns>The specified connection string.</returns>
19+
public string? GetConnectionString() => _connectionString;
20+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Aspire.Hosting.ApplicationModel;
5+
6+
/// <summary>
7+
/// A resource that represents a MongoDb container.
8+
/// </summary>
9+
/// <param name="name">The name of the resource.</param>
10+
/// <param name="password">The MongoDb root password.</param>
11+
public class MongoDbContainerResource(string name, string password) : ContainerResource(name), IMongoDbResource
12+
{
13+
public string Password { get; } = password;
14+
15+
/// <summary>
16+
/// Gets the connection string for the MongoDb server.
17+
/// </summary>
18+
/// <returns>A connection string for the MongoDb server in the form "mongodb://host:port".</returns>
19+
public string? GetConnectionString()
20+
{
21+
if (!this.TryGetAllocatedEndPoints(out var allocatedEndpoints))
22+
{
23+
throw new DistributedApplicationException("Expected allocated endpoints!");
24+
}
25+
26+
var allocatedEndpoint = allocatedEndpoints.Single();
27+
28+
return $"mongodb://root:{Password}@{allocatedEndpoint.Address}:{allocatedEndpoint.Port}";
29+
}
30+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Aspire.Hosting.ApplicationModel;
5+
6+
/// <summary>
7+
/// A resource that represents a MongoDb database. This is a child resource of a <see cref="MongoDbContainerResource"/>.
8+
/// </summary>
9+
/// <param name="name">The name of the resource.</param>
10+
/// <param name="mongoDbContainer">The MongoDb server resource associated with this database.</param>
11+
public class MongoDbDatabaseResource(string name, MongoDbContainerResource mongoDbContainer)
12+
: Resource(name), IMongoDbResource, IResourceWithParent<MongoDbContainerResource>
13+
{
14+
public MongoDbContainerResource Parent => mongoDbContainer;
15+
16+
/// <summary>
17+
/// Gets the connection string for the MongoDb database.
18+
/// </summary>
19+
/// <returns>A connection string for the MongoDb database.</returns>
20+
public string? GetConnectionString()
21+
{
22+
if (Parent.GetConnectionString() is { } connectionString)
23+
{
24+
return $"{connectionString}/{Name}";
25+
}
26+
27+
throw new DistributedApplicationException("Parent resource connection string was null.");
28+
}
29+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>$(NetCurrent)</TargetFramework>
5+
<IsPackable>true</IsPackable>
6+
<PackageTags>$(ComponentDatabasePackageTags) MongoDb</PackageTags>
7+
<Description>A generic MongoDb client that integrates with Aspire.</Description>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<PackageReference Include="MongoDB.Driver" />
12+
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" />
13+
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" />
14+
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" />
15+
</ItemGroup>
16+
17+
</Project>
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Aspire.MongoDB.Driver;
5+
using Microsoft.Extensions.Configuration;
6+
using Microsoft.Extensions.DependencyInjection;
7+
using MongoDB.Driver;
8+
9+
namespace Microsoft.Extensions.Hosting;
10+
11+
/// <summary>
12+
/// Extension methods for connecting MongoDB database with MongoDB.Driver client.
13+
/// </summary>
14+
public static class AspireMongoDbDriverExtensions
15+
{
16+
private const string DefaultConfigSectionName = "Aspire:MongoDB";
17+
18+
/// <summary>
19+
/// Registers <see cref="IMongoClient"/> and <see cref="IMongoDatabase"/> instances for connecting MongoDB database with MongoDB.Driver client.
20+
/// </summary>
21+
/// <param name="builder">The <see cref="IHostApplicationBuilder" /> to read config from and add services to.</param>
22+
/// <param name="connectionName">A name used to retrieve the connection string from the ConnectionStrings configuration section.</param>
23+
/// <param name="configureSettings">An optional delegate that can be used for customizing options. It's invoked after the settings are read from the configuration.</param>
24+
/// <remarks>Reads the configuration from "Aspire:MongoDB" section.</remarks>
25+
/// <exception cref="InvalidOperationException">Thrown when mandatory <see cref="MongoDbSettings.ConnectionString"/> is not provided.</exception>
26+
public static void AddMongoDbDataSource(
27+
this IHostApplicationBuilder builder,
28+
string connectionName,
29+
Action<MongoDbSettings>? configureSettings = null)
30+
=> AddMongoDbDataSource(builder, DefaultConfigSectionName, configureSettings, connectionName, serviceKey: null);
31+
32+
/// <summary>
33+
/// Registers <see cref="IMongoClient"/> and <see cref="IMongoDatabase"/> instances for connecting MongoDB database with MongoDB.Driver client.
34+
/// </summary>
35+
/// <param name="builder">The <see cref="IHostApplicationBuilder" /> to read config from and add services to.</param>
36+
/// <param name="name">The name of the component, which is used as the <see cref="ServiceDescriptor.ServiceKey"/> of the service and also to retrieve the connection string from the ConnectionStrings configuration section.</param>
37+
/// <param name="configureSettings">An optional delegate that can be used for customizing options. It's invoked after the settings are read from the configuration.</param>
38+
/// <remarks>Reads the configuration from "Aspire:MongoDB:{name}" section.</remarks>
39+
/// <exception cref="ArgumentNullException">Thrown if mandatory <paramref name="builder"/> is null.</exception>
40+
/// <exception cref="InvalidOperationException">Thrown when mandatory <see cref="MongoDbSettings.ConnectionString"/> is not provided.</exception>
41+
public static void AddKeyedMongoDbDataSource(
42+
this IHostApplicationBuilder builder,
43+
string name,
44+
Action<MongoDbSettings>? configureSettings = null)
45+
{
46+
ArgumentException.ThrowIfNullOrEmpty(name);
47+
48+
AddMongoDbDataSource(builder, $"{DefaultConfigSectionName}:{name}", configureSettings, connectionName: name, serviceKey: name);
49+
}
50+
51+
private static void AddMongoDbDataSource(
52+
IHostApplicationBuilder builder,
53+
string configurationSectionName,
54+
Action<MongoDbSettings>? configureSettings,
55+
string connectionName,
56+
string? serviceKey)
57+
{
58+
ArgumentNullException.ThrowIfNull(builder);
59+
60+
var settings = builder.GetMongoDbSettings(
61+
connectionName,
62+
configurationSectionName,
63+
configureSettings);
64+
65+
settings.ValidateSettings(connectionName, configurationSectionName);
66+
67+
builder.RegisterMongoDbServices(settings, serviceKey);
68+
}
69+
70+
private static void RegisterMongoDbServices(this IHostApplicationBuilder builder, MongoDbSettings settings, string? serviceKey)
71+
{
72+
var mongoUrl = MongoUrl.Create(settings.ConnectionString);
73+
74+
builder.Services.AddMongoClient(mongoUrl, serviceKey);
75+
76+
if (!string.IsNullOrWhiteSpace(mongoUrl.DatabaseName))
77+
{
78+
builder.Services.AddMongoDatabase(mongoUrl, serviceKey);
79+
}
80+
}
81+
82+
private static IServiceCollection AddMongoClient(this IServiceCollection services, MongoUrl mongoUrl, string? serviceKey = null)
83+
{
84+
if (string.IsNullOrWhiteSpace(serviceKey))
85+
{
86+
return services.AddSingleton<IMongoClient>(_ => new MongoClient(mongoUrl));
87+
}
88+
89+
return services
90+
.AddKeyedSingleton<IMongoClient>(serviceKey, (_, __) => new MongoClient(mongoUrl));
91+
}
92+
93+
private static IServiceCollection AddMongoDatabase(this IServiceCollection services, MongoUrl mongoUrl, string? serviceKey = null)
94+
{
95+
if (string.IsNullOrWhiteSpace(serviceKey))
96+
{
97+
return services.AddSingleton<IMongoDatabase>(provider =>
98+
{
99+
return provider
100+
.GetRequiredService<IMongoClient>()
101+
.GetDatabase(mongoUrl.DatabaseName);
102+
});
103+
}
104+
105+
return services.AddKeyedSingleton<IMongoDatabase>(serviceKey, (provider, _) =>
106+
{
107+
return provider
108+
.GetRequiredKeyedService<IMongoClient>(serviceKey)
109+
.GetDatabase(mongoUrl.DatabaseName);
110+
});
111+
}
112+
113+
private static MongoDbSettings GetMongoDbSettings(
114+
this IHostApplicationBuilder builder,
115+
string connectionName,
116+
string configurationSectionName,
117+
Action<MongoDbSettings>? configureSettings)
118+
{
119+
MongoDbSettings settings = new();
120+
121+
builder.Configuration
122+
.GetSection(configurationSectionName)
123+
.Bind(settings);
124+
125+
if (builder.Configuration.GetConnectionString(connectionName) is string connectionString)
126+
{
127+
settings.ConnectionString = connectionString;
128+
}
129+
130+
configureSettings?.Invoke(settings);
131+
132+
return settings;
133+
}
134+
135+
private static void ValidateSettings(this MongoDbSettings settings, string connectionName, string configurationSectionName)
136+
{
137+
if (string.IsNullOrEmpty(settings.ConnectionString))
138+
{
139+
throw new InvalidOperationException($"ConnectionString is missing. It should be provided in 'ConnectionStrings:{connectionName}' or under the 'ConnectionString' key in '{configurationSectionName}' configuration section.");
140+
}
141+
}
142+
}

0 commit comments

Comments
 (0)