diff --git a/Aspire.sln b/Aspire.sln index 67aaa7e232b..a76dfced011 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 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestProject.LaunchSettings", "tests\testproject\TestProject.LaunchSettings\TestProject.LaunchSettings.csproj", "{A734177E-213B-4D68-98A4-6F5C00234053}" EndProject Global @@ -432,6 +436,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 {A734177E-213B-4D68-98A4-6F5C00234053}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A734177E-213B-4D68-98A4-6F5C00234053}.Debug|Any CPU.Build.0 = Debug|Any CPU {A734177E-213B-4D68-98A4-6F5C00234053}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -510,6 +526,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} {A734177E-213B-4D68-98A4-6F5C00234053} = {975F6F41-B455-451D-A312-098DE4A167B6} EndGlobalSection diff --git a/Directory.Packages.props b/Directory.Packages.props index 28016362143..0254f2f56a6 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -35,6 +35,7 @@ + @@ -71,6 +72,8 @@ + + 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..a2bffeddc3d --- /dev/null +++ b/src/Aspire.Hosting/MongoDB/MongoDBBuilderExtensions.cs @@ -0,0 +1,86 @@ +// 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; + + /// + /// 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. + /// A reference to the . + public static IResourceBuilder AddMongoDBContainer( + this IDistributedApplicationBuilder builder, + string name, + int? port = null) + { + 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" }); + } + + /// + /// 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..fbb68f23811 --- /dev/null +++ b/src/Aspire.Hosting/MongoDB/MongoDBContainerResource.cs @@ -0,0 +1,32 @@ +// 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. +public class MongoDBContainerResource(string name) : ContainerResource(name), IMongoDBResource +{ + /// + /// 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) + .Build(); + } +} diff --git a/src/Aspire.Hosting/MongoDB/MongoDBDatabaseResource.cs b/src/Aspire.Hosting/MongoDB/MongoDBDatabaseResource.cs new file mode 100644 index 00000000000..7e0e4b45f37 --- /dev/null +++ b/src/Aspire.Hosting/MongoDB/MongoDBDatabaseResource.cs @@ -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; + +/// +/// 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) + { + return connectionString.EndsWith('/') ? + $"{connectionString}{Name}" : + $"{connectionString}/{Name}"; + } + + 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..d4594054b4a --- /dev/null +++ b/src/Components/Aspire.MongoDB.Driver/AspireMongoDBDriverExtensions.cs @@ -0,0 +1,233 @@ +// 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, + object? 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)); + } + + builder.AddMongoDatabase(settings.ConnectionString, serviceKey); + builder.AddHealthCheck( + serviceKey is null ? "MongoDB.Driver" : $"MongoDB.Driver_{connectionName}", + settings); + } + + private static void AddMongoClient( + this IHostApplicationBuilder builder, + MongoDBSettings mongoDbSettings, + string connectionName, + string configurationSectionName, + Action? configureClientSettings, + object? serviceKey) + { + if (serviceKey is null) + { + builder + .Services + .AddSingleton(sp => sp.CreateMongoClient(connectionName, configurationSectionName, mongoDbSettings, configureClientSettings)); + return; + } + + builder + .Services + .AddKeyedSingleton(serviceKey, (sp, _) => sp.CreateMongoClient(connectionName, configurationSectionName, mongoDbSettings, configureClientSettings)); + } + + 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; + } + + if (serviceKey is null) + { + 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 healthCheckName, + MongoDBSettings settings) + { + if (!settings.HealthChecks || string.IsNullOrWhiteSpace(settings.ConnectionString)) + { + return; + } + + builder.TryAddHealthCheck( + healthCheckName, + healthCheck => healthCheck.AddMongoDb( + settings.ConnectionString, + healthCheckName, + null, + null, + settings.HealthCheckTimeout > 0 ? TimeSpan.FromMilliseconds(settings.HealthCheckTimeout.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) + { + var settings = new MongoDBSettings(); + + 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..01e046d85e8 --- /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": "Gets or sets the connection string of the MongoDB database to connect to." + }, + "HealthChecks": { + "type": "boolean", + "description": "Gets or sets a boolean value that indicates whether the MongoDB health check is enabled or not." + }, + "HealthCheckTimeout": { + "type": "integer", + "description": "Gets or sets a integer value that indicates the MongoDB health check timeout in milliseconds." + }, + "Tracing": { + "type": "boolean", + "description": "Gets or sets a boolean value that 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..ebe1e838439 --- /dev/null +++ b/src/Components/Aspire.MongoDB.Driver/MongoDBSettings.cs @@ -0,0 +1,37 @@ +// 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 +{ + /// + /// 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. + /// + /// The default value is . + /// + /// + public bool HealthChecks { 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. + /// + /// 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 new file mode 100644 index 00000000000..5d6e6194849 --- /dev/null +++ b/src/Components/Aspire.MongoDB.Driver/README.md @@ -0,0 +1,113 @@ +# Aspire.MongoDB.Driver library + +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 + +### 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", + "HealthChecks": 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..b33c6e1267f --- /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") + .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://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..1b3b952a931 --- /dev/null +++ b/tests/Aspire.MongoDB.Driver.Tests/AspireMongoDBDriverExtensionsTests.cs @@ -0,0 +1,172 @@ +// 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.HealthChecks = 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.HealthChecks = 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.HealthChecks = 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.HealthChecks = 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..b816f42936e --- /dev/null +++ b/tests/Aspire.MongoDB.Driver.Tests/ConformanceTests.cs @@ -0,0 +1,127 @@ +// 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"; + + private static readonly Lazy s_canConnectToServer = new(GetCanConnect); + + 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 bool SupportsKeyedRegistrations => true; + + protected override bool CanConnectToServer => s_canConnectToServer.Value; + + protected override string ValidJsonConfig => """ + { + "Aspire": { + "MongoDB": { + "Driver": { + "ConnectionString": "YOUR_CONNECTION_STRING", + "HealthChecks": true, + "HealthCheckTimeout": 100, + "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", + "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.HealthChecks = 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 ClientAndDatabaseInstancesShouldBeResolved(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); + } + + private static bool GetCanConnect() + { + var client = new MongoClient(ConnectionSting); + + try + { + client.ListDatabaseNames(); + return true; + } + catch (Exception) + { + return false; + } + } +} 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..45a3272abcc 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 + + $(NoWarn);CS8002 +