From fc7fc084c12f9fbec1aeafd3dc82d18746a8c025 Mon Sep 17 00:00:00 2001 From: Ailton Silva Date: Fri, 1 Dec 2023 11:02:47 -0300 Subject: [PATCH 01/15] feat: add mongodb container and connection support --- Aspire.sln | 18 ++ Directory.Packages.props | 5 +- .../MongoDB/IMongoDBResource.cs | 11 + .../MongoDB/MongoDBBuilderExtensions.cs | 94 ++++++++ .../MongoDB/MongoDBConnectionResource.cs | 20 ++ .../MongoDB/MongoDBConnectionStringBuilder.cs | 62 +++++ .../MongoDB/MongoDBContainerResource.cs | 41 ++++ .../MongoDB/MongoDBDatabaseResource.cs | 40 ++++ .../Aspire.MongoDB.Driver.csproj | 25 ++ .../AspireMongoDBDriverExtensions.cs | 225 ++++++++++++++++++ .../ConfigurationSchema.json | 60 +++++ .../Aspire.MongoDB.Driver/MongoDBSettings.cs | 33 +++ .../Aspire.MongoDB.Driver/README.md | 113 +++++++++ src/Components/Aspire_Components_Progress.md | 1 + src/Components/Telemetry.md | 13 + .../Aspire.Hosting.Tests.csproj | 2 + .../MongoDB/AddMongoDBTests.cs | 99 ++++++++ .../MongoDB/MongoDBContainerResourceTests.cs | 36 +++ .../Aspire.MongoDB.Driver.Tests.csproj | 14 ++ .../AspireMongoDBDriverExtensionsTests.cs | 173 ++++++++++++++ .../ConformanceTests.cs | 101 ++++++++ .../TestProject.AppHost/TestProgram.cs | 4 +- .../Program.cs | 1 + .../TestProject.IntegrationServiceA.csproj | 5 +- 24 files changed, 1193 insertions(+), 3 deletions(-) create mode 100644 src/Aspire.Hosting/MongoDB/IMongoDBResource.cs create mode 100644 src/Aspire.Hosting/MongoDB/MongoDBBuilderExtensions.cs create mode 100644 src/Aspire.Hosting/MongoDB/MongoDBConnectionResource.cs create mode 100644 src/Aspire.Hosting/MongoDB/MongoDBConnectionStringBuilder.cs create mode 100644 src/Aspire.Hosting/MongoDB/MongoDBContainerResource.cs create mode 100644 src/Aspire.Hosting/MongoDB/MongoDBDatabaseResource.cs create mode 100644 src/Components/Aspire.MongoDB.Driver/Aspire.MongoDB.Driver.csproj create mode 100644 src/Components/Aspire.MongoDB.Driver/AspireMongoDBDriverExtensions.cs create mode 100644 src/Components/Aspire.MongoDB.Driver/ConfigurationSchema.json create mode 100644 src/Components/Aspire.MongoDB.Driver/MongoDBSettings.cs create mode 100644 src/Components/Aspire.MongoDB.Driver/README.md create mode 100644 tests/Aspire.Hosting.Tests/MongoDB/AddMongoDBTests.cs create mode 100644 tests/Aspire.Hosting.Tests/MongoDB/MongoDBContainerResourceTests.cs create mode 100644 tests/Aspire.MongoDB.Driver.Tests/Aspire.MongoDB.Driver.Tests.csproj create mode 100644 tests/Aspire.MongoDB.Driver.Tests/AspireMongoDBDriverExtensionsTests.cs create mode 100644 tests/Aspire.MongoDB.Driver.Tests/ConformanceTests.cs diff --git a/Aspire.sln b/Aspire.sln index 34f1f0c41a0..a8ca025ec69 100644 --- a/Aspire.sln +++ b/Aspire.sln @@ -160,6 +160,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 @@ -430,6 +434,18 @@ 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 + {6472D59F-7C04-43DE-AD33-9F20BE3804BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6472D59F-7C04-43DE-AD33-9F20BE3804BF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6472D59F-7C04-43DE-AD33-9F20BE3804BF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6472D59F-7C04-43DE-AD33-9F20BE3804BF}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -504,6 +520,8 @@ Global {6472D59F-7C04-43DE-AD33-9F20BE3804BF} = {975F6F41-B455-451D-A312-098DE4A167B6} {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 diff --git a/Directory.Packages.props b/Directory.Packages.props index 28016362143..6b5cd6952b7 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -4,6 +4,7 @@ + @@ -71,6 +72,8 @@ + + @@ -99,4 +102,4 @@ - + \ No newline at end of file diff --git a/src/Aspire.Hosting/MongoDB/IMongoDBResource.cs b/src/Aspire.Hosting/MongoDB/IMongoDBResource.cs new file mode 100644 index 00000000000..8209c835abd --- /dev/null +++ b/src/Aspire.Hosting/MongoDB/IMongoDBResource.cs @@ -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; + +/// +/// Represents a MongoDB resource that requires a connection string. +/// +public interface IMongoDBResource : IResourceWithConnectionString +{ +} diff --git a/src/Aspire.Hosting/MongoDB/MongoDBBuilderExtensions.cs b/src/Aspire.Hosting/MongoDB/MongoDBBuilderExtensions.cs new file mode 100644 index 00000000000..da6c665b020 --- /dev/null +++ b/src/Aspire.Hosting/MongoDB/MongoDBBuilderExtensions.cs @@ -0,0 +1,94 @@ +// 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 Aspire.Hosting.ApplicationModel; +using Aspire.Hosting.Publishing; + +namespace Aspire.Hosting; + +/// +/// Provides extension methods for adding MongoDB resources to an . +/// +public static class MongoDBBuilderExtensions +{ + private const int DefaultContainerPort = 27017; + private const string PasswordEnvVarName = "MONGO_INITDB_ROOT_PASSWORD"; + private const string UserNameEnvVarName = "MONGO_INITDB_ROOT_USERNAME"; + + /// + /// Adds a MongoDB container to the application model. The default image is "mongo" and the tag is "latest". + /// + /// + /// The . + /// The name of the resource. This name will be used as the connection string name when referenced in a dependency. + /// The host port for MongoDB. + /// The password for the MongoDB root user. Defaults to a random password. + /// A reference to the . + public static IResourceBuilder AddMongoDBContainer( + this IDistributedApplicationBuilder builder, + string name, + int? port = null, + string? password = null) + { + password ??= Guid.NewGuid().ToString("N"); + + var mongoDBContainer = new MongoDBContainerResource(name, password); + + return builder + .AddResource(mongoDBContainer) + .WithManifestPublishingCallback(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) + .WithEnvironment(UserNameEnvVarName, () => mongoDBContainer.UserName); + } + + /// + /// 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. + /// + /// The . + /// The name of the resource. This name will be used as the connection string name when referenced in a dependency. + /// The MongoDB connection string (optional). + /// A reference to the . + public static IResourceBuilder AddMongoDBConnection(this IDistributedApplicationBuilder builder, string name, string? connectionString = null) + { + var mongoDBConnection = new MongoDBConnectionResource(name, connectionString); + + return builder + .AddResource(mongoDBConnection) + .WithManifestPublishingCallback(context => context.WriteMongoDBConnectionToManifest(mongoDBConnection)); + } + + /// + /// Adds a MongoDB database to the application model. + /// + /// The MongoDB server resource builder. + /// The name of the resource. This name will be used as the connection string name when referenced in a dependency. + /// A reference to the . + public static IResourceBuilder AddDatabase(this IResourceBuilder builder, string name) + { + var mongoDBDatabase = new MongoDBDatabaseResource(name, builder.Resource); + + return builder.ApplicationBuilder + .AddResource(mongoDBDatabase) + .WithManifestPublishingCallback(context => context.WriteMongoDBDatabaseToManifest(mongoDBDatabase)); + } + + private static void WriteMongoDBContainerToManifest(this ManifestPublishingContext context) + { + context.Writer.WriteString("type", "mongodb.server.v0"); + } + + private static void WriteMongoDBConnectionToManifest(this ManifestPublishingContext context, MongoDBConnectionResource mongoDbConnection) + { + context.Writer.WriteString("type", "mongodb.connection.v0"); + context.Writer.WriteString("connectionString", mongoDbConnection.GetConnectionString()); + } + + private static void WriteMongoDBDatabaseToManifest(this ManifestPublishingContext context, MongoDBDatabaseResource mongoDbDatabase) + { + context.Writer.WriteString("type", "mongodb.database.v0"); + context.Writer.WriteString("parent", mongoDbDatabase.Parent.Name); + } +} diff --git a/src/Aspire.Hosting/MongoDB/MongoDBConnectionResource.cs b/src/Aspire.Hosting/MongoDB/MongoDBConnectionResource.cs new file mode 100644 index 00000000000..b3d749eddca --- /dev/null +++ b/src/Aspire.Hosting/MongoDB/MongoDBConnectionResource.cs @@ -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; + +/// +/// A resource that represents a MongoDB connection. +/// +/// The name of the resource. +/// The MongoDB connection string. +public class MongoDBConnectionResource(string name, string? connectionString) : Resource(name), IMongoDBResource +{ + private readonly string? _connectionString = connectionString; + + /// + /// Gets the connection string for the MongoDB server. + /// + /// The specified connection string. + public string? GetConnectionString() => _connectionString; +} diff --git a/src/Aspire.Hosting/MongoDB/MongoDBConnectionStringBuilder.cs b/src/Aspire.Hosting/MongoDB/MongoDBConnectionStringBuilder.cs new file mode 100644 index 00000000000..874876e1e73 --- /dev/null +++ b/src/Aspire.Hosting/MongoDB/MongoDBConnectionStringBuilder.cs @@ -0,0 +1,62 @@ +// 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.MongoDB; + +internal class MongoDBConnectionStringBuilder +{ + private const string Scheme = "mongodb"; + + private string? _server; + private int _port; + private string? _userName; + private string? _password; + + public MongoDBConnectionStringBuilder WithServer(string server) + { + ArgumentNullException.ThrowIfNullOrWhiteSpace(server, nameof(server)); + + _server = server; + + return this; + } + + public MongoDBConnectionStringBuilder WithPort(int port) + { + _port = port; + + return this; + } + + public MongoDBConnectionStringBuilder WithUserName(string userName) + { + ArgumentNullException.ThrowIfNullOrWhiteSpace(userName, nameof(userName)); + + _userName = userName; + + return this; + } + + public MongoDBConnectionStringBuilder WithPassword(string password) + { + ArgumentNullException.ThrowIfNullOrWhiteSpace(password, nameof(password)); + + _password = password; + + return this; + } + + public string Build() + { + var builder = new UriBuilder + { + Scheme = Scheme, + Host = _server, + Port = _port, + UserName = _userName, + Password = _password + }; + + return builder.ToString(); + } +} diff --git a/src/Aspire.Hosting/MongoDB/MongoDBContainerResource.cs b/src/Aspire.Hosting/MongoDB/MongoDBContainerResource.cs new file mode 100644 index 00000000000..637fece1276 --- /dev/null +++ b/src/Aspire.Hosting/MongoDB/MongoDBContainerResource.cs @@ -0,0 +1,41 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Aspire.Hosting.MongoDB; + +namespace Aspire.Hosting.ApplicationModel; + +/// +/// A resource that represents a MongoDB container. +/// +/// The name of the resource. +/// The MongoDB root password. +public class MongoDBContainerResource(string name, string password) : ContainerResource(name), IMongoDBResource +{ + private const string DefaultUserName = "root"; + + public string Password { get; } = password; + + public string UserName { get; } = DefaultUserName; + + /// + /// Gets the connection string for the MongoDB server. + /// + /// A connection string for the MongoDB server in the form "mongodb://host:port". + public string? GetConnectionString() + { + if (!this.TryGetAllocatedEndPoints(out var allocatedEndpoints)) + { + throw new DistributedApplicationException("Expected allocated endpoints!"); + } + + var allocatedEndpoint = allocatedEndpoints.Single(); + + return new MongoDBConnectionStringBuilder() + .WithServer(allocatedEndpoint.Address) + .WithPort(allocatedEndpoint.Port) + .WithUserName(UserName) + .WithPassword(Password) + .Build(); + } +} diff --git a/src/Aspire.Hosting/MongoDB/MongoDBDatabaseResource.cs b/src/Aspire.Hosting/MongoDB/MongoDBDatabaseResource.cs new file mode 100644 index 00000000000..dcd4a8eecec --- /dev/null +++ b/src/Aspire.Hosting/MongoDB/MongoDBDatabaseResource.cs @@ -0,0 +1,40 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Text; + +namespace Aspire.Hosting.ApplicationModel; + +/// +/// A resource that represents a MongoDB database. This is a child resource of a . +/// +/// The name of the resource. +/// The MongoDB server resource associated with this database. +public class MongoDBDatabaseResource(string name, MongoDBContainerResource mongoDBContainer) + : Resource(name), IMongoDBResource, IResourceWithParent +{ + public MongoDBContainerResource Parent => mongoDBContainer; + + /// + /// Gets the connection string for the MongoDB database. + /// + /// A connection string for the MongoDB database. + public string? GetConnectionString() + { + if (Parent.GetConnectionString() is { } connectionString) + { + var builder = new StringBuilder(connectionString); + + if (!connectionString.EndsWith('/')) + { + builder.Append('/'); + } + + builder.Append(Name); + + return builder.ToString(); + } + + throw new DistributedApplicationException("Parent resource connection string was null."); + } +} diff --git a/src/Components/Aspire.MongoDB.Driver/Aspire.MongoDB.Driver.csproj b/src/Components/Aspire.MongoDB.Driver/Aspire.MongoDB.Driver.csproj new file mode 100644 index 00000000000..e8ed5a17692 --- /dev/null +++ b/src/Components/Aspire.MongoDB.Driver/Aspire.MongoDB.Driver.csproj @@ -0,0 +1,25 @@ + + + + $(NetCurrent) + true + $(ComponentDatabasePackageTags) MongoDB + A generic MongoDB client that integrates with Aspire. + + + + + + + + + + + + + + + + + + diff --git a/src/Components/Aspire.MongoDB.Driver/AspireMongoDBDriverExtensions.cs b/src/Components/Aspire.MongoDB.Driver/AspireMongoDBDriverExtensions.cs new file mode 100644 index 00000000000..4df1f6be903 --- /dev/null +++ b/src/Components/Aspire.MongoDB.Driver/AspireMongoDBDriverExtensions.cs @@ -0,0 +1,225 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Aspire; +using Aspire.MongoDB.Driver; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using MongoDB.Driver; +using MongoDB.Driver.Core.Configuration; +using MongoDB.Driver.Core.Extensions.DiagnosticSources; + +namespace Microsoft.Extensions.Hosting; + +/// +/// Extension methods for connecting MongoDB database with MongoDB.Driver client. +/// +public static class AspireMongoDBDriverExtensions +{ + private const string DefaultConfigSectionName = "Aspire:MongoDB:Driver"; + private const string ActivityNameSource = "MongoDB.Driver.Core.Extensions.DiagnosticSources"; + + /// + /// Registers and instances for connecting MongoDB database with MongoDB.Driver client. + /// + /// The to read config from and add services to. + /// A name used to retrieve the connection string from the ConnectionStrings configuration section. + /// An optional delegate that can be used for customizing options. It's invoked after the settings are read from the configuration. + /// Reads the configuration from "Aspire:MongoDB:Driver" section. + /// An optional delegate that can be used for customizing MongoClientSettings. + /// Thrown when mandatory is not provided. + public static void AddMongoDBClient( + this IHostApplicationBuilder builder, + string connectionName, + Action? configureSettings = null, + Action? configureClientSettings = null) + => builder.AddMongoDBClient(DefaultConfigSectionName, configureSettings, configureClientSettings, connectionName, serviceKey: null); + + /// + /// Registers and instances for connecting MongoDB database with MongoDB.Driver client. + /// + /// The to read config from and add services to. + /// The name of the component, which is used as the of the service and also to retrieve the connection string from the ConnectionStrings configuration section. + /// An optional delegate that can be used for customizing options. It's invoked after the settings are read from the configuration. + /// Reads the configuration from "Aspire:MongoDB:Driver:{name}" section. + /// An optional delegate that can be used for customizing MongoClientSettings. + /// Thrown if mandatory is null. + /// Thrown when mandatory is not provided. + public static void AddKeyedMongoDBClient( + this IHostApplicationBuilder builder, + string name, + Action? configureSettings = null, + Action? configureClientSettings = null) + { + ArgumentException.ThrowIfNullOrEmpty(name); + + builder.AddMongoDBClient( + $"{DefaultConfigSectionName}:{name}", + configureSettings, + configureClientSettings, + connectionName: name, + serviceKey: name); + } + + private static void AddMongoDBClient( + this IHostApplicationBuilder builder, + string configurationSectionName, + Action? configureSettings, + Action? configureClientSettings, + string connectionName, + string? serviceKey) + { + ArgumentNullException.ThrowIfNull(builder); + + var settings = builder.GetMongoDBSettings( + connectionName, + configurationSectionName, + configureSettings); + + builder.AddMongoClient( + settings, + connectionName, + configurationSectionName, + configureClientSettings, + serviceKey); + + if (settings.Tracing) + { + builder.Services + .AddOpenTelemetry() + .WithTracing(tracer => tracer.AddSource(ActivityNameSource)); + } + + if (string.IsNullOrWhiteSpace(settings.ConnectionString)) + { + return; + } + + builder.AddMongoDatabase(settings.ConnectionString, serviceKey); + + if (settings.HealthCheckEnabled) + { + builder.AddHealthCheck( + settings.ConnectionString, + serviceKey is null ? "MongoDB.Driver" : $"MongoDB.Driver_{connectionName}", + settings.HealthCheckTimeout); + } + } + + private static IServiceCollection AddMongoClient( + this IHostApplicationBuilder builder, + MongoDBSettings mongoDbSettings, + string connectionName, + string configurationSectionName, + Action? configureClientSettings, + string? serviceKey) + { + if (string.IsNullOrWhiteSpace(serviceKey)) + { + return builder + .Services + .AddSingleton(sp => sp.CreateMongoClient(connectionName, configurationSectionName, mongoDbSettings, configureClientSettings)); + } + + return builder + .Services + .AddKeyedSingleton(serviceKey, (sp, _) => sp.CreateMongoClient(connectionName, configurationSectionName, mongoDbSettings, configureClientSettings)); + } + + private static IServiceCollection AddMongoDatabase(this IHostApplicationBuilder builder, string connectionString, string? serviceKey = null) + { + var mongoUrl = MongoUrl.Create(connectionString); + + if (string.IsNullOrWhiteSpace(mongoUrl.DatabaseName)) + { + return builder.Services; + } + + if (string.IsNullOrWhiteSpace(serviceKey)) + { + return builder.Services.AddSingleton(provider => + { + return provider + .GetRequiredService() + .GetDatabase(mongoUrl.DatabaseName); + }); + } + + return builder.Services.AddKeyedSingleton(serviceKey, (provider, _) => + { + return provider + .GetRequiredKeyedService(serviceKey) + .GetDatabase(mongoUrl.DatabaseName); + }); + } + + private static void AddHealthCheck( + this IHostApplicationBuilder builder, + string connectionString, + string healthCheckName, + int? timeout) + { + builder.TryAddHealthCheck( + healthCheckName, + healthCheck => healthCheck.AddMongoDb( + connectionString, + healthCheckName, + null, + null, + timeout > 0 ? TimeSpan.FromMilliseconds(timeout.Value) : null)); + } + + private static MongoClient CreateMongoClient( + this IServiceProvider serviceProvider, + string connectionName, + string configurationSectionName, + MongoDBSettings mongoDbSettings, + Action? configureClientSettings) + { + mongoDbSettings.ValidateSettings(connectionName, configurationSectionName); + + var clientSettings = MongoClientSettings.FromConnectionString(mongoDbSettings.ConnectionString); + + if (mongoDbSettings.Tracing) + { + clientSettings.ClusterConfigurator = cb => cb.Subscribe(new DiagnosticsActivityEventSubscriber()); + } + + configureClientSettings?.Invoke(clientSettings); + + clientSettings.LoggingSettings ??= new LoggingSettings(serviceProvider.GetService()); + + return new MongoClient(clientSettings); + } + + private static MongoDBSettings GetMongoDBSettings( + this IHostApplicationBuilder builder, + string connectionName, + string configurationSectionName, + Action? configureSettings) + { + 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."); + } + } +} diff --git a/src/Components/Aspire.MongoDB.Driver/ConfigurationSchema.json b/src/Components/Aspire.MongoDB.Driver/ConfigurationSchema.json new file mode 100644 index 00000000000..2e3a22ae03b --- /dev/null +++ b/src/Components/Aspire.MongoDB.Driver/ConfigurationSchema.json @@ -0,0 +1,60 @@ +{ + "definitions": { + "logLevel": { + "properties": { + "MongoDB": { + "$ref": "#/definitions/logLevelThreshold" + }, + "MongoDB.Command": { + "$ref": "#/definitions/logLevelThreshold" + }, + "MongoDB.SDAM": { + "$ref": "#/definitions/logLevelThreshold" + }, + "MongoDB.ServerSelection": { + "$ref": "#/definitions/logLevelThreshold" + }, + "MongoDB.Connection": { + "$ref": "#/definitions/logLevelThreshold" + }, + "MongoDB.Internal": { + "$ref": "#/definitions/logLevelThreshold" + } + } + } + }, + "properties": { + "Aspire": { + "type": "object", + "properties": { + "MongoDB": { + "type": "object", + "properties": { + "Driver": { + "type": "object", + "properties": { + "ConnectionString": { + "type": "string", + "description": "The connection string of the MongoDB database to connect to." + }, + "HealthCheckEnabled": { + "type": "boolean", + "description": "Indicates whether the MongoDB health check is enabled or not." + }, + "HealthCheckTimeout": { + "type": "integer", + "description": "Indicates the MongoDB health check timeout in milliseconds." + }, + "Tracing": { + "type": "boolean", + "description": "Indicates whether the Open Telemetry tracing is enabled or not." + } + } + } + } + } + } + } + }, + "type": "object" +} diff --git a/src/Components/Aspire.MongoDB.Driver/MongoDBSettings.cs b/src/Components/Aspire.MongoDB.Driver/MongoDBSettings.cs new file mode 100644 index 00000000000..5de62631c6d --- /dev/null +++ b/src/Components/Aspire.MongoDB.Driver/MongoDBSettings.cs @@ -0,0 +1,33 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Aspire.MongoDB.Driver; + +/// +/// Provides the client configuration settings for connecting to a MongoDB database using MongoDB driver. +/// +public sealed class MongoDBSettings +{ + /// + /// The connection string of the MongoDB database to connect to. + /// + public string? ConnectionString { get; set; } + + /// + /// Gets or sets a boolean value that indicates whether the MongoDB health check is enabled or not. + /// Enabled by default. + /// + public bool HealthCheckEnabled { get; set; } = true; + + /// + /// Gets or sets a integer value that indicates the MongoDB health check timeout in milliseconds. + /// + public int? HealthCheckTimeout { get; set; } + + /// + /// Gets or sets a boolean value that indicates whether the Open Telemetry tracing is enabled or not. + /// Enabled by default. + /// + public bool Tracing { get; set; } = true; + +} diff --git a/src/Components/Aspire.MongoDB.Driver/README.md b/src/Components/Aspire.MongoDB.Driver/README.md new file mode 100644 index 00000000000..ab66e4e9076 --- /dev/null +++ b/src/Components/Aspire.MongoDB.Driver/README.md @@ -0,0 +1,113 @@ +# Aspire.MongoDB.Driver library + +Registers [MongoClient](https://www.mongodb.com/docs/drivers/csharp/current/quick-start/#add-mongodb-as-a-dependency) in the DI container for connecting MongoDB database. + +## Getting started + +### Prerequisites + +- MongoDB database and connection string for accessing the database. + +### Install the package + +Install the .NET Aspire MongoDB.Driver library with [NuGet](https://www.nuget.org): + +```dotnetcli +dotnet add package Aspire.MongoDB.Driver +``` + +## Usage example + +In the _Program.cs_ file of your project, call the `AddMongoDBClient` extension method to register a `IMongoClient` for use via the dependency injection container. The method takes a connection name parameter. + +```csharp +builder.AddMongoDBClient("mongodb"); +``` + +You can then retrieve a `IMongoClient` instance using dependency injection. For example, to retrieve a connection from a Web API controller: + +```csharp +private readonly IMongoClient _client; + +public ProductsController(IMongoClient client) +{ + _client = client; +} +``` + +## Configuration + +The .NET Aspire MongoDB component provides multiple options to configure the database connection based on the requirements and conventions of your project. + +### Use a connection string + +When using a connection string from the `ConnectionStrings` configuration section, you can provide the name of the connection string when calling `builder.AddMongoDBClient()`: + +```csharp +builder.AddMongoDBClient("myConnection"); +``` + +And then the connection string will be retrieved from the `ConnectionStrings` configuration section: + +```json +{ + "ConnectionStrings": { + "myConnection": "mongodb://server:port/test", + } +} +``` + +See the [ConnectionString documentation](https://www.mongodb.com/docs/v3.0/reference/connection-string/) for more information on how to format this connection string. + +### Use configuration providers + +The .NET Aspire MongoDB component supports [Microsoft.Extensions.Configuration](https://learn.microsoft.com/dotnet/api/microsoft.extensions.configuration). It loads the `MongoDBSettings` from configuration by using the `Aspire:MongoDB:Driver` key. Example `appsettings.json` that configures some of the options: + +```json +{ + "Aspire": { + "MongoDB": { + "Driver": { + "ConnectionString": "mongodb://server:port/test", + "HealthCheckEnabled": true, + "HealthCheckTimeout": 10000, + "Tracing": true + }, + } + } +} +``` + +### Use inline delegates + +Also you can pass the `Action configureSettings` delegate to set up some or all the options inline: + +```csharp + builder.AddMongoDBClient("mongodb", settings => settings.ConnectionString = "mongodb://server:port/test"); +``` + +## AppHost extensions + +In your AppHost project, register a MongoDB container and consume the connection using the following methods: + +```csharp +var mongodb = builder.AddMongoDBContainer("mongodb").AddDatabase("mydatabase"); + +var myService = builder.AddProject() + .WithReference(mongodb); +``` + +The `WithReference` method configures a connection in the `MyService` project named `mongodb`. In the _Program.cs_ file of `MyService`, the database connection can be consumed using: + +```csharp +builder.AddMongoDBClient("mongodb"); +``` + +## Additional documentation + +* https://www.mongodb.com/docs/drivers/csharp/current/quick-start/ +* https://github.com/dotnet/aspire/tree/main/src/Components/README.md + +## Feedback & contributing + +https://github.com/dotnet/aspire diff --git a/src/Components/Aspire_Components_Progress.md b/src/Components/Aspire_Components_Progress.md index 427a1ac1c66..6bfd399c7ac 100644 --- a/src/Components/Aspire_Components_Progress.md +++ b/src/Components/Aspire_Components_Progress.md @@ -10,6 +10,7 @@ As part of the .NET Aspire November preview, we want to include a set of .NET As | Microsoft.Data.SqlClient | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | | Microsoft.EntityFramework.Cosmos | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | | Microsoft.EntityFrameworkCore.SqlServer | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| MongoDB.Driver | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | | Azure.Data.Tables | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | | Azure.Messaging.ServiceBus | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | | Azure.Security.KeyVault | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | diff --git a/src/Components/Telemetry.md b/src/Components/Telemetry.md index d55e0b7543d..f5b76e3e90d 100644 --- a/src/Components/Telemetry.md +++ b/src/Components/Telemetry.md @@ -78,6 +78,19 @@ Aspire.Microsoft.Data.SqlClient: - "number-of-stasis-connections" - "number-of-reclaimed-connections" +Aspire.MongoDB.Driver: +- Log categories: + - "MongoDB" + - "MongoDB.Command" + - "MongoDB.SDAM" + - "MongoDB.ServerSelection" + - "MongoDB.Connection" + - "MongoDB.Internal" +- Activity source names: + - "MongoDB.Driver.Core.Extensions.DiagnosticSources" +- Metric names: + - none + Aspire.Microsoft.EntityFrameworkCore.Cosmos: - Log categories: - "Azure-Cosmos-Operation-Request-Diagnostics" diff --git a/tests/Aspire.Hosting.Tests/Aspire.Hosting.Tests.csproj b/tests/Aspire.Hosting.Tests/Aspire.Hosting.Tests.csproj index 0b25e781558..4f1e89098ce 100644 --- a/tests/Aspire.Hosting.Tests/Aspire.Hosting.Tests.csproj +++ b/tests/Aspire.Hosting.Tests/Aspire.Hosting.Tests.csproj @@ -3,6 +3,8 @@ $(NetCurrent) true + + $(NoWarn),CS8002 diff --git a/tests/Aspire.Hosting.Tests/MongoDB/AddMongoDBTests.cs b/tests/Aspire.Hosting.Tests/MongoDB/AddMongoDBTests.cs new file mode 100644 index 00000000000..80e2e97a409 --- /dev/null +++ b/tests/Aspire.Hosting.Tests/MongoDB/AddMongoDBTests.cs @@ -0,0 +1,99 @@ +// 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 Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace Aspire.Hosting.Tests; + +public class AddMongoDBTests +{ + [Fact] + public void AddMongoDBContainerWithDefaultsAddsAnnotationMetadata() + { + var appBuilder = DistributedApplication.CreateBuilder(); + + appBuilder.AddMongoDBContainer("mongodb"); + + var app = appBuilder.Build(); + + var appModel = app.Services.GetRequiredService(); + + var containerResource = Assert.Single(appModel.Resources.OfType()); + Assert.Equal("mongodb", containerResource.Name); + + var manifestAnnotation = Assert.Single(containerResource.Annotations.OfType()); + Assert.NotNull(manifestAnnotation.Callback); + + var serviceBinding = Assert.Single(containerResource.Annotations.OfType()); + Assert.Equal(27017, serviceBinding.ContainerPort); + Assert.False(serviceBinding.IsExternal); + Assert.Equal("tcp", serviceBinding.Name); + Assert.Null(serviceBinding.Port); + Assert.Equal(ProtocolType.Tcp, serviceBinding.Protocol); + Assert.Equal("tcp", serviceBinding.Transport); + Assert.Equal("tcp", serviceBinding.UriScheme); + + var containerAnnotation = Assert.Single(containerResource.Annotations.OfType()); + Assert.Equal("latest", containerAnnotation.Tag); + Assert.Equal("mongo", containerAnnotation.Image); + Assert.Null(containerAnnotation.Registry); + } + + [Fact] + public void AddMongoDBContainerAddsAnnotationMetadata() + { + var appBuilder = DistributedApplication.CreateBuilder(); + appBuilder.AddMongoDBContainer("mongodb", 9813); + + var app = appBuilder.Build(); + + var appModel = app.Services.GetRequiredService(); + + var containerResource = Assert.Single(appModel.Resources.OfType()); + Assert.Equal("mongodb", containerResource.Name); + + var manifestAnnotation = Assert.Single(containerResource.Annotations.OfType()); + Assert.NotNull(manifestAnnotation.Callback); + + var serviceBinding = Assert.Single(containerResource.Annotations.OfType()); + Assert.Equal(27017, serviceBinding.ContainerPort); + Assert.False(serviceBinding.IsExternal); + Assert.Equal("tcp", serviceBinding.Name); + Assert.Equal(9813, serviceBinding.Port); + Assert.Equal(ProtocolType.Tcp, serviceBinding.Protocol); + Assert.Equal("tcp", serviceBinding.Transport); + Assert.Equal("tcp", serviceBinding.UriScheme); + + var containerAnnotation = Assert.Single(containerResource.Annotations.OfType()); + Assert.Equal("latest", containerAnnotation.Tag); + Assert.Equal("mongo", containerAnnotation.Image); + Assert.Null(containerAnnotation.Registry); + } + + [Fact] + public void MongoDBCreatesConnectionString() + { + var appBuilder = DistributedApplication.CreateBuilder(); + appBuilder + .AddMongoDBContainer("mongodb", password: "password") + .WithAnnotation( + new AllocatedEndpointAnnotation("mybinding", + ProtocolType.Tcp, + "localhost", + 27017, + "https" + )) + .AddDatabase("mydatabase"); + + var app = appBuilder.Build(); + + var appModel = app.Services.GetRequiredService(); + + var connectionStringResource = Assert.Single(appModel.Resources.OfType()); + var connectionString = connectionStringResource.GetConnectionString(); + + Assert.Equal("mongodb://root:password@localhost:27017/mydatabase", connectionString); + } +} diff --git a/tests/Aspire.Hosting.Tests/MongoDB/MongoDBContainerResourceTests.cs b/tests/Aspire.Hosting.Tests/MongoDB/MongoDBContainerResourceTests.cs new file mode 100644 index 00000000000..f34bcbf519c --- /dev/null +++ b/tests/Aspire.Hosting.Tests/MongoDB/MongoDBContainerResourceTests.cs @@ -0,0 +1,36 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Aspire.Hosting.MongoDB; +using MongoDB.Driver; +using Xunit; + +namespace Aspire.Hosting.Tests.MongoDB; + +public class MongoDBContainerResourceTests +{ + [Theory] + [InlineData("password", "mongodb://root:password@myserver:1000/")] + [InlineData("@abc!$", "mongodb://root:%40abc!$@myserver:1000/")] + [InlineData("mypasswordwitha\"inthemiddle", "mongodb://root:mypasswordwitha\"inthemiddle@myserver:1000/")] + [InlineData("mypasswordwitha\"attheend\"", "mongodb://root:mypasswordwitha\"attheend\"@myserver:1000/")] + [InlineData("\"mypasswordwitha\"atthestart", "mongodb://root:\"mypasswordwitha\"atthestart@myserver:1000/")] + [InlineData("mypasswordwitha'inthemiddle", "mongodb://root:mypasswordwitha'inthemiddle@myserver:1000/")] + [InlineData("mypasswordwitha'attheend'", "mongodb://root:mypasswordwitha'attheend'@myserver:1000/")] + [InlineData("'mypasswordwitha'atthestart", "mongodb://root:'mypasswordwitha'atthestart@myserver:1000/")] + public void TestSpecialCharactersAndEscapeForPassword(string password, string expectedConnectionString) + { + var connectionString = new MongoDBConnectionStringBuilder() + .WithServer("myserver") + .WithPort(1000) + .WithUserName("root") + .WithPassword(password) + .Build(); + + Assert.NotNull(connectionString); + + var builder = MongoUrl.Create(connectionString); + Assert.Equal(password, builder.Password); + Assert.Equal(expectedConnectionString, connectionString); + } +} diff --git a/tests/Aspire.MongoDB.Driver.Tests/Aspire.MongoDB.Driver.Tests.csproj b/tests/Aspire.MongoDB.Driver.Tests/Aspire.MongoDB.Driver.Tests.csproj new file mode 100644 index 00000000000..ffe79958817 --- /dev/null +++ b/tests/Aspire.MongoDB.Driver.Tests/Aspire.MongoDB.Driver.Tests.csproj @@ -0,0 +1,14 @@ + + + + $(NetCurrent) + + $(NoWarn);CS8002 + + + + + + + + diff --git a/tests/Aspire.MongoDB.Driver.Tests/AspireMongoDBDriverExtensionsTests.cs b/tests/Aspire.MongoDB.Driver.Tests/AspireMongoDBDriverExtensionsTests.cs new file mode 100644 index 00000000000..1ceb641e3f8 --- /dev/null +++ b/tests/Aspire.MongoDB.Driver.Tests/AspireMongoDBDriverExtensionsTests.cs @@ -0,0 +1,173 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using Microsoft.Extensions.Hosting; +using MongoDB.Driver; +using Xunit; + +namespace Aspire.MongoDB.Driver.Tests; + +public class AspireMongoDBDriverExtensionsTests +{ + private const string DefaultConnectionString = "mongodb://localhost:27017/mydatabase"; + private const string DefaultConnectionName = "mongodb"; + + [Theory] + [InlineData("mongodb://localhost:27017/mydatabase", true)] + [InlineData("mongodb://localhost:27017", false)] + public void AddMongoDBDataSource_ReadsFromConnectionStringsCorrectly(string connectionString, bool shouldRegisterDatabase) + { + var builder = CreateBuilder(connectionString); + + builder.AddMongoDBClient(DefaultConnectionName); + + var host = builder.Build(); + + var mongoClient = host.Services.GetRequiredService(); + + var uri = MongoUrl.Create(connectionString); + + Assert.Equal(uri.Server.Host, mongoClient.Settings.Server.Host); + Assert.Equal(uri.Server.Port, mongoClient.Settings.Server.Port); + + var mongoDatabase = host.Services.GetService(); + + if (shouldRegisterDatabase) + { + Assert.NotNull(mongoDatabase); + Assert.Equal(uri.DatabaseName, mongoDatabase.DatabaseNamespace.DatabaseName); + } + else + { + Assert.Null(mongoDatabase); + } + } + + [Theory] + [InlineData("mongodb://localhost:27017/mydatabase", true)] + [InlineData("mongodb://localhost:27017", false)] + public void AddKeyedMongoDBDataSource_ReadsFromConnectionStringsCorrectly(string connectionString, bool shouldRegisterDatabase) + { + var key = DefaultConnectionName; + + var builder = CreateBuilder(connectionString); + + builder.AddKeyedMongoDBClient(key); + + var host = builder.Build(); + + var mongoClient = host.Services.GetRequiredKeyedService(key); + + var uri = MongoUrl.Create(connectionString); + + Assert.Equal(uri.Server.Host, mongoClient.Settings.Server.Host); + Assert.Equal(uri.Server.Port, mongoClient.Settings.Server.Port); + + var mongoDatabase = host.Services.GetKeyedService(key); + + if (shouldRegisterDatabase) + { + Assert.NotNull(mongoDatabase); + Assert.Equal(uri.DatabaseName, mongoDatabase.DatabaseNamespace.DatabaseName); + } + else + { + Assert.Null(mongoDatabase); + } + } + + [Fact] + public async Task AddMongoDBDataSource_HealthCheckShouldBeRegisteredWhenEnabled() + { + var builder = CreateBuilder(DefaultConnectionString); + + builder.AddMongoDBClient(DefaultConnectionName, settings => + { + settings.HealthCheckEnabled = true; + settings.HealthCheckTimeout = 1; + }); + + var host = builder.Build(); + + var healthCheckService = host.Services.GetRequiredService(); + + var healthCheckReport = await healthCheckService.CheckHealthAsync(); + + var healthCheckName = "MongoDB.Driver"; + + Assert.Contains(healthCheckReport.Entries, x => x.Key == healthCheckName); + } + + [Fact] + public void AddKeyedMongoDBDataSource_HealthCheckShouldNotBeRegisteredWhenDisabled() + { + var builder = CreateBuilder(DefaultConnectionString); + + builder.AddKeyedMongoDBClient(DefaultConnectionName, settings => + { + settings.HealthCheckEnabled = false; + }); + + var host = builder.Build(); + + var healthCheckService = host.Services.GetService(); + + Assert.Null(healthCheckService); + + } + + [Fact] + public async Task AddKeyedMongoDBDataSource_HealthCheckShouldBeRegisteredWhenEnabled() + { + var key = DefaultConnectionName; + + var builder = CreateBuilder(DefaultConnectionString); + + builder.AddKeyedMongoDBClient(key, settings => + { + settings.HealthCheckEnabled = true; + settings.HealthCheckTimeout = 1; + }); + + var host = builder.Build(); + + var healthCheckService = host.Services.GetRequiredService(); + + var healthCheckReport = await healthCheckService.CheckHealthAsync(); + + var healthCheckName = $"MongoDB.Driver_{key}"; + + Assert.Contains(healthCheckReport.Entries, x => x.Key == healthCheckName); + } + + [Fact] + public void AddMongoDBDataSource_HealthCheckShouldNotBeRegisteredWhenDisabled() + { + var builder = CreateBuilder(DefaultConnectionString); + + builder.AddMongoDBClient(DefaultConnectionName, settings => + { + settings.HealthCheckEnabled = false; + }); + + var host = builder.Build(); + + var healthCheckService = host.Services.GetService(); + + Assert.Null(healthCheckService); + + } + + private static HostApplicationBuilder CreateBuilder(string connectionString) + { + var builder = Host.CreateEmptyApplicationBuilder(null); + + builder.Configuration.AddInMemoryCollection([ + new KeyValuePair($"ConnectionStrings:{DefaultConnectionName}", connectionString) + ]); + return builder; + } +} diff --git a/tests/Aspire.MongoDB.Driver.Tests/ConformanceTests.cs b/tests/Aspire.MongoDB.Driver.Tests/ConformanceTests.cs new file mode 100644 index 00000000000..862e459a142 --- /dev/null +++ b/tests/Aspire.MongoDB.Driver.Tests/ConformanceTests.cs @@ -0,0 +1,101 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Aspire.Components.ConformanceTests; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using MongoDB.Driver; +using Xunit; + +namespace Aspire.MongoDB.Driver.Tests; + +public class ConformanceTests : ConformanceTests +{ + private const string ConnectionSting = "mongodb://root:password@localhost:27017/test_db"; + + protected override ServiceLifetime ServiceLifetime => ServiceLifetime.Singleton; + + protected override string ActivitySourceName => "MongoDB.Driver.Core.Extensions.DiagnosticSources"; + + protected override string JsonSchemaPath => "src/Components/Aspire.MongoDB.Driver/ConfigurationSchema.json"; + + protected override string ValidJsonConfig => """ + { + "Aspire": { + "MongoDB": { + "Driver": { + "ConnectionString": "YOUR_CONNECTION_STRING", + "HealthCheckEnabled": true, + "HealthCheckTimeout": 100, + "Tracing": true, + "Metrics": true + } + } + } + } + """; + + protected override string[] RequiredLogCategories => [ + "MongoDB.SDAM", + "MongoDB.ServerSelection", + "MongoDB.Connection", + ]; + + protected override void PopulateConfiguration(ConfigurationManager configuration, string? key = null) + => configuration.AddInMemoryCollection(new KeyValuePair[1] + { + new KeyValuePair( + CreateConfigKey("Aspire:MongoDB:Driver", key, "ConnectionString"), + ConnectionSting) + }); + + protected override void RegisterComponent(HostApplicationBuilder builder, Action? configure = null, string? key = null) + { + if (key is null) + { + builder.AddMongoDBClient("mongodb", configure); + } + else + { + builder.AddKeyedMongoDBClient(key, configure); + } + } + + protected override void SetHealthCheck(MongoDBSettings options, bool enabled) + { + options.HealthCheckEnabled = enabled; + options.HealthCheckTimeout = 10; + } + + protected override void SetMetrics(MongoDBSettings options, bool enabled) => throw new NotImplementedException(); + + protected override void SetTracing(MongoDBSettings options, bool enabled) + { + options.Tracing = enabled; + } + + protected override void TriggerActivity(IMongoClient service) + { + using var source = new CancellationTokenSource(10); + + service.ListDatabases(source.Token); + } + + [Theory] + [InlineData(null)] + [InlineData("key")] + public void BothDataSourceAndConnectionCanBeResolved(string? key) + { + using IHost host = CreateHostWithComponent(key: key); + + IMongoClient? mongoClient = Resolve(); + IMongoDatabase? mongoDatabase = Resolve(); + + Assert.NotNull(mongoClient); + Assert.NotNull(mongoDatabase); + + T? Resolve() => key is null ? host.Services.GetService() : host.Services.GetKeyedService(key); + } + +} diff --git a/tests/testproject/TestProject.AppHost/TestProgram.cs b/tests/testproject/TestProject.AppHost/TestProgram.cs index 158a6cd8c5d..4f47ab34b84 100644 --- a/tests/testproject/TestProject.AppHost/TestProgram.cs +++ b/tests/testproject/TestProject.AppHost/TestProgram.cs @@ -37,13 +37,15 @@ private TestProgram(string[] args, Assembly assembly, bool includeIntegrationSer var redis = AppBuilder.AddRedisContainer("redis"); var postgres = AppBuilder.AddPostgresContainer("postgres"); var rabbitmq = AppBuilder.AddRabbitMQContainer("rabbitmq"); + var mongodb = AppBuilder.AddMongoDBContainer("mongodb"); IntegrationServiceABuilder = AppBuilder.AddProject("integrationservicea") .WithReference(sqlserver) .WithReference(mysql) .WithReference(redis) .WithReference(postgres) - .WithReference(rabbitmq); + .WithReference(rabbitmq) + .WithReference(mongodb); } } diff --git a/tests/testproject/TestProject.IntegrationServiceA/Program.cs b/tests/testproject/TestProject.IntegrationServiceA/Program.cs index a8a243cedb9..0448552746b 100644 --- a/tests/testproject/TestProject.IntegrationServiceA/Program.cs +++ b/tests/testproject/TestProject.IntegrationServiceA/Program.cs @@ -7,6 +7,7 @@ builder.AddRedis("redis"); builder.AddNpgsqlDataSource("postgres"); builder.AddRabbitMQ("rabbitmq"); +builder.AddMongoDBClient("mongodb"); var app = builder.Build(); diff --git a/tests/testproject/TestProject.IntegrationServiceA/TestProject.IntegrationServiceA.csproj b/tests/testproject/TestProject.IntegrationServiceA/TestProject.IntegrationServiceA.csproj index 79dc6d40c41..ba89af2d267 100644 --- a/tests/testproject/TestProject.IntegrationServiceA/TestProject.IntegrationServiceA.csproj +++ b/tests/testproject/TestProject.IntegrationServiceA/TestProject.IntegrationServiceA.csproj @@ -1,14 +1,17 @@ - + net8.0 enable enable + + CS8002 + From bc3d5bfe5c788c740d37eafa29c40a2158dc056d Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Fri, 1 Dec 2023 11:45:23 -0800 Subject: [PATCH 02/15] Move package reference to correct section --- Directory.Packages.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 6b5cd6952b7..0254f2f56a6 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -4,7 +4,6 @@ - @@ -36,6 +35,7 @@ + @@ -102,4 +102,4 @@ - \ No newline at end of file + From 4a08605738506aa056d98a139b7298a5f851d9a5 Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Fri, 1 Dec 2023 14:55:25 -0800 Subject: [PATCH 03/15] Use standard HealthChecks property name --- .../AspireMongoDBDriverExtensions.cs | 2 +- .../Aspire.MongoDB.Driver/ConfigurationSchema.json | 10 +++++----- .../Aspire.MongoDB.Driver/MongoDBSettings.cs | 12 ++++++++---- src/Components/Aspire.MongoDB.Driver/README.md | 2 +- .../AspireMongoDBDriverExtensionsTests.cs | 8 ++++---- .../Aspire.MongoDB.Driver.Tests/ConformanceTests.cs | 4 ++-- 6 files changed, 21 insertions(+), 17 deletions(-) diff --git a/src/Components/Aspire.MongoDB.Driver/AspireMongoDBDriverExtensions.cs b/src/Components/Aspire.MongoDB.Driver/AspireMongoDBDriverExtensions.cs index 4df1f6be903..ba936fa8a7e 100644 --- a/src/Components/Aspire.MongoDB.Driver/AspireMongoDBDriverExtensions.cs +++ b/src/Components/Aspire.MongoDB.Driver/AspireMongoDBDriverExtensions.cs @@ -98,7 +98,7 @@ private static void AddMongoDBClient( builder.AddMongoDatabase(settings.ConnectionString, serviceKey); - if (settings.HealthCheckEnabled) + if (settings.HealthChecks) { builder.AddHealthCheck( settings.ConnectionString, diff --git a/src/Components/Aspire.MongoDB.Driver/ConfigurationSchema.json b/src/Components/Aspire.MongoDB.Driver/ConfigurationSchema.json index 2e3a22ae03b..01e046d85e8 100644 --- a/src/Components/Aspire.MongoDB.Driver/ConfigurationSchema.json +++ b/src/Components/Aspire.MongoDB.Driver/ConfigurationSchema.json @@ -35,19 +35,19 @@ "properties": { "ConnectionString": { "type": "string", - "description": "The connection string of the MongoDB database to connect to." + "description": "Gets or sets the connection string of the MongoDB database to connect to." }, - "HealthCheckEnabled": { + "HealthChecks": { "type": "boolean", - "description": "Indicates whether the MongoDB health check is enabled or not." + "description": "Gets or sets a boolean value that indicates whether the MongoDB health check is enabled or not." }, "HealthCheckTimeout": { "type": "integer", - "description": "Indicates the MongoDB health check timeout in milliseconds." + "description": "Gets or sets a integer value that indicates the MongoDB health check timeout in milliseconds." }, "Tracing": { "type": "boolean", - "description": "Indicates whether the Open Telemetry tracing is enabled or not." + "description": "Gets or sets a boolean value that indicates whether the Open Telemetry tracing is enabled or not." } } } diff --git a/src/Components/Aspire.MongoDB.Driver/MongoDBSettings.cs b/src/Components/Aspire.MongoDB.Driver/MongoDBSettings.cs index 5de62631c6d..ebe1e838439 100644 --- a/src/Components/Aspire.MongoDB.Driver/MongoDBSettings.cs +++ b/src/Components/Aspire.MongoDB.Driver/MongoDBSettings.cs @@ -9,15 +9,17 @@ namespace Aspire.MongoDB.Driver; public sealed class MongoDBSettings { /// - /// The connection string of the MongoDB database to connect to. + /// Gets or sets the connection string of the MongoDB database to connect to. /// public string? ConnectionString { get; set; } /// /// Gets or sets a boolean value that indicates whether the MongoDB health check is enabled or not. - /// Enabled by default. + /// + /// The default value is . + /// /// - public bool HealthCheckEnabled { get; set; } = true; + public bool HealthChecks { get; set; } = true; /// /// Gets or sets a integer value that indicates the MongoDB health check timeout in milliseconds. @@ -26,7 +28,9 @@ public sealed class MongoDBSettings /// /// Gets or sets a boolean value that indicates whether the Open Telemetry tracing is enabled or not. - /// Enabled by default. + /// + /// The default value is . + /// /// public bool Tracing { get; set; } = true; diff --git a/src/Components/Aspire.MongoDB.Driver/README.md b/src/Components/Aspire.MongoDB.Driver/README.md index ab66e4e9076..5c3db53f147 100644 --- a/src/Components/Aspire.MongoDB.Driver/README.md +++ b/src/Components/Aspire.MongoDB.Driver/README.md @@ -69,7 +69,7 @@ The .NET Aspire MongoDB component supports [Microsoft.Extensions.Configuration]( "MongoDB": { "Driver": { "ConnectionString": "mongodb://server:port/test", - "HealthCheckEnabled": true, + "HealthChecks": true, "HealthCheckTimeout": 10000, "Tracing": true }, diff --git a/tests/Aspire.MongoDB.Driver.Tests/AspireMongoDBDriverExtensionsTests.cs b/tests/Aspire.MongoDB.Driver.Tests/AspireMongoDBDriverExtensionsTests.cs index 1ceb641e3f8..fe8bfb2826e 100644 --- a/tests/Aspire.MongoDB.Driver.Tests/AspireMongoDBDriverExtensionsTests.cs +++ b/tests/Aspire.MongoDB.Driver.Tests/AspireMongoDBDriverExtensionsTests.cs @@ -86,7 +86,7 @@ public async Task AddMongoDBDataSource_HealthCheckShouldBeRegisteredWhenEnabled( builder.AddMongoDBClient(DefaultConnectionName, settings => { - settings.HealthCheckEnabled = true; + settings.HealthChecks = true; settings.HealthCheckTimeout = 1; }); @@ -108,7 +108,7 @@ public void AddKeyedMongoDBDataSource_HealthCheckShouldNotBeRegisteredWhenDisabl builder.AddKeyedMongoDBClient(DefaultConnectionName, settings => { - settings.HealthCheckEnabled = false; + settings.HealthChecks = false; }); var host = builder.Build(); @@ -128,7 +128,7 @@ public async Task AddKeyedMongoDBDataSource_HealthCheckShouldBeRegisteredWhenEna builder.AddKeyedMongoDBClient(key, settings => { - settings.HealthCheckEnabled = true; + settings.HealthChecks = true; settings.HealthCheckTimeout = 1; }); @@ -150,7 +150,7 @@ public void AddMongoDBDataSource_HealthCheckShouldNotBeRegisteredWhenDisabled() builder.AddMongoDBClient(DefaultConnectionName, settings => { - settings.HealthCheckEnabled = false; + settings.HealthChecks = false; }); var host = builder.Build(); diff --git a/tests/Aspire.MongoDB.Driver.Tests/ConformanceTests.cs b/tests/Aspire.MongoDB.Driver.Tests/ConformanceTests.cs index 862e459a142..775e17c0475 100644 --- a/tests/Aspire.MongoDB.Driver.Tests/ConformanceTests.cs +++ b/tests/Aspire.MongoDB.Driver.Tests/ConformanceTests.cs @@ -26,7 +26,7 @@ public class ConformanceTests : ConformanceTests "MongoDB": { "Driver": { "ConnectionString": "YOUR_CONNECTION_STRING", - "HealthCheckEnabled": true, + "HealthChecks": true, "HealthCheckTimeout": 100, "Tracing": true, "Metrics": true @@ -64,7 +64,7 @@ protected override void RegisterComponent(HostApplicationBuilder builder, Action protected override void SetHealthCheck(MongoDBSettings options, bool enabled) { - options.HealthCheckEnabled = enabled; + options.HealthChecks = enabled; options.HealthCheckTimeout = 10; } From 49835c27913db9a1b6bacbf73ebda938b1c89a0d Mon Sep 17 00:00:00 2001 From: Ailton Silva Date: Mon, 4 Dec 2023 10:22:31 -0300 Subject: [PATCH 04/15] refactor: change methods to void and small fixes in AspireMongoDBDriverExtensions --- .../AspireMongoDBDriverExtensions.cs | 68 +++++++++++-------- 1 file changed, 38 insertions(+), 30 deletions(-) diff --git a/src/Components/Aspire.MongoDB.Driver/AspireMongoDBDriverExtensions.cs b/src/Components/Aspire.MongoDB.Driver/AspireMongoDBDriverExtensions.cs index ba936fa8a7e..d8fd9c3b4cd 100644 --- a/src/Components/Aspire.MongoDB.Driver/AspireMongoDBDriverExtensions.cs +++ b/src/Components/Aspire.MongoDB.Driver/AspireMongoDBDriverExtensions.cs @@ -68,7 +68,7 @@ private static void AddMongoDBClient( Action? configureSettings, Action? configureClientSettings, string connectionName, - string? serviceKey) + object? serviceKey) { ArgumentNullException.ThrowIfNull(builder); @@ -91,62 +91,63 @@ private static void AddMongoDBClient( .WithTracing(tracer => tracer.AddSource(ActivityNameSource)); } - if (string.IsNullOrWhiteSpace(settings.ConnectionString)) - { - return; - } - builder.AddMongoDatabase(settings.ConnectionString, serviceKey); - - if (settings.HealthChecks) - { - builder.AddHealthCheck( - settings.ConnectionString, - serviceKey is null ? "MongoDB.Driver" : $"MongoDB.Driver_{connectionName}", - settings.HealthCheckTimeout); - } + builder.AddHealthCheck( + serviceKey is null ? "MongoDB.Driver" : $"MongoDB.Driver_{connectionName}", + settings); } - private static IServiceCollection AddMongoClient( + private static void AddMongoClient( this IHostApplicationBuilder builder, MongoDBSettings mongoDbSettings, string connectionName, string configurationSectionName, Action? configureClientSettings, - string? serviceKey) + object? serviceKey) { - if (string.IsNullOrWhiteSpace(serviceKey)) + if (string.IsNullOrWhiteSpace(serviceKey as string)) { - return builder + builder .Services .AddSingleton(sp => sp.CreateMongoClient(connectionName, configurationSectionName, mongoDbSettings, configureClientSettings)); + return; } - return builder + builder .Services .AddKeyedSingleton(serviceKey, (sp, _) => sp.CreateMongoClient(connectionName, configurationSectionName, mongoDbSettings, configureClientSettings)); } - private static IServiceCollection AddMongoDatabase(this IHostApplicationBuilder builder, string connectionString, string? serviceKey = null) + private static void AddMongoDatabase( + this IHostApplicationBuilder builder, + string? connectionString, + object? serviceKey = null) { + if (string.IsNullOrWhiteSpace(connectionString)) + { + return; + } + var mongoUrl = MongoUrl.Create(connectionString); if (string.IsNullOrWhiteSpace(mongoUrl.DatabaseName)) { - return builder.Services; + return; } - if (string.IsNullOrWhiteSpace(serviceKey)) + if (string.IsNullOrWhiteSpace(serviceKey as string)) { - return builder.Services.AddSingleton(provider => + builder.Services.AddSingleton(provider => { return provider .GetRequiredService() .GetDatabase(mongoUrl.DatabaseName); }); + + return; } - return builder.Services.AddKeyedSingleton(serviceKey, (provider, _) => + builder.Services.AddKeyedSingleton(serviceKey, (provider, _) => { return provider .GetRequiredKeyedService(serviceKey) @@ -156,18 +157,22 @@ private static IServiceCollection AddMongoDatabase(this IHostApplicationBuilder private static void AddHealthCheck( this IHostApplicationBuilder builder, - string connectionString, string healthCheckName, - int? timeout) + MongoDBSettings settings) { + if (!settings.HealthChecks || string.IsNullOrWhiteSpace(settings.ConnectionString)) + { + return; + } + builder.TryAddHealthCheck( healthCheckName, healthCheck => healthCheck.AddMongoDb( - connectionString, + settings.ConnectionString, healthCheckName, null, null, - timeout > 0 ? TimeSpan.FromMilliseconds(timeout.Value) : null)); + settings.HealthCheckTimeout > 0 ? TimeSpan.FromMilliseconds(settings.HealthCheckTimeout.Value) : null)); } private static MongoClient CreateMongoClient( @@ -199,7 +204,7 @@ private static MongoDBSettings GetMongoDBSettings( string configurationSectionName, Action? configureSettings) { - MongoDBSettings settings = new(); + var settings = new MongoDBSettings(); builder.Configuration .GetSection(configurationSectionName) @@ -215,7 +220,10 @@ private static MongoDBSettings GetMongoDBSettings( return settings; } - private static void ValidateSettings(this MongoDBSettings settings, string connectionName, string configurationSectionName) + private static void ValidateSettings( + this MongoDBSettings settings, + string connectionName, + string configurationSectionName) { if (string.IsNullOrEmpty(settings.ConnectionString)) { From 5d6461e3c8c1bf4bafa7ba79ecfdf43cf65e0c09 Mon Sep 17 00:00:00 2001 From: Ailton Pinto Date: Mon, 4 Dec 2023 10:26:18 -0300 Subject: [PATCH 05/15] Update tests/Aspire.MongoDB.Driver.Tests/AspireMongoDBDriverExtensionsTests.cs Co-authored-by: Eric Erhardt --- .../AspireMongoDBDriverExtensionsTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Aspire.MongoDB.Driver.Tests/AspireMongoDBDriverExtensionsTests.cs b/tests/Aspire.MongoDB.Driver.Tests/AspireMongoDBDriverExtensionsTests.cs index fe8bfb2826e..1b3b952a931 100644 --- a/tests/Aspire.MongoDB.Driver.Tests/AspireMongoDBDriverExtensionsTests.cs +++ b/tests/Aspire.MongoDB.Driver.Tests/AspireMongoDBDriverExtensionsTests.cs @@ -158,7 +158,6 @@ public void AddMongoDBDataSource_HealthCheckShouldNotBeRegisteredWhenDisabled() var healthCheckService = host.Services.GetService(); Assert.Null(healthCheckService); - } private static HostApplicationBuilder CreateBuilder(string connectionString) From 9ce8ef2ead8dba3b66610c4120a47c41a0f53ba4 Mon Sep 17 00:00:00 2001 From: Ailton Silva Date: Mon, 4 Dec 2023 10:40:05 -0300 Subject: [PATCH 06/15] refactor: mongodb conformancetests refactoring --- .../ConformanceTests.cs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/tests/Aspire.MongoDB.Driver.Tests/ConformanceTests.cs b/tests/Aspire.MongoDB.Driver.Tests/ConformanceTests.cs index 775e17c0475..866b204cb22 100644 --- a/tests/Aspire.MongoDB.Driver.Tests/ConformanceTests.cs +++ b/tests/Aspire.MongoDB.Driver.Tests/ConformanceTests.cs @@ -20,6 +20,10 @@ public class ConformanceTests : ConformanceTests protected override string JsonSchemaPath => "src/Components/Aspire.MongoDB.Driver/ConfigurationSchema.json"; + protected override bool SupportsKeyedRegistrations => true; + + protected override bool CanConnectToServer => false; + protected override string ValidJsonConfig => """ { "Aspire": { @@ -28,14 +32,20 @@ public class ConformanceTests : ConformanceTests "ConnectionString": "YOUR_CONNECTION_STRING", "HealthChecks": true, "HealthCheckTimeout": 100, - "Tracing": true, - "Metrics": true + "Tracing": true } } } } """; + protected override (string json, string error)[] InvalidJsonToErrorMessage => new[] + { + ("""{"Aspire": { "MongoDB":{ "Driver": { "HealthChecks": "true"}}}}""", "Value is \"string\" but should be \"boolean\""), + ("""{"Aspire": { "MongoDB":{ "Driver": { "HealthCheckTimeout": "10000"}}}}""", "Value is \"string\" but should be \"integer\""), + ("""{"Aspire": { "MongoDB":{ "Driver": { "Tracing": "true"}}}}""", "Value is \"string\" but should be \"boolean\""), + }; + protected override string[] RequiredLogCategories => [ "MongoDB.SDAM", "MongoDB.ServerSelection", @@ -85,7 +95,7 @@ protected override void TriggerActivity(IMongoClient service) [Theory] [InlineData(null)] [InlineData("key")] - public void BothDataSourceAndConnectionCanBeResolved(string? key) + public void ClientAndDatabaseInstancesShouldBeResolved(string? key) { using IHost host = CreateHostWithComponent(key: key); @@ -97,5 +107,4 @@ public void BothDataSourceAndConnectionCanBeResolved(string? key) T? Resolve() => key is null ? host.Services.GetService() : host.Services.GetKeyedService(key); } - } From 89cecae7e990ca03a917966bbde69dd93b86515d Mon Sep 17 00:00:00 2001 From: Ailton Pinto Date: Mon, 4 Dec 2023 14:39:19 -0300 Subject: [PATCH 07/15] refactor: mongodb connectionString builder Co-authored-by: Eric Erhardt --- .../MongoDB/MongoDBDatabaseResource.cs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/Aspire.Hosting/MongoDB/MongoDBDatabaseResource.cs b/src/Aspire.Hosting/MongoDB/MongoDBDatabaseResource.cs index dcd4a8eecec..0c0c252a628 100644 --- a/src/Aspire.Hosting/MongoDB/MongoDBDatabaseResource.cs +++ b/src/Aspire.Hosting/MongoDB/MongoDBDatabaseResource.cs @@ -23,16 +23,9 @@ public class MongoDBDatabaseResource(string name, MongoDBContainerResource mongo { if (Parent.GetConnectionString() is { } connectionString) { - var builder = new StringBuilder(connectionString); - - if (!connectionString.EndsWith('/')) - { - builder.Append('/'); - } - - builder.Append(Name); - - return builder.ToString(); + return connectionString.EndsWith('/') ? + $"{connectionString}{Name}" : + $"{connectionString}/{Name}"; } throw new DistributedApplicationException("Parent resource connection string was null."); From b880d73bcc7e1f9064b5e2ce40a85ff82ab1c7d0 Mon Sep 17 00:00:00 2001 From: Ailton Pinto Date: Mon, 4 Dec 2023 14:40:04 -0300 Subject: [PATCH 08/15] chore: update mongodb readme Co-authored-by: Eric Erhardt --- src/Components/Aspire.MongoDB.Driver/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Components/Aspire.MongoDB.Driver/README.md b/src/Components/Aspire.MongoDB.Driver/README.md index 5c3db53f147..5d6e6194849 100644 --- a/src/Components/Aspire.MongoDB.Driver/README.md +++ b/src/Components/Aspire.MongoDB.Driver/README.md @@ -1,6 +1,6 @@ # Aspire.MongoDB.Driver library -Registers [MongoClient](https://www.mongodb.com/docs/drivers/csharp/current/quick-start/#add-mongodb-as-a-dependency) in the DI container for connecting MongoDB database. +Registers [IMongoClient](https://www.mongodb.com/docs/drivers/csharp/current/quick-start/#add-mongodb-as-a-dependency) in the DI container for connecting MongoDB database. ## Getting started From b55117081f443655621988978e43ab252deacf1a Mon Sep 17 00:00:00 2001 From: Ailton Pinto Date: Mon, 4 Dec 2023 14:48:05 -0300 Subject: [PATCH 09/15] refactor: refactoring on AspireMongoDBDriverExtensions.cs Co-authored-by: Eric Erhardt --- .../Aspire.MongoDB.Driver/AspireMongoDBDriverExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Components/Aspire.MongoDB.Driver/AspireMongoDBDriverExtensions.cs b/src/Components/Aspire.MongoDB.Driver/AspireMongoDBDriverExtensions.cs index d8fd9c3b4cd..2477584f9b8 100644 --- a/src/Components/Aspire.MongoDB.Driver/AspireMongoDBDriverExtensions.cs +++ b/src/Components/Aspire.MongoDB.Driver/AspireMongoDBDriverExtensions.cs @@ -105,7 +105,7 @@ private static void AddMongoClient( Action? configureClientSettings, object? serviceKey) { - if (string.IsNullOrWhiteSpace(serviceKey as string)) + if (serviceKey is null) { builder .Services From c7adf69b1dd51054b2faeaf1479efdbb0616c0ee Mon Sep 17 00:00:00 2001 From: Ailton Pinto Date: Mon, 4 Dec 2023 14:48:41 -0300 Subject: [PATCH 10/15] refactor: refactoring on AspireMongoDBDriverExtensions.cs Co-authored-by: Eric Erhardt --- .../Aspire.MongoDB.Driver/AspireMongoDBDriverExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Components/Aspire.MongoDB.Driver/AspireMongoDBDriverExtensions.cs b/src/Components/Aspire.MongoDB.Driver/AspireMongoDBDriverExtensions.cs index 2477584f9b8..d4594054b4a 100644 --- a/src/Components/Aspire.MongoDB.Driver/AspireMongoDBDriverExtensions.cs +++ b/src/Components/Aspire.MongoDB.Driver/AspireMongoDBDriverExtensions.cs @@ -135,7 +135,7 @@ private static void AddMongoDatabase( return; } - if (string.IsNullOrWhiteSpace(serviceKey as string)) + if (serviceKey is null) { builder.Services.AddSingleton(provider => { From c027e5188280b21cdd15d01bea484f8e6793bea5 Mon Sep 17 00:00:00 2001 From: Ailton Pinto Date: Mon, 4 Dec 2023 14:50:14 -0300 Subject: [PATCH 11/15] chore: Update tests/testproject/TestProject.IntegrationServiceA/TestProject.IntegrationServiceA.csproj Co-authored-by: Eric Erhardt --- .../TestProject.IntegrationServiceA.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testproject/TestProject.IntegrationServiceA/TestProject.IntegrationServiceA.csproj b/tests/testproject/TestProject.IntegrationServiceA/TestProject.IntegrationServiceA.csproj index ba89af2d267..45a3272abcc 100644 --- a/tests/testproject/TestProject.IntegrationServiceA/TestProject.IntegrationServiceA.csproj +++ b/tests/testproject/TestProject.IntegrationServiceA/TestProject.IntegrationServiceA.csproj @@ -5,7 +5,7 @@ enable enable - CS8002 + $(NoWarn);CS8002 From e405fde42a448dd789c4aa7d79f9996086476582 Mon Sep 17 00:00:00 2001 From: Ailton Silva Date: Mon, 4 Dec 2023 14:59:47 -0300 Subject: [PATCH 12/15] fix: mongodb conformanceTests to check if the server is available --- .../ConformanceTests.cs | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/tests/Aspire.MongoDB.Driver.Tests/ConformanceTests.cs b/tests/Aspire.MongoDB.Driver.Tests/ConformanceTests.cs index 866b204cb22..93bb30b5791 100644 --- a/tests/Aspire.MongoDB.Driver.Tests/ConformanceTests.cs +++ b/tests/Aspire.MongoDB.Driver.Tests/ConformanceTests.cs @@ -14,6 +14,9 @@ public class ConformanceTests : ConformanceTests { private const string ConnectionSting = "mongodb://root:password@localhost:27017/test_db"; + + private static readonly Lazy s_canConnectToServer = new(GetCanConnect); + protected override ServiceLifetime ServiceLifetime => ServiceLifetime.Singleton; protected override string ActivitySourceName => "MongoDB.Driver.Core.Extensions.DiagnosticSources"; @@ -22,7 +25,7 @@ public class ConformanceTests : ConformanceTests protected override bool SupportsKeyedRegistrations => true; - protected override bool CanConnectToServer => false; + protected override bool CanConnectToServer => s_canConnectToServer.Value; protected override string ValidJsonConfig => """ { @@ -107,4 +110,19 @@ public void ClientAndDatabaseInstancesShouldBeResolved(string? key) T? Resolve() => key is null ? host.Services.GetService() : host.Services.GetKeyedService(key); } + + private static bool GetCanConnect() + { + var client = new MongoClient(ConnectionSting); + + try + { + client.ListDatabaseNames(); + return true; + } + catch (Exception) + { + return false; + } + } } From efc6886ca6e7300bbe649ba14dee7dfd92d83578 Mon Sep 17 00:00:00 2001 From: Ailton Silva Date: Mon, 4 Dec 2023 15:15:31 -0300 Subject: [PATCH 13/15] fix: remove unnecessary usings and spaces --- src/Aspire.Hosting/MongoDB/MongoDBDatabaseResource.cs | 2 -- tests/Aspire.MongoDB.Driver.Tests/ConformanceTests.cs | 1 - 2 files changed, 3 deletions(-) diff --git a/src/Aspire.Hosting/MongoDB/MongoDBDatabaseResource.cs b/src/Aspire.Hosting/MongoDB/MongoDBDatabaseResource.cs index 0c0c252a628..7e0e4b45f37 100644 --- a/src/Aspire.Hosting/MongoDB/MongoDBDatabaseResource.cs +++ b/src/Aspire.Hosting/MongoDB/MongoDBDatabaseResource.cs @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Text; - namespace Aspire.Hosting.ApplicationModel; /// diff --git a/tests/Aspire.MongoDB.Driver.Tests/ConformanceTests.cs b/tests/Aspire.MongoDB.Driver.Tests/ConformanceTests.cs index 93bb30b5791..b816f42936e 100644 --- a/tests/Aspire.MongoDB.Driver.Tests/ConformanceTests.cs +++ b/tests/Aspire.MongoDB.Driver.Tests/ConformanceTests.cs @@ -14,7 +14,6 @@ public class ConformanceTests : ConformanceTests { private const string ConnectionSting = "mongodb://root:password@localhost:27017/test_db"; - private static readonly Lazy s_canConnectToServer = new(GetCanConnect); protected override ServiceLifetime ServiceLifetime => ServiceLifetime.Singleton; From 0f52a59b6750c8ce096d6c45f2fb109dc1f4ef25 Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Tue, 5 Dec 2023 14:19:38 -0800 Subject: [PATCH 14/15] Remove authentication altogether --- .../MongoDB/MongoDBBuilderExtensions.cs | 10 ++-------- .../MongoDB/MongoDBContainerResource.cs | 11 +---------- 2 files changed, 3 insertions(+), 18 deletions(-) diff --git a/src/Aspire.Hosting/MongoDB/MongoDBBuilderExtensions.cs b/src/Aspire.Hosting/MongoDB/MongoDBBuilderExtensions.cs index da6c665b020..00f20464d85 100644 --- a/src/Aspire.Hosting/MongoDB/MongoDBBuilderExtensions.cs +++ b/src/Aspire.Hosting/MongoDB/MongoDBBuilderExtensions.cs @@ -13,8 +13,6 @@ namespace Aspire.Hosting; public static class MongoDBBuilderExtensions { private const int DefaultContainerPort = 27017; - private const string PasswordEnvVarName = "MONGO_INITDB_ROOT_PASSWORD"; - private const string UserNameEnvVarName = "MONGO_INITDB_ROOT_USERNAME"; /// /// Adds a MongoDB container to the application model. The default image is "mongo" and the tag is "latest". @@ -31,17 +29,13 @@ public static IResourceBuilder AddMongoDBContainer( int? port = null, string? password = null) { - password ??= Guid.NewGuid().ToString("N"); - - var mongoDBContainer = new MongoDBContainerResource(name, password); + var mongoDBContainer = new MongoDBContainerResource(name); return builder .AddResource(mongoDBContainer) .WithManifestPublishingCallback(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) - .WithEnvironment(UserNameEnvVarName, () => mongoDBContainer.UserName); + .WithAnnotation(new ContainerImageAnnotation { Image = "mongo", Tag = "latest" }); } /// diff --git a/src/Aspire.Hosting/MongoDB/MongoDBContainerResource.cs b/src/Aspire.Hosting/MongoDB/MongoDBContainerResource.cs index 637fece1276..fbb68f23811 100644 --- a/src/Aspire.Hosting/MongoDB/MongoDBContainerResource.cs +++ b/src/Aspire.Hosting/MongoDB/MongoDBContainerResource.cs @@ -9,15 +9,8 @@ namespace Aspire.Hosting.ApplicationModel; /// A resource that represents a MongoDB container. /// /// The name of the resource. -/// The MongoDB root password. -public class MongoDBContainerResource(string name, string password) : ContainerResource(name), IMongoDBResource +public class MongoDBContainerResource(string name) : ContainerResource(name), IMongoDBResource { - private const string DefaultUserName = "root"; - - public string Password { get; } = password; - - public string UserName { get; } = DefaultUserName; - /// /// Gets the connection string for the MongoDB server. /// @@ -34,8 +27,6 @@ public class MongoDBContainerResource(string name, string password) : ContainerR return new MongoDBConnectionStringBuilder() .WithServer(allocatedEndpoint.Address) .WithPort(allocatedEndpoint.Port) - .WithUserName(UserName) - .WithPassword(Password) .Build(); } } From fbcc1ad34af7f4dd796752ddb8f74528e5bc6eec Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Tue, 5 Dec 2023 14:32:25 -0800 Subject: [PATCH 15/15] Fix unit tests --- src/Aspire.Hosting/MongoDB/MongoDBBuilderExtensions.cs | 4 +--- tests/Aspire.Hosting.Tests/MongoDB/AddMongoDBTests.cs | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Aspire.Hosting/MongoDB/MongoDBBuilderExtensions.cs b/src/Aspire.Hosting/MongoDB/MongoDBBuilderExtensions.cs index 00f20464d85..a2bffeddc3d 100644 --- a/src/Aspire.Hosting/MongoDB/MongoDBBuilderExtensions.cs +++ b/src/Aspire.Hosting/MongoDB/MongoDBBuilderExtensions.cs @@ -21,13 +21,11 @@ public static class MongoDBBuilderExtensions /// The . /// The name of the resource. This name will be used as the connection string name when referenced in a dependency. /// The host port for MongoDB. - /// The password for the MongoDB root user. Defaults to a random password. /// A reference to the . public static IResourceBuilder AddMongoDBContainer( this IDistributedApplicationBuilder builder, string name, - int? port = null, - string? password = null) + int? port = null) { var mongoDBContainer = new MongoDBContainerResource(name); diff --git a/tests/Aspire.Hosting.Tests/MongoDB/AddMongoDBTests.cs b/tests/Aspire.Hosting.Tests/MongoDB/AddMongoDBTests.cs index 80e2e97a409..b33c6e1267f 100644 --- a/tests/Aspire.Hosting.Tests/MongoDB/AddMongoDBTests.cs +++ b/tests/Aspire.Hosting.Tests/MongoDB/AddMongoDBTests.cs @@ -77,7 +77,7 @@ public void MongoDBCreatesConnectionString() { var appBuilder = DistributedApplication.CreateBuilder(); appBuilder - .AddMongoDBContainer("mongodb", password: "password") + .AddMongoDBContainer("mongodb") .WithAnnotation( new AllocatedEndpointAnnotation("mybinding", ProtocolType.Tcp, @@ -94,6 +94,6 @@ public void MongoDBCreatesConnectionString() var connectionStringResource = Assert.Single(appModel.Resources.OfType()); var connectionString = connectionStringResource.GetConnectionString(); - Assert.Equal("mongodb://root:password@localhost:27017/mydatabase", connectionString); + Assert.Equal("mongodb://localhost:27017/mydatabase", connectionString); } }