Skip to content

Commit

Permalink
feat: add mongodb container and connection support
Browse files Browse the repository at this point in the history
  • Loading branch information
ailtonguitar committed Nov 22, 2023
1 parent 69937e2 commit 850b587
Show file tree
Hide file tree
Showing 13 changed files with 429 additions and 1 deletion.
14 changes: 14 additions & 0 deletions Aspire.sln
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.MySqlConnector.Tests
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestProject.IntegrationServiceA", "tests\testproject\TestProject.IntegrationServiceA\TestProject.IntegrationServiceA.csproj", "{DCF2D47A-921A-4900-B5B2-CF97B3531CE8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aspire.MongoDB.Driver", "src\Components\Aspire.MongoDB.Driver\Aspire.MongoDB.Driver.csproj", "{20A5A907-A135-4735-B4BF-E13514F360E3}"
EndProject
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}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -424,6 +428,14 @@ Global
{DCF2D47A-921A-4900-B5B2-CF97B3531CE8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DCF2D47A-921A-4900-B5B2-CF97B3531CE8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DCF2D47A-921A-4900-B5B2-CF97B3531CE8}.Release|Any CPU.Build.0 = Release|Any CPU
{20A5A907-A135-4735-B4BF-E13514F360E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{20A5A907-A135-4735-B4BF-E13514F360E3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{20A5A907-A135-4735-B4BF-E13514F360E3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{20A5A907-A135-4735-B4BF-E13514F360E3}.Release|Any CPU.Build.0 = Release|Any CPU
{E592E447-BA3C-44FA-86C1-EBEDC864A644}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E592E447-BA3C-44FA-86C1-EBEDC864A644}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E592E447-BA3C-44FA-86C1-EBEDC864A644}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E592E447-BA3C-44FA-86C1-EBEDC864A644}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -497,6 +509,8 @@ Global
{165411FE-755E-4869-A756-F87F455860AC} = {4981B3A5-4AFD-4191-BF7D-8692D9783D60}
{CA283D7F-EB95-4353-B196-C409965D2B42} = {27381127-6C45-4B4C-8F18-41FF48DFE4B2}
{C8079F06-304F-49B1-A0C1-45AA3782A923} = {4981B3A5-4AFD-4191-BF7D-8692D9783D60}
{20A5A907-A135-4735-B4BF-E13514F360E3} = {27381127-6C45-4B4C-8F18-41FF48DFE4B2}
{E592E447-BA3C-44FA-86C1-EBEDC864A644} = {4981B3A5-4AFD-4191-BF7D-8692D9783D60}
{DCF2D47A-921A-4900-B5B2-CF97B3531CE8} = {975F6F41-B455-451D-A312-098DE4A167B6}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
Expand Down
3 changes: 2 additions & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
<PackageVersion Include="JsonSchema.Net" Version="5.3.1" />
<PackageVersion Include="Microsoft.FluentUI.AspNetCore.Components" Version="4.0.0" />
<PackageVersion Include="Microsoft.FluentUI.AspNetCore.Components.Icons" Version="4.0.0" />
<PackageVersion Include="MongoDB.Driver" Version="2.19.0" />
<PackageVersion Include="MySqlConnector.DependencyInjection" Version="2.3.1" />
<PackageVersion Include="Npgsql.DependencyInjection" Version="8.0.0-rc.2" />
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.0-rc.2" />
Expand Down Expand Up @@ -99,4 +100,4 @@
<PackageVersion Include="Microsoft.Signed.Wix" Version="1.0.0-v3.14.0.5722" />
<PackageVersion Include="Microsoft.DotNet.Build.Tasks.Installers" Version="8.0.0-beta.23564.4" />
</ItemGroup>
</Project>
</Project>
11 changes: 11 additions & 0 deletions src/Aspire.Hosting/MongoDB/IMongoDbResource.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Aspire.Hosting.ApplicationModel;

/// <summary>
/// Represents a MongoDb resource that requires a connection string.
/// </summary>
public interface IMongoDbResource : IResourceWithConnectionString
{
}
92 changes: 92 additions & 0 deletions src/Aspire.Hosting/MongoDB/MongoDbBuilderExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Net.Sockets;
using System.Text.Json;
using Aspire.Hosting.ApplicationModel;

namespace Aspire.Hosting.MongoDb;

/// <summary>
/// Provides extension methods for adding MongoDB resources to an <see cref="IDistributedApplicationBuilder"/>.
/// </summary>
public static class MongoDbBuilderExtensions
{
private const int DefaultContainerPort = 27017;
private const string DefaultPassword = "password";
private const string PasswordEnvVarName = "MONGO_INITDB_ROOT_PASSWORD";

/// <summary>
/// Adds a MongoDB container to the application model. The default image is "mongo" and the tag is "latest".
/// </summary>
/// <returns></returns>
/// <param name="builder">The <see cref="IDistributedApplicationBuilder"/>.</param>
/// <param name="name">The name of the resource. This name will be used as the connection string name when referenced in a dependency.</param>
/// <param name="port">The host port for MongoDB.</param>
/// <param name="password">The password for the MongoDB root user. Defaults to a 'password' password.</param>
/// <returns>A reference to the <see cref="IResourceBuilder{MongoDbContainerResource}"/>.</returns>
public static IResourceBuilder<MongoDbContainerResource> AddMongoDbContainer(
this IDistributedApplicationBuilder builder,
string name,
int port = DefaultContainerPort,
string password = DefaultPassword)
{
var mongoDbContainer = new MongoDbContainerResource(name, password);

return builder
.AddResource(mongoDbContainer)
.WithAnnotation(new ManifestPublishingCallbackAnnotation(WriteMongoDbContainerToManifest))
.WithAnnotation(new ServiceBindingAnnotation(ProtocolType.Tcp, port: port, containerPort: DefaultContainerPort)) // Internal port is always 27017.
.WithAnnotation(new ContainerImageAnnotation { Image = "mongo", Tag = "latest" })
.WithEnvironment(PasswordEnvVarName, () => mongoDbContainer.Password);
}

/// <summary>
/// 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.
/// </summary>
/// <param name="builder">The <see cref="IDistributedApplicationBuilder"/>.</param>
/// <param name="name">The name of the resource. This name will be used as the connection string name when referenced in a dependency.</param>
/// <param name="connectionString">The MongoDB connection string (optional).</param>
/// <returns>A reference to the <see cref="IResourceBuilder{MongoDbConnectionResource}"/>.</returns>
public static IResourceBuilder<MongoDbConnectionResource> AddMongoDbConnection(this IDistributedApplicationBuilder builder, string name, string? connectionString = null)
{
var mongoDbConnection = new MongoDbConnectionResource(name, connectionString);

return builder
.AddResource(mongoDbConnection)
.WithAnnotation(new ManifestPublishingCallbackAnnotation((json) => WriteMongoDbConnectionToManifest(json, mongoDbConnection)));
}

/// <summary>
/// Adds a MongoDB database to the application model.
/// </summary>
/// <param name="builder">The MongoDB server resource builder.</param>
/// <param name="name">The name of the resource. This name will be used as the connection string name when referenced in a dependency.</param>
/// <returns>A reference to the <see cref="IResourceBuilder{MongoDbDatabaseResource}"/>.</returns>
public static IResourceBuilder<MongoDbDatabaseResource> AddDatabase(this IResourceBuilder<MongoDbContainerResource> builder, string name)
{
var mongoDbDatabase = new MongoDbDatabaseResource(name, builder.Resource);

return builder.ApplicationBuilder
.AddResource(mongoDbDatabase)
.WithAnnotation(new ManifestPublishingCallbackAnnotation(
(json) => WriteMongoDbDatabaseToManifest(json, mongoDbDatabase)));
}

private static void WriteMongoDbContainerToManifest(Utf8JsonWriter jsonWriter)
{
jsonWriter.WriteString("type", "mongodb.server.v0");
}

private static void WriteMongoDbConnectionToManifest(Utf8JsonWriter jsonWriter, MongoDbConnectionResource mongoDbConnection)
{
jsonWriter.WriteString("type", "mongodb.connection.v0");
jsonWriter.WriteString("connectionString", mongoDbConnection.GetConnectionString());
}

private static void WriteMongoDbDatabaseToManifest(Utf8JsonWriter json, MongoDbDatabaseResource mongoDbDatabase)
{
json.WriteString("type", "mongodb.database.v0");
json.WriteString("parent", mongoDbDatabase.Parent.Name);
}
}
20 changes: 20 additions & 0 deletions src/Aspire.Hosting/MongoDB/MongoDbConnectionResource.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Aspire.Hosting.ApplicationModel;

/// <summary>
/// A resource that represents a MongoDb connection.
/// </summary>
/// <param name="name">The name of the resource.</param>
/// <param name="connectionString">The MongoDb connection string.</param>
public class MongoDbConnectionResource(string name, string? connectionString) : Resource(name), IMySqlResource
{
private readonly string? _connectionString = connectionString;

/// <summary>
/// Gets the connection string for the MongoDb server.
/// </summary>
/// <returns>The specified connection string.</returns>
public string? GetConnectionString() => _connectionString;
}
30 changes: 30 additions & 0 deletions src/Aspire.Hosting/MongoDB/MongoDbContainerResource.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Aspire.Hosting.ApplicationModel;

/// <summary>
/// A resource that represents a MongoDb container.
/// </summary>
/// <param name="name">The name of the resource.</param>
/// <param name="password">The MongoDb root password.</param>
public class MongoDbContainerResource(string name, string password) : ContainerResource(name), IMongoDbResource
{
public string Password { get; } = password;

/// <summary>
/// Gets the connection string for the MongoDb server.
/// </summary>
/// <returns>A connection string for the MongoDb server in the form "mongodb://host:port".</returns>
public string? GetConnectionString()
{
if (!this.TryGetAllocatedEndPoints(out var allocatedEndpoints))
{
throw new DistributedApplicationException("Expected allocated endpoints!");
}

var allocatedEndpoint = allocatedEndpoints.Single();

return $"mongodb://root:{Password}@{allocatedEndpoint.Address}:{allocatedEndpoint.Port}";
}
}
31 changes: 31 additions & 0 deletions src/Aspire.Hosting/MongoDB/MongoDbDatabaseResource.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Aspire.Hosting.ApplicationModel;

/// <summary>
/// A resource that represents a MongoDb database. This is a child resource of a <see cref="MongoDbContainerResource"/>.
/// </summary>
/// <param name="name">The name of the resource.</param>
/// <param name="mongoDbContainer">The MongoDb server resource associated with this database.</param>
public class MongoDbDatabaseResource(string name, MongoDbContainerResource mongoDbContainer)
: Resource(name), IMongoDbResource, IResourceWithParent<MongoDbContainerResource>
{
public MongoDbContainerResource Parent => mongoDbContainer;

/// <summary>
/// Gets the connection string for the MongoDb database.
/// </summary>
/// <returns>A connection string for the MongoDb database.</returns>
public string? GetConnectionString()
{
if (Parent.GetConnectionString() is { } connectionString)
{
return $"{connectionString}/{Name}";
}
else
{
throw new DistributedApplicationException("Parent resource connection string was null.");
}
}
}
17 changes: 17 additions & 0 deletions src/Components/Aspire.MongoDB.Driver/Aspire.MongoDB.Driver.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>$(NetCurrent)</TargetFramework>
<IsPackable>true</IsPackable>
<PackageTags>$(ComponentDatabasePackageTags) MongoDb</PackageTags>
<Description>A generic MongoDb client that integrates with Aspire.</Description>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="MongoDB.Driver" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" />
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" />
</ItemGroup>

</Project>
109 changes: 109 additions & 0 deletions src/Components/Aspire.MongoDB.Driver/AspireMongoDbDriverExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Aspire.MongoDB.Driver;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using MongoDB.Driver;

namespace Microsoft.Extensions.Hosting;

/// <summary>
/// Extension methods for connecting MongoDB database with MongoDB.Driver client.
/// </summary>
public static class AspireMongoDbDriverExtensions
{
private const string DefaultConfigSectionName = "Aspire:MongoDB";

/// <summary>
/// Registers <see cref="IMongoClient"/> instance for connecting MongoDB database with MongoDB.Driver client.
/// </summary>
/// <param name="builder">The <see cref="IHostApplicationBuilder" /> to read config from and add services to.</param>
/// <param name="connectionName">A name used to retrieve the connection string from the ConnectionStrings configuration section.</param>
/// <param name="configureSettings">An optional delegate that can be used for customizing options. It's invoked after the settings are read from the configuration.</param>
/// <remarks>Reads the configuration from "Aspire:MongoDB" section.</remarks>
/// <exception cref="InvalidOperationException">Thrown when mandatory <see cref="MongoDbSettings.ConnectionString"/> is not provided.</exception>
public static void AddMongoDbDataSource(
this IHostApplicationBuilder builder,
string connectionName,
Action<MongoDbSettings>? configureSettings = null)
=> AddMongoDbDataSource(builder, DefaultConfigSectionName, configureSettings, connectionName, serviceKey: null);

/// <summary>
/// Registers <see cref="IMongoClient"/> instance for connecting MongoDB database with MongoDB.Driver client.
/// </summary>
/// <param name="builder">The <see cref="IHostApplicationBuilder" /> to read config from and add services to.</param>
/// <param name="name">The name of the component, which is used as the <see cref="ServiceDescriptor.ServiceKey"/> of the service and also to retrieve the connection string from the ConnectionStrings configuration section.</param>
/// <param name="configureSettings">An optional delegate that can be used for customizing options. It's invoked after the settings are read from the configuration.</param>
/// <remarks>Reads the configuration from "Aspire:MongoDB:{name}" section.</remarks>
/// <exception cref="ArgumentNullException">Thrown if mandatory <paramref name="builder"/> is null.</exception>
/// <exception cref="InvalidOperationException">Thrown when mandatory <see cref="MongoDbSettings.ConnectionString"/> is not provided.</exception>
public static void AddKeyedMongoDbDataSource(
this IHostApplicationBuilder builder,
string name,
Action<MongoDbSettings>? configureSettings = null)
{
ArgumentException.ThrowIfNullOrEmpty(name);

AddMongoDbDataSource(builder, $"{DefaultConfigSectionName}:{name}", configureSettings, connectionName: name, serviceKey: name);
}

private static void AddMongoDbDataSource(
IHostApplicationBuilder builder,
string configurationSectionName,
Action<MongoDbSettings>? configureSettings,
string connectionName,
object? serviceKey)
{
ArgumentNullException.ThrowIfNull(builder);

var settings = builder.GetMongoDbSettings(
configurationSectionName,
configureSettings,
connectionName);

settings.ValidateSettings(connectionName, configurationSectionName);

builder.RegisterMongoDbServices(settings, serviceKey);
}

private static void RegisterMongoDbServices(this IHostApplicationBuilder builder, MongoDbSettings settings, object? serviceKey)
{
if (serviceKey is null)
{
builder.Services
.AddSingleton<IMongoClient>(_ => new MongoClient(settings.ConnectionString));
return;
}

builder.Services.AddKeyedSingleton<IMongoClient>(
serviceKey,
(_, __) => new MongoClient(settings.ConnectionString));
}

private static MongoDbSettings GetMongoDbSettings(this IHostApplicationBuilder builder, string configurationSectionName, Action<MongoDbSettings>? configureSettings, string connectionName)
{
MongoDbSettings settings = new();

builder.Configuration
.GetSection(configurationSectionName)
.Bind(settings);

if (builder.Configuration.GetConnectionString(connectionName) is string connectionString)
{
settings.ConnectionString = connectionString;
}

configureSettings?.Invoke(settings);

return settings;
}

private static void ValidateSettings(this MongoDbSettings settings, string connectionName, string configurationSectionName)
{
if (string.IsNullOrEmpty(settings.ConnectionString))
{
throw new InvalidOperationException($"ConnectionString is missing. It should be provided in 'ConnectionStrings:{connectionName}' or under the 'ConnectionString' key in '{configurationSectionName}' configuration section.");
}
}
}
31 changes: 31 additions & 0 deletions src/Components/Aspire.MongoDB.Driver/ConfigurationSchema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"definitions": {
"logLevel": {
"properties": {
"MongoDB": {
"$ref": "#/definitions/logLevelThreshold"
},
"MongoDB.Driver": {
"$ref": "#/definitions/logLevelThreshold"
}
}
}
},
"properties": {
"Aspire": {
"type": "object",
"properties": {
"MongoDB": {
"type": "object",
"properties": {
"ConnectionString": {
"type": "string",
"description": "Gets or sets the connection string of the MongoDB database to connect to."
}
}
}
}
}
},
"type": "object"
}

0 comments on commit 850b587

Please sign in to comment.