From b21485e77be634f3304bb82e6b8a7dc7842a5124 Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Fri, 6 Dec 2024 16:24:33 +0100 Subject: [PATCH] Remove private static cache from MongoDbHealthCheck (#2334) * remove the static IMongoClient cache, force the users to follow the best practices (singleton), fixes #2148 * make the field lazy, so it's initialized the first time it's needed (and it's required only when db name was provided) * When running the tests locally during development, don't re-attempt as it prolongs the time it takes to run the tests. * add README * fix a link in Azure doc --- .../README.md | 2 +- .../MongoDbHealthCheckBuilderExtensions.cs | 212 +++--------------- .../MongoDbHealthCheck.cs | 40 ++-- src/HealthChecks.MongoDb/README.md | 43 ++++ .../DependencyInjection/RegistrationTests.cs | 80 +++---- .../Functional/MongoDbHealthCheckTests.cs | 67 ++++-- .../HealthChecks.MongoDb.approved.txt | 12 +- 7 files changed, 166 insertions(+), 290 deletions(-) create mode 100644 src/HealthChecks.MongoDb/README.md diff --git a/src/HealthChecks.Azure.Storage.Blobs/README.md b/src/HealthChecks.Azure.Storage.Blobs/README.md index c592572c36..8ff04419ba 100644 --- a/src/HealthChecks.Azure.Storage.Blobs/README.md +++ b/src/HealthChecks.Azure.Storage.Blobs/README.md @@ -39,5 +39,5 @@ void Configure(IHealthChecksBuilder builder) ### Breaking changes -In the prior releases, `AzureBlobStorageHealthCheck` was a part of `HealthChecks.AzureStorage` package. It had a dependency on not just `Azure.Storage.Blobs`, but also `Azure.Storage.Queues` and `Azure.Storage.Files.Shares`. The packages have been split to avoid bringing unnecessary dependencies. Moreover, `AzureBlobStorageHealthCheck` was letting the users specify how `BlobServiceClient` should be created (from raw connection string or from endpoint uri and managed identity credentials), at a cost of maintaining an internal, static client instances cache. Now the type does not create client instances nor maintain an internal cache and **it's the caller responsibility to provide the instance of `BlobServiceClient`** (please see [#2040](https://github.com/Xabaril/AspNetCore.Diagnostics.HealthChecks/issues/2040) for more details). Since Azure SDK recommends treating clients as singletons and client instances can be expensive to create, it's recommended to register a singleton factory method for Azure SDK clients. So the clients are created only when needed and once per whole application lifetime. +In the prior releases, `AzureBlobStorageHealthCheck` was a part of `HealthChecks.AzureStorage` package. It had a dependency on not just `Azure.Storage.Blobs`, but also `Azure.Storage.Queues` and `Azure.Storage.Files.Shares`. The packages have been split to avoid bringing unnecessary dependencies. Moreover, `AzureBlobStorageHealthCheck` was letting the users specify how `BlobServiceClient` should be created (from raw connection string or from endpoint uri and managed identity credentials), at a cost of maintaining an internal, static client instances cache. Now the type does not create client instances nor maintain an internal cache and **it's the caller responsibility to provide the instance of `BlobServiceClient`** (please see [#2040](https://github.com/Xabaril/AspNetCore.Diagnostics.HealthChecks/issues/2040) for more details). Since Azure SDK [recommends](https://devblogs.microsoft.com/azure-sdk/lifetime-management-and-thread-safety-guarantees-of-azure-sdk-net-clients) treating clients as singletons and client instances can be expensive to create, it's recommended to register a singleton factory method for Azure SDK clients. So the clients are created only when needed and once per whole application lifetime. diff --git a/src/HealthChecks.MongoDb/DependencyInjection/MongoDbHealthCheckBuilderExtensions.cs b/src/HealthChecks.MongoDb/DependencyInjection/MongoDbHealthCheckBuilderExtensions.cs index 39a39b44bb..0a3202cda1 100644 --- a/src/HealthChecks.MongoDb/DependencyInjection/MongoDbHealthCheckBuilderExtensions.cs +++ b/src/HealthChecks.MongoDb/DependencyInjection/MongoDbHealthCheckBuilderExtensions.cs @@ -12,40 +12,14 @@ public static class MongoDbHealthCheckBuilderExtensions private const string NAME = "mongodb"; /// - /// Add a health check for MongoDb database that list all databases on the system. + /// Add a health check for MongoDb that list all databases from specified or pings the database returned by . /// /// The . - /// The MongoDb connection string to be used. - /// The health check name. Optional. If null the type name 'mongodb' will be used for the name. - /// - /// The that should be reported when the health check fails. Optional. If null then - /// the default status of will be reported. + /// + /// An optional factory to obtain instance. + /// When not provided, or is simply resolved from . /// - /// A list of tags that can be used to filter sets of health checks. Optional. - /// An optional representing the timeout of the check. - /// The specified . - public static IHealthChecksBuilder AddMongoDb( - this IHealthChecksBuilder builder, - string mongodbConnectionString, - string? name = default, - HealthStatus? failureStatus = default, - IEnumerable? tags = default, - TimeSpan? timeout = default) - { - return builder.Add(new HealthCheckRegistration( - name ?? NAME, - sp => new MongoDbHealthCheck(mongodbConnectionString), - failureStatus, - tags, - timeout)); - } - - /// - /// Add a health check for MongoDb database that list all collections from specified database on . - /// - /// The . - /// The MongoDb connection string to be used. - /// The Database name to check. + /// An optional factory to obtain the name of the database to ping. /// The health check name. Optional. If null the type name 'mongodb' will be used for the name. /// /// The that should be reported when the health check fails. Optional. If null then @@ -56,8 +30,8 @@ public static IHealthChecksBuilder AddMongoDb( /// The specified . public static IHealthChecksBuilder AddMongoDb( this IHealthChecksBuilder builder, - string mongodbConnectionString, - string mongoDatabaseName, + Func? clientFactory = default, + Func? databaseNameFactory = default, string? name = default, HealthStatus? failureStatus = default, IEnumerable? tags = default, @@ -65,47 +39,26 @@ public static IHealthChecksBuilder AddMongoDb( { return builder.Add(new HealthCheckRegistration( name ?? NAME, - sp => new MongoDbHealthCheck(mongodbConnectionString, mongoDatabaseName), + sp => Factory(sp, clientFactory, databaseNameFactory), failureStatus, tags, timeout)); - } - /// - /// Add a health check for MongoDb database that list all databases on the system. - /// - /// The . - /// A factory to build MongoDb connection string to use. - /// The health check name. Optional. If null the type name 'mongodb' will be used for the name. - /// - /// The that should be reported when the health check fails. Optional. If null then - /// the default status of will be reported. - /// - /// A list of tags that can be used to filter sets of health checks. Optional. - /// An optional representing the timeout of the check. - /// The specified . - public static IHealthChecksBuilder AddMongoDb( - this IHealthChecksBuilder builder, - Func mongodbConnectionStringFactory, - string? name = default, - HealthStatus? failureStatus = default, - IEnumerable? tags = default, - TimeSpan? timeout = default) - { - return builder.Add(new HealthCheckRegistration( - name ?? NAME, - sp => new MongoDbHealthCheck(mongodbConnectionStringFactory(sp)), - failureStatus, - tags, - timeout)); + static MongoDbHealthCheck Factory(IServiceProvider sp, Func? clientFactory, Func? databaseNameFactory) + { + // The user might have registered a factory for MongoClient type, but not for the abstraction (IMongoClient). + // That is why we try to resolve MongoClient first. + IMongoClient client = clientFactory?.Invoke(sp) ?? sp.GetService() ?? sp.GetRequiredService(); + string? databaseName = databaseNameFactory?.Invoke(sp); + return new(client, databaseName); + } } /// - /// Add a health check for MongoDb database that list all collections from specified database on . + /// Add a health check for MongoDb that pings the database. /// /// The . - /// A factory to build MongoDb connection string to use. - /// The Database name to check. + /// A factory to obtain instance. /// The health check name. Optional. If null the type name 'mongodb' will be used for the name. /// /// The that should be reported when the health check fails. Optional. If null then @@ -116,136 +69,21 @@ public static IHealthChecksBuilder AddMongoDb( /// The specified . public static IHealthChecksBuilder AddMongoDb( this IHealthChecksBuilder builder, - Func mongodbConnectionStringFactory, - string mongoDatabaseName, + Func dbFactory, string? name = default, HealthStatus? failureStatus = default, IEnumerable? tags = default, TimeSpan? timeout = default) { - return builder.Add(new HealthCheckRegistration( - name ?? NAME, - sp => new MongoDbHealthCheck(mongodbConnectionStringFactory(sp), mongoDatabaseName), - failureStatus, - tags, - timeout)); - } + Guard.ThrowIfNull(dbFactory); - /// - /// Add a health check for MongoDb that list all databases from specified . - /// - /// The . - /// The MongoClientSettings to be used. - /// The health check name. Optional. If null the type name 'mongodb' will be used for the name. - /// - /// The that should be reported when the health check fails. Optional. If null then - /// the default status of will be reported. - /// - /// A list of tags that can be used to filter sets of health checks. Optional. - /// An optional representing the timeout of the check. - /// The specified . - public static IHealthChecksBuilder AddMongoDb( - this IHealthChecksBuilder builder, - MongoClientSettings mongoClientSettings, - string? name = default, - HealthStatus? failureStatus = default, - IEnumerable? tags = default, - TimeSpan? timeout = default) - { - return builder.Add(new HealthCheckRegistration( - name ?? NAME, - sp => new MongoDbHealthCheck(mongoClientSettings), - failureStatus, - tags, - timeout)); - } - - /// - /// Add a health check for MongoDb database that list all collections from specified database on . - /// - /// The . - /// The MongoClientSettings to be used. - /// The Database name to check. - /// The health check name. Optional. If null the type name 'mongodb' will be used for the name. - /// - /// The that should be reported when the health check fails. Optional. If null then - /// the default status of will be reported. - /// - /// A list of tags that can be used to filter sets of health checks. Optional. - /// An optional representing the timeout of the check. - /// The specified . - public static IHealthChecksBuilder AddMongoDb( - this IHealthChecksBuilder builder, - MongoClientSettings mongoClientSettings, - string mongoDatabaseName, - string? name = default, - HealthStatus? failureStatus = default, - IEnumerable? tags = default, - TimeSpan? timeout = default) - { - return builder.Add(new HealthCheckRegistration( - name ?? NAME, - sp => new MongoDbHealthCheck(mongoClientSettings, mongoDatabaseName), - failureStatus, - tags, - timeout)); - } - - /// - /// Add a health check for MongoDb that list all databases from specified . - /// - /// The . - /// A factory to build MongoClient to be used. - /// The health check name. Optional. If null the type name 'mongodb' will be used for the name. - /// - /// The that should be reported when the health check fails. Optional. If null then - /// the default status of will be reported. - /// - /// A list of tags that can be used to filter sets of health checks. Optional. - /// An optional representing the timeout of the check. - /// The specified . - public static IHealthChecksBuilder AddMongoDb( - this IHealthChecksBuilder builder, - Func mongoClientFactory, - string? name = default, - HealthStatus? failureStatus = default, - IEnumerable? tags = default, - TimeSpan? timeout = default) - { - return builder.Add(new HealthCheckRegistration( - name ?? NAME, - sp => new MongoDbHealthCheck(mongoClientFactory(sp)), - failureStatus, - tags, - timeout)); - } - - /// - /// Add a health check for MongoDb database that list all collections from specified database on . - /// - /// The . - /// A factory to build MongoClient to be used. - /// The name of the database to check. - /// The health check name. Optional. If null the type name 'mongodb' will be used for the name. - /// - /// The that should be reported when the health check fails. Optional. If null then - /// the default status of will be reported. - /// - /// A list of tags that can be used to filter sets of health checks. Optional. - /// An optional representing the timeout of the check. - /// The specified . - public static IHealthChecksBuilder AddMongoDb( - this IHealthChecksBuilder builder, - Func mongoClientFactory, - string mongoDatabaseName, - string? name = default, - HealthStatus? failureStatus = default, - IEnumerable? tags = default, - TimeSpan? timeout = default) - { return builder.Add(new HealthCheckRegistration( name ?? NAME, - sp => new MongoDbHealthCheck(mongoClientFactory(sp), mongoDatabaseName), + sp => + { + IMongoDatabase db = dbFactory.Invoke(sp); + return new MongoDbHealthCheck(db.Client, db.DatabaseNamespace.DatabaseName); + }, failureStatus, tags, timeout)); diff --git a/src/HealthChecks.MongoDb/MongoDbHealthCheck.cs b/src/HealthChecks.MongoDb/MongoDbHealthCheck.cs index 36c4cc7235..99a3fc2a5a 100644 --- a/src/HealthChecks.MongoDb/MongoDbHealthCheck.cs +++ b/src/HealthChecks.MongoDb/MongoDbHealthCheck.cs @@ -1,4 +1,3 @@ -using System.Collections.Concurrent; using Microsoft.Extensions.Diagnostics.HealthChecks; using MongoDB.Bson; using MongoDB.Driver; @@ -7,32 +6,23 @@ namespace HealthChecks.MongoDb; public class MongoDbHealthCheck : IHealthCheck { - private const int MAX_PING_ATTEMPTS = 2; + // When running the tests locally during development, don't re-attempt + // as it prolongs the time it takes to run the tests. + private const int MAX_PING_ATTEMPTS +#if DEBUG + = 1; +#else + = 2; +#endif - private static readonly BsonDocumentCommand _command = new(BsonDocument.Parse("{ping:1}")); - private static readonly ConcurrentDictionary _mongoClient = new(); - private readonly MongoClientSettings _mongoClientSettings; + private static readonly Lazy> _command = new(() => new(BsonDocument.Parse("{ping:1}"))); + private readonly IMongoClient _client; private readonly string? _specifiedDatabase; - public MongoDbHealthCheck(string connectionString, string? databaseName = default) - : this(MongoClientSettings.FromUrl(MongoUrl.Create(connectionString)), databaseName) - { - if (databaseName == default) - { - _specifiedDatabase = MongoUrl.Create(connectionString)?.DatabaseName; - } - } - public MongoDbHealthCheck(IMongoClient client, string? databaseName = default) - : this(client.Settings, databaseName) - { - _mongoClient[_mongoClientSettings.ToString()] = client; - } - - public MongoDbHealthCheck(MongoClientSettings clientSettings, string? databaseName = default) { + _client = client; _specifiedDatabase = databaseName; - _mongoClientSettings = clientSettings; } /// @@ -40,8 +30,6 @@ public async Task CheckHealthAsync(HealthCheckContext context { try { - var mongoClient = _mongoClient.GetOrAdd(_mongoClientSettings.ToString(), _ => new MongoClient(_mongoClientSettings)); - if (!string.IsNullOrEmpty(_specifiedDatabase)) { // some users can't list all databases depending on database privileges, with @@ -57,9 +45,9 @@ public async Task CheckHealthAsync(HealthCheckContext context { try { - await mongoClient + await _client .GetDatabase(_specifiedDatabase) - .RunCommandAsync(_command, cancellationToken: cancellationToken) + .RunCommandAsync(_command.Value, cancellationToken: cancellationToken) .ConfigureAwait(false); break; } @@ -80,7 +68,7 @@ await mongoClient } else { - using var cursor = await mongoClient.ListDatabaseNamesAsync(cancellationToken).ConfigureAwait(false); + using var cursor = await _client.ListDatabaseNamesAsync(cancellationToken).ConfigureAwait(false); await cursor.FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); } diff --git a/src/HealthChecks.MongoDb/README.md b/src/HealthChecks.MongoDb/README.md new file mode 100644 index 0000000000..a5ee186a4f --- /dev/null +++ b/src/HealthChecks.MongoDb/README.md @@ -0,0 +1,43 @@ +## MongoDB Health Check + +This health check verifies the ability to communicate with [MongoDB](https://www.mongodb.com/). It uses the provided [MongoClient](https://www.mongodb.com/docs/drivers/csharp/current/) to list database names or ping configured database. + +### Defaults + +By default, the `MongoClient` instance is resolved from service provider. + +```csharp +void Configure(IHealthChecksBuilder builder) +{ + builder.Services + .AddSingleton(sp => new MongoClient("mongodb://localhost:27017")) + .AddHealthChecks() + .AddMongoDb(); +} +``` + +### Customization + +You can additionally add the following parameters: + +- `clientFactory`: A factory method to provide `MongoClient` instance. +- `databaseNameFactory`: A factory method to provide database name. +- `name`: The health check name. The default is `mongodb`. +- `failureStatus`: The `HealthStatus` that should be reported when the health check fails. Default is `HealthStatus.Unhealthy`. +- `tags`: A list of tags that can be used to filter sets of health checks. +- `timeout`: A `System.TimeSpan` representing the timeout of the check. + +```csharp +void Configure(IHealthChecksBuilder builder) +{ + builder.Services + .AddSingleton(sp => new MongoClient("mongodb://localhost:27017")) + .AddHealthChecks() + .AddMongoDb(databaseNameFactory: sp => "theName"); +} +``` + +### Breaking changes + +`MongoDbHealthCheck` was letting the users specify how `MongoClient` should be created (from raw connection string or from `MongoUrl` or from `MongoClientSettings`), at a cost of maintaining an internal, static client instances cache. Now the type does not create client instances nor maintain an internal cache and **it's the caller responsibility to provide the instance of `MongoClient`** (please see [#2048](https://github.com/Xabaril/AspNetCore.Diagnostics.HealthChecks/issues/2148) for more details). Since MongoDB [recommends](https://mongodb.github.io/mongo-csharp-driver/2.17/reference/driver/connecting#re-use) treating clients as singletons and client instances can be expensive to create, it's recommended to register a singleton factory method for `MongoClient`. So the client is created only when needed and once per whole application lifetime. + diff --git a/test/HealthChecks.MongoDb.Tests/DependencyInjection/RegistrationTests.cs b/test/HealthChecks.MongoDb.Tests/DependencyInjection/RegistrationTests.cs index 05b6b34c0b..1fda660dbd 100644 --- a/test/HealthChecks.MongoDb.Tests/DependencyInjection/RegistrationTests.cs +++ b/test/HealthChecks.MongoDb.Tests/DependencyInjection/RegistrationTests.cs @@ -4,28 +4,24 @@ namespace HealthChecks.MongoDb.Tests.DependencyInjection; public class mongodb_registration_should { - [Fact] - public void add_health_check_when_properly_configured_connectionString() + [Theory] + [InlineData(true)] + [InlineData(false)] + public void add_health_check_when_properly_configured_mongoClientFactory_defaults(bool registerAsAbstraction) { var services = new ServiceCollection(); - services.AddHealthChecks() - .AddMongoDb("mongodb://connectionstring"); - - using var serviceProvider = services.BuildServiceProvider(); - var options = serviceProvider.GetRequiredService>(); - var registration = options.Value.Registrations.First(); - var check = registration.Factory(serviceProvider); + if (registerAsAbstraction) + { + services.AddSingleton(sp => new MongoClient(MongoUrl.Create("mongodb://connectionstring"))); + } + else + { + services.AddSingleton(sp => new MongoClient(MongoUrl.Create("mongodb://connectionstring"))); + } - registration.Name.ShouldBe("mongodb"); - check.ShouldBeOfType(); - } - [Fact] - public void add_health_check_when_properly_configured_mongoClientSettings() - { - var services = new ServiceCollection(); services.AddHealthChecks() - .AddMongoDb(MongoClientSettings.FromUrl(MongoUrl.Create("mongodb://connectionstring"))); + .AddMongoDb(); // use default arguments and get the client resolved from the container using var serviceProvider = services.BuildServiceProvider(); var options = serviceProvider.GetRequiredService>(); @@ -36,16 +32,16 @@ public void add_health_check_when_properly_configured_mongoClientSettings() registration.Name.ShouldBe("mongodb"); check.ShouldBeOfType(); } + [Fact] - public void add_health_check_when_properly_configured_mongoClientFactory() + public void add_health_check_when_properly_configured_mongoClientFactory_custom() { var services = new ServiceCollection(); services .AddSingleton(MongoClientSettings.FromUrl(MongoUrl.Create("mongodb://connectionstring"))) - .AddSingleton(sp => new MongoClient(sp.GetRequiredService())) .AddHealthChecks() - .AddMongoDb(sp => sp.GetRequiredService()); + .AddMongoDb(sp => new MongoClient(sp.GetRequiredService())); using var serviceProvider = services.BuildServiceProvider(); var options = serviceProvider.GetRequiredService>(); @@ -56,30 +52,17 @@ public void add_health_check_when_properly_configured_mongoClientFactory() registration.Name.ShouldBe("mongodb"); check.ShouldBeOfType(); } - [Fact] - public void add_named_health_check_when_properly_configured_connectionString() - { - var services = new ServiceCollection(); - services.AddHealthChecks() - .AddMongoDb("mongodb://connectionstring", name: "my-mongodb-group"); - - using var serviceProvider = services.BuildServiceProvider(); - var options = serviceProvider.GetRequiredService>(); - - var registration = options.Value.Registrations.First(); - var check = registration.Factory(serviceProvider); - registration.Name.ShouldBe("my-mongodb-group"); - check.ShouldBeOfType(); - } [Fact] - public void add_named_health_check_when_properly_configured_mongoClientSettings() + public void add_health_check_when_properly_configured_mongoDatabaseNameFactory_custom() { + bool called = false; var services = new ServiceCollection(); services + .AddSingleton(sp => new MongoClient(MongoUrl.Create("mongodb://connectionstring"))) .AddHealthChecks() - .AddMongoDb(MongoClientSettings.FromUrl(MongoUrl.Create("mongodb://connectionstring")), name: "my-mongodb-group"); + .AddMongoDb(databaseNameFactory: _ => { called = true; return "customName"; }); using var serviceProvider = services.BuildServiceProvider(); var options = serviceProvider.GetRequiredService>(); @@ -87,27 +70,20 @@ public void add_named_health_check_when_properly_configured_mongoClientSettings( var registration = options.Value.Registrations.First(); var check = registration.Factory(serviceProvider); - registration.Name.ShouldBe("my-mongodb-group"); + registration.Name.ShouldBe("mongodb"); check.ShouldBeOfType(); + called.ShouldBeTrue(); } + [Fact] - public void add_named_health_check_when_properly_configured_mongoClientFactory() + public void throw_argumentNullException_for_null_dbFactory() { var services = new ServiceCollection(); + var healthChecksBuilder = services.AddHealthChecks(); - services - .AddSingleton(MongoClientSettings.FromUrl(MongoUrl.Create("mongodb://connectionstring"))) - .AddSingleton(sp => new MongoClient(sp.GetRequiredService())) - .AddHealthChecks() - .AddMongoDb(sp => sp.GetRequiredService(), name: "my-mongodb-group"); - - using var serviceProvider = services.BuildServiceProvider(); - var options = serviceProvider.GetRequiredService>(); + Action addNull = () => healthChecksBuilder.AddMongoDb(dbFactory: null!); - var registration = options.Value.Registrations.First(); - var check = registration.Factory(serviceProvider); - - registration.Name.ShouldBe("my-mongodb-group"); - check.ShouldBeOfType(); + addNull + .ShouldThrow(); } } diff --git a/test/HealthChecks.MongoDb.Tests/Functional/MongoDbHealthCheckTests.cs b/test/HealthChecks.MongoDb.Tests/Functional/MongoDbHealthCheckTests.cs index 61735d1a71..bee1c28fbb 100644 --- a/test/HealthChecks.MongoDb.Tests/Functional/MongoDbHealthCheckTests.cs +++ b/test/HealthChecks.MongoDb.Tests/Functional/MongoDbHealthCheckTests.cs @@ -1,4 +1,5 @@ using System.Net; +using MongoDB.Driver; namespace HealthChecks.MongoDb.Tests.Functional; @@ -12,8 +13,10 @@ public async Task be_healthy_listing_all_databases_if_mongodb_is_available() var webHostBuilder = new WebHostBuilder() .ConfigureServices(services => { - services.AddHealthChecks() - .AddMongoDb(connectionString, tags: ["mongodb"]); + services + .AddSingleton(sp => new MongoClient(connectionString)) + .AddHealthChecks() + .AddMongoDb(tags: ["mongodb"]); }) .Configure(app => { @@ -38,8 +41,10 @@ public async Task be_healthy_on_specified_database_if_mongodb_is_available_and_d var webHostBuilder = new WebHostBuilder() .ConfigureServices(services => { - services.AddHealthChecks() - .AddMongoDb(connectionString, mongoDatabaseName: "local", tags: ["mongodb"]); + services + .AddSingleton(sp => new MongoClient(connectionString)) + .AddHealthChecks() + .AddMongoDb(databaseNameFactory: _ => "local", tags: ["mongodb"]); }) .Configure(app => { @@ -55,6 +60,7 @@ public async Task be_healthy_on_specified_database_if_mongodb_is_available_and_d response.StatusCode.ShouldBe(HttpStatusCode.OK); } + [Fact] public async Task be_healthy_on_connectionstring_specified_database_if_mongodb_is_available_and_database_exist() { @@ -63,8 +69,10 @@ public async Task be_healthy_on_connectionstring_specified_database_if_mongodb_i var webHostBuilder = new WebHostBuilder() .ConfigureServices(services => { - services.AddHealthChecks() - .AddMongoDb(connectionString, tags: ["mongodb"]); + services + .AddSingleton(sp => new MongoClient(connectionString)) + .AddHealthChecks() + .AddMongoDb(tags: ["mongodb"]); }) .Configure(app => { @@ -80,6 +88,33 @@ public async Task be_healthy_on_connectionstring_specified_database_if_mongodb_i response.StatusCode.ShouldBe(HttpStatusCode.OK); } + + [Fact] + public async Task be_healthy_on_connectionstring_specified_database_if_mongodb_is_available_and_database_exist_dbFactory() + { + var webHostBuilder = new WebHostBuilder() + .ConfigureServices(services => + { + services + .AddSingleton(sp => new MongoClient("mongodb://localhost:27017").GetDatabase("namedDb")) + .AddHealthChecks() + .AddMongoDb(dbFactory: sp => sp.GetRequiredService(), tags: ["mongodb"]); + }) + .Configure(app => + { + app.UseHealthChecks("/health", new HealthCheckOptions + { + Predicate = r => r.Tags.Contains("mongodb") + }); + }); + + using var server = new TestServer(webHostBuilder); + + using var response = await server.CreateRequest("/health").GetAsync(); + + response.StatusCode.ShouldBe(HttpStatusCode.OK); + } + [Fact] public async Task be_healthy_on_connectionstring_specified_database_if_mongodb_is_available_and_database_not_exist() { @@ -89,8 +124,10 @@ public async Task be_healthy_on_connectionstring_specified_database_if_mongodb_i var webHostBuilder = new WebHostBuilder() .ConfigureServices(services => { - services.AddHealthChecks() - .AddMongoDb(connectionString, tags: ["mongodb"]); + services + .AddSingleton(sp => new MongoClient(connectionString)) + .AddHealthChecks() + .AddMongoDb(tags: ["mongodb"]); }) .Configure(app => { @@ -113,8 +150,10 @@ public async Task be_unhealthy_listing_all_databases_if_mongodb_is_not_available var webHostBuilder = new WebHostBuilder() .ConfigureServices(services => { - services.AddHealthChecks() - .AddMongoDb("mongodb://nonexistingdomain:27017", tags: ["mongodb"]); + services + .AddSingleton(sp => new MongoClient("mongodb://nonexistingdomain:27017")) + .AddHealthChecks() + .AddMongoDb(tags: ["mongodb"]); }) .Configure(app => { @@ -139,10 +178,10 @@ public async Task be_unhealthy_on_specified_database_if_mongodb_is_not_available var webHostBuilder = new WebHostBuilder() .ConfigureServices(services => { - services.AddHealthChecks() - .AddMongoDb("mongodb://nonexistingdomain:27017", - mongoDatabaseName: mongoDatabaseName, - tags: ["mongodb"]); + services + .AddSingleton(sp => new MongoClient("mongodb://nonexistingdomain:27017")) + .AddHealthChecks() + .AddMongoDb(databaseNameFactory: _ => mongoDatabaseName, tags: ["mongodb"]); }) .Configure(app => { diff --git a/test/HealthChecks.MongoDb.Tests/HealthChecks.MongoDb.approved.txt b/test/HealthChecks.MongoDb.Tests/HealthChecks.MongoDb.approved.txt index 88613ac36a..654b566c0e 100644 --- a/test/HealthChecks.MongoDb.Tests/HealthChecks.MongoDb.approved.txt +++ b/test/HealthChecks.MongoDb.Tests/HealthChecks.MongoDb.approved.txt @@ -3,8 +3,6 @@ namespace HealthChecks.MongoDb public class MongoDbHealthCheck : Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck { public MongoDbHealthCheck(MongoDB.Driver.IMongoClient client, string? databaseName = null) { } - public MongoDbHealthCheck(MongoDB.Driver.MongoClientSettings clientSettings, string? databaseName = null) { } - public MongoDbHealthCheck(string connectionString, string? databaseName = null) { } public System.Threading.Tasks.Task CheckHealthAsync(Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckContext context, System.Threading.CancellationToken cancellationToken = default) { } } } @@ -12,13 +10,7 @@ namespace Microsoft.Extensions.DependencyInjection { public static class MongoDbHealthCheckBuilderExtensions { - public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddMongoDb(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, MongoDB.Driver.MongoClientSettings mongoClientSettings, string? name = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = default, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = default) { } - public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddMongoDb(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, System.Func mongoClientFactory, string? name = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = default, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = default) { } - public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddMongoDb(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, System.Func mongodbConnectionStringFactory, string? name = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = default, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = default) { } - public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddMongoDb(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string mongodbConnectionString, string? name = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = default, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = default) { } - public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddMongoDb(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, MongoDB.Driver.MongoClientSettings mongoClientSettings, string mongoDatabaseName, string? name = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = default, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = default) { } - public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddMongoDb(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, System.Func mongoClientFactory, string mongoDatabaseName, string? name = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = default, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = default) { } - public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddMongoDb(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, System.Func mongodbConnectionStringFactory, string mongoDatabaseName, string? name = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = default, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = default) { } - public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddMongoDb(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string mongodbConnectionString, string mongoDatabaseName, string? name = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = default, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = default) { } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddMongoDb(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, System.Func dbFactory, string? name = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = default, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = default) { } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddMongoDb(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, System.Func? clientFactory = null, System.Func? databaseNameFactory = null, string? name = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = default, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = default) { } } } \ No newline at end of file