diff --git a/src/HealthChecks.Redis/DependencyInjection/RedisHealthCheckBuilderExtensions.cs b/src/HealthChecks.Redis/DependencyInjection/RedisHealthCheckBuilderExtensions.cs
index b1d2d638ab..a50d2323d1 100644
--- a/src/HealthChecks.Redis/DependencyInjection/RedisHealthCheckBuilderExtensions.cs
+++ b/src/HealthChecks.Redis/DependencyInjection/RedisHealthCheckBuilderExtensions.cs
@@ -66,6 +66,37 @@ public static IHealthChecksBuilder AddRedis(
timeout));
}
+ ///
+ /// Add a health check for Redis services.
+ ///
+ /// The .
+ /// A factory to build the Redis connection string to use.
+ /// The health check name. Optional. If null the type name 'redis' 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 AddRedis(
+ this IHealthChecksBuilder builder,
+ Func> connectionStringFactory,
+ string? name = default,
+ HealthStatus? failureStatus = default,
+ IEnumerable? tags = default,
+ TimeSpan? timeout = default)
+ {
+ Guard.ThrowIfNull(connectionStringFactory);
+
+ return builder.Add(new HealthCheckRegistration(
+ name ?? NAME,
+ sp => new RedisHealthCheck((ct) => connectionStringFactory(sp, ct)),
+ failureStatus,
+ tags,
+ timeout));
+ }
+
///
/// Add a health check for Redis services.
///
@@ -120,4 +151,35 @@ public static IHealthChecksBuilder AddRedis(
tags,
timeout));
}
+
+ ///
+ /// Add a health check for Redis services.
+ ///
+ /// The .
+ /// A factory to build the Redis connection to use.
+ /// The health check name. Optional. If null the type name 'redis' 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 AddRedis(
+ this IHealthChecksBuilder builder,
+ Func> connectionMultiplexerFactory,
+ string? name = default,
+ HealthStatus? failureStatus = default,
+ IEnumerable? tags = default,
+ TimeSpan? timeout = default)
+ {
+ Guard.ThrowIfNull(connectionMultiplexerFactory);
+
+ return builder.Add(new HealthCheckRegistration(
+ name ?? NAME,
+ sp => new RedisHealthCheck((ct) => connectionMultiplexerFactory(sp, ct)),
+ failureStatus,
+ tags,
+ timeout));
+ }
}
diff --git a/src/HealthChecks.Redis/RedisHealthCheck.cs b/src/HealthChecks.Redis/RedisHealthCheck.cs
index 48a163674d..14f54a8647 100644
--- a/src/HealthChecks.Redis/RedisHealthCheck.cs
+++ b/src/HealthChecks.Redis/RedisHealthCheck.cs
@@ -9,14 +9,19 @@ namespace HealthChecks.Redis;
///
public class RedisHealthCheck : IHealthCheck
{
- private static readonly ConcurrentDictionary _connections = new();
- private readonly string? _redisConnectionString;
+ private static readonly ConcurrentDictionary>, IConnectionMultiplexer> _connections = new();
+ private readonly Func>? _redisConnectionStringFactory;
private readonly IConnectionMultiplexer? _connectionMultiplexer;
- private readonly Func? _connectionMultiplexerFactory;
+ private readonly Func>? _connectionMultiplexerFactory;
public RedisHealthCheck(string redisConnectionString)
{
- _redisConnectionString = Guard.ThrowIfNull(redisConnectionString);
+ _redisConnectionStringFactory = (_) => Task.FromResult(Guard.ThrowIfNull(redisConnectionString));
+ }
+
+ public RedisHealthCheck(Func> redisConnectionStringFactory)
+ {
+ _redisConnectionStringFactory = Guard.ThrowIfNull(redisConnectionStringFactory);
}
public RedisHealthCheck(IConnectionMultiplexer connectionMultiplexer)
@@ -35,6 +40,11 @@ public RedisHealthCheck(IConnectionMultiplexer connectionMultiplexer)
/// for the first time, so exceptions can be handled gracefully.
///
internal RedisHealthCheck(Func connectionMultiplexerFactory)
+ {
+ _connectionMultiplexerFactory = (ct) => Task.FromResult(connectionMultiplexerFactory());
+ }
+
+ internal RedisHealthCheck(Func> connectionMultiplexerFactory)
{
_connectionMultiplexerFactory = connectionMultiplexerFactory;
}
@@ -44,13 +54,19 @@ public async Task CheckHealthAsync(HealthCheckContext context
{
try
{
- IConnectionMultiplexer? connection = _connectionMultiplexer ?? _connectionMultiplexerFactory?.Invoke();
+ var connection = (_connectionMultiplexer, _connectionMultiplexerFactory) switch
+ {
+ (not null, _) => _connectionMultiplexer,
+ (null, { } factory) => await factory(cancellationToken).ConfigureAwait(false),
+ _ => null
+ };
- if (_redisConnectionString is not null && !_connections.TryGetValue(_redisConnectionString, out connection))
+ if (_redisConnectionStringFactory is not null && !_connections.TryGetValue(_redisConnectionStringFactory, out connection))
{
try
{
- var connectionMultiplexerTask = ConnectionMultiplexer.ConnectAsync(_redisConnectionString!);
+ var redisConnectionString = await _redisConnectionStringFactory(cancellationToken).ConfigureAwait(false);
+ var connectionMultiplexerTask = ConnectionMultiplexer.ConnectAsync(redisConnectionString!);
connection = await TimeoutAsync(connectionMultiplexerTask, cancellationToken).ConfigureAwait(false);
}
catch (OperationCanceledException)
@@ -58,11 +74,11 @@ public async Task CheckHealthAsync(HealthCheckContext context
return new HealthCheckResult(context.Registration.FailureStatus, description: "Healthcheck timed out");
}
- if (!_connections.TryAdd(_redisConnectionString, connection))
+ if (!_connections.TryAdd(_redisConnectionStringFactory, connection))
{
// Dispose new connection which we just created, because we don't need it.
connection.Dispose();
- connection = _connections[_redisConnectionString];
+ connection = _connections[_redisConnectionStringFactory];
}
}
@@ -99,9 +115,9 @@ public async Task CheckHealthAsync(HealthCheckContext context
}
catch (Exception ex)
{
- if (_redisConnectionString is not null)
+ if (_redisConnectionStringFactory is not null)
{
- _connections.TryRemove(_redisConnectionString, out var connection);
+ _connections.TryRemove(_redisConnectionStringFactory, out var connection);
#pragma warning disable IDISP007 // Don't dispose injected [false positive here]
connection?.Dispose();
#pragma warning restore IDISP007 // Don't dispose injected
diff --git a/test/HealthChecks.Redis.Tests/DependencyInjection/RegistrationTests.cs b/test/HealthChecks.Redis.Tests/DependencyInjection/RegistrationTests.cs
index 7513076441..327bf7fe83 100644
--- a/test/HealthChecks.Redis.Tests/DependencyInjection/RegistrationTests.cs
+++ b/test/HealthChecks.Redis.Tests/DependencyInjection/RegistrationTests.cs
@@ -62,6 +62,29 @@ public void add_health_check_with_connection_string_factory_when_properly_config
factoryCalled.ShouldBeTrue();
}
+ [Fact]
+ public void add_health_check_with_async_connection_string_factory_when_properly_configured()
+ {
+ var services = new ServiceCollection();
+ var factoryCalled = false;
+ services.AddHealthChecks()
+ .AddRedis((sp, ct) =>
+ {
+ factoryCalled = true;
+ return Task.FromResult("connectionstring");
+ });
+
+ using var serviceProvider = services.BuildServiceProvider();
+ var options = serviceProvider.GetRequiredService>();
+
+ var registration = options.Value.Registrations.First();
+ var check = registration.Factory(serviceProvider);
+
+ registration.Name.ShouldBe("redis");
+ check.ShouldBeOfType();
+ factoryCalled.ShouldBeFalse();
+ }
+
[Fact]
public void add_named_health_check_with_connection_multiplexer_when_properly_configured()
{
@@ -109,4 +132,33 @@ public void add_health_check_with_connection_multiplexer_when_properly_configure
// the factory is called when it's used for the first time, as it can throw
factoryCalled.ShouldBeFalse();
}
+
+ [Fact]
+ public void add_health_check_with_async_connection_multiplexer_when_properly_configured()
+ {
+ var connectionMultiplexer = Substitute.For();
+ var services = new ServiceCollection();
+
+ services.AddSingleton(connectionMultiplexer);
+ var factoryCalled = false;
+
+ services.AddHealthChecks()
+ .AddRedis((sp, _) =>
+ {
+ factoryCalled = true;
+ var multiplexer = sp.GetRequiredService();
+ return Task.FromResult(multiplexer);
+ });
+
+ using var serviceProvider = services.BuildServiceProvider();
+ var options = serviceProvider.GetRequiredService>();
+
+ var registration = options.Value.Registrations.First();
+ var check = registration.Factory(serviceProvider);
+
+ registration.Name.ShouldBe("redis");
+ check.ShouldBeOfType();
+ // the factory is called when it's used for the first time, as it can throw
+ factoryCalled.ShouldBeFalse();
+ }
}
diff --git a/test/HealthChecks.Redis.Tests/HealthChecks.Redis.approved.txt b/test/HealthChecks.Redis.Tests/HealthChecks.Redis.approved.txt
index 176cfd64fc..e3a0f715ab 100644
--- a/test/HealthChecks.Redis.Tests/HealthChecks.Redis.approved.txt
+++ b/test/HealthChecks.Redis.Tests/HealthChecks.Redis.approved.txt
@@ -3,6 +3,7 @@ namespace HealthChecks.Redis
public class RedisHealthCheck : Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck
{
public RedisHealthCheck(StackExchange.Redis.IConnectionMultiplexer connectionMultiplexer) { }
+ public RedisHealthCheck(System.Func> redisConnectionStringFactory) { }
public RedisHealthCheck(string redisConnectionString) { }
public System.Threading.Tasks.Task CheckHealthAsync(Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckContext context, System.Threading.CancellationToken cancellationToken = default) { }
}
@@ -14,6 +15,8 @@ namespace Microsoft.Extensions.DependencyInjection
public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddRedis(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, StackExchange.Redis.IConnectionMultiplexer connectionMultiplexer, 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 AddRedis(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, System.Func connectionMultiplexerFactory, 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 AddRedis(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, System.Func connectionStringFactory, 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 AddRedis(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, System.Func> connectionMultiplexerFactory, 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 AddRedis(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, System.Func> connectionStringFactory, 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 AddRedis(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, string redisConnectionString, 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