diff --git a/src/HealthChecks.MySql/DependencyInjection/MySqlHealthCheckBuilderExtensions.cs b/src/HealthChecks.MySql/DependencyInjection/MySqlHealthCheckBuilderExtensions.cs
index 09e2fac106..fc8fcf0fdc 100644
--- a/src/HealthChecks.MySql/DependencyInjection/MySqlHealthCheckBuilderExtensions.cs
+++ b/src/HealthChecks.MySql/DependencyInjection/MySqlHealthCheckBuilderExtensions.cs
@@ -10,14 +10,50 @@ namespace Microsoft.Extensions.DependencyInjection;
public static class MySqlHealthCheckBuilderExtensions
{
private const string NAME = "mysql";
- internal const string HEALTH_QUERY = "SELECT 1;";
+
+ ///
+ /// Add a health check for MySQL databases.
+ ///
+ /// The .
+ /// An optional factory to create the . By default, one will be retrieved from the service collection.
+ /// The optional query to be executed. If this is null, a MySQL "ping" packet will be sent to the server instead of a query.
+ /// An optional action to allow additional MySQL specific configuration.
+ /// The health check name. Optional. If null the type name 'mysql' 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 AddMySql(
+ this IHealthChecksBuilder builder,
+ Func? dataSourceFactory = null,
+ string? healthQuery = null,
+ Action? configure = null,
+ string? name = default,
+ HealthStatus? failureStatus = default,
+ IEnumerable? tags = default,
+ TimeSpan? timeout = default)
+ {
+ return builder.Add(new HealthCheckRegistration(
+ name ?? NAME,
+ sp => new MySqlHealthCheck(new(dataSourceFactory?.Invoke(sp) ?? sp.GetRequiredService())
+ {
+ CommandText = healthQuery,
+ Configure = configure,
+ }),
+ failureStatus,
+ tags,
+ timeout));
+ }
///
/// Add a health check for MySQL databases.
///
/// The .
/// The MySQL connection string to be used.
- /// The query to be executed.
+ /// The optional query to be executed. If this is null, a MySQL "ping" packet will be sent to the server instead of a query.
/// An optional action to allow additional MySQL specific configuration.
/// The health check name. Optional. If null the type name 'mysql' will be used for the name.
///
@@ -30,14 +66,20 @@ public static class MySqlHealthCheckBuilderExtensions
public static IHealthChecksBuilder AddMySql(
this IHealthChecksBuilder builder,
string connectionString,
- string healthQuery = HEALTH_QUERY,
+ string? healthQuery = null,
Action? configure = null,
string? name = default,
HealthStatus? failureStatus = default,
IEnumerable? tags = default,
TimeSpan? timeout = default)
{
- return builder.AddMySql(_ => connectionString, healthQuery, configure, name, failureStatus, tags, timeout);
+ Guard.ThrowIfNull(connectionString, throwOnEmptyString: true);
+
+ return builder.AddMySql(new(connectionString)
+ {
+ CommandText = healthQuery,
+ Configure = configure
+ }, name, failureStatus, tags, timeout);
}
///
@@ -45,7 +87,7 @@ public static IHealthChecksBuilder AddMySql(
///
/// The .
/// A factory to build the MySQL connection string to use.
- /// The query to be executed.
+ /// The optional query to be executed. If this is null, a MySQL "ping" packet will be sent to the server instead of a query.
/// An optional action to allow additional MySQL specific configuration.
/// The health check name. Optional. If null the type name 'mysql' will be used for the name.
///
@@ -58,7 +100,7 @@ public static IHealthChecksBuilder AddMySql(
public static IHealthChecksBuilder AddMySql(
this IHealthChecksBuilder builder,
Func connectionStringFactory,
- string healthQuery = HEALTH_QUERY,
+ string? healthQuery = null,
Action? configure = null,
string? name = default,
HealthStatus? failureStatus = default,
@@ -69,16 +111,11 @@ public static IHealthChecksBuilder AddMySql(
return builder.Add(new HealthCheckRegistration(
name ?? NAME,
- sp =>
+ sp => new MySqlHealthCheck(new(connectionStringFactory(sp))
{
- var options = new MySqlHealthCheckOptions
- {
- ConnectionString = connectionStringFactory(sp),
- CommandText = healthQuery,
- Configure = configure,
- };
- return new MySqlHealthCheck(options);
- },
+ CommandText = healthQuery,
+ Configure = configure,
+ }),
failureStatus,
tags,
timeout));
diff --git a/src/HealthChecks.MySql/MySqlHealthCheck.cs b/src/HealthChecks.MySql/MySqlHealthCheck.cs
index fb7f433193..e12009cb2d 100644
--- a/src/HealthChecks.MySql/MySqlHealthCheck.cs
+++ b/src/HealthChecks.MySql/MySqlHealthCheck.cs
@@ -12,8 +12,11 @@ public class MySqlHealthCheck : IHealthCheck
public MySqlHealthCheck(MySqlHealthCheckOptions options)
{
- Guard.ThrowIfNull(options.ConnectionString, true);
- Guard.ThrowIfNull(options.CommandText, true);
+ Guard.ThrowIfNull(options);
+ if (options.DataSource is null && options.ConnectionString is null)
+ throw new InvalidOperationException("One of options.DataSource or options.ConnectionString must be specified.");
+ if (options.DataSource is not null && options.ConnectionString is not null)
+ throw new InvalidOperationException("Only one of options.DataSource or options.ConnectionString must be specified.");
_options = options;
}
@@ -22,18 +25,30 @@ public async Task CheckHealthAsync(HealthCheckContext context
{
try
{
- using var connection = new MySqlConnection(_options.ConnectionString);
+ using var connection = _options.DataSource is not null ?
+ _options.DataSource.CreateConnection() :
+ new MySqlConnection(_options.ConnectionString);
_options.Configure?.Invoke(connection);
await connection.OpenAsync(cancellationToken).ConfigureAwait(false);
- using var command = connection.CreateCommand();
- command.CommandText = _options.CommandText;
- object? result = await command.ExecuteScalarAsync(cancellationToken).ConfigureAwait(false);
+ if (_options.CommandText is { } commandText)
+ {
+ using var command = connection.CreateCommand();
+ command.CommandText = _options.CommandText;
+ object? result = await command.ExecuteScalarAsync(cancellationToken).ConfigureAwait(false);
- return _options.HealthCheckResultBuilder == null
- ? HealthCheckResult.Healthy()
- : _options.HealthCheckResultBuilder(result);
+ return _options.HealthCheckResultBuilder == null
+ ? HealthCheckResult.Healthy()
+ : _options.HealthCheckResultBuilder(result);
+ }
+ else
+ {
+ var success = await connection.PingAsync(cancellationToken).ConfigureAwait(false);
+ return _options.HealthCheckResultBuilder is null
+ ? (success ? HealthCheckResult.Healthy() : new HealthCheckResult(context.Registration.FailureStatus)) :
+ _options.HealthCheckResultBuilder(success);
+ }
}
catch (Exception ex)
{
diff --git a/src/HealthChecks.MySql/MySqlHealthCheckOptions.cs b/src/HealthChecks.MySql/MySqlHealthCheckOptions.cs
index d8a1f073d7..a2a15690af 100644
--- a/src/HealthChecks.MySql/MySqlHealthCheckOptions.cs
+++ b/src/HealthChecks.MySql/MySqlHealthCheckOptions.cs
@@ -1,4 +1,3 @@
-using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using MySqlConnector;
@@ -8,16 +7,55 @@ namespace HealthChecks.MySql;
/// Options for .
///
public class MySqlHealthCheckOptions
-{
+{
+ ///
+ /// Creates an instance of .
+ ///
+ /// The to be used.
+ ///
+ /// Depending on how the was configured, the connections it hands out may be pooled.
+ /// That is why it should be the exact same that is used by other parts of your app.
+ ///
+ public MySqlHealthCheckOptions(MySqlDataSource dataSource)
+ {
+ DataSource = Guard.ThrowIfNull(dataSource);
+ }
+
+ ///
+ /// Creates an instance of .
+ ///
+ /// The MySQL connection string to be used.
+ ///
+ /// supports additional configuration beyond the connection string, such as logging and naming pools for diagnostics.
+ /// To specify a data source, use and the constructor.
+ ///
+ public MySqlHealthCheckOptions(string connectionString)
+ {
+ ConnectionString = Guard.ThrowIfNull(connectionString, throwOnEmptyString: true);
+ }
+
+ ///
+ /// The MySQL data source to be used.
+ ///
+ ///
+ /// Depending on how the was configured, the connections it hands out may be pooled.
+ /// That is why it should be the exact same that is used by other parts of your app.
+ ///
+ public MySqlDataSource? DataSource { get; }
+
///
- /// The MySQL connection string to be used.
+ /// The MySQL connection string to be used, if isn't set.
///
- public string ConnectionString { get; set; } = null!;
+ ///
+ /// supports additional configuration beyond the connection string, such as logging and naming pools for diagnostics.
+ /// To specify a data source, use and the constructor.
+ ///
+ public string? ConnectionString { get; }
///
/// The query to be executed.
///
- public string CommandText { get; set; } = MySqlHealthCheckBuilderExtensions.HEALTH_QUERY;
+ public string? CommandText { get; set; }
///
/// An optional action executed before the connection is opened in the health check.
diff --git a/src/HealthChecks.MySql/README.md b/src/HealthChecks.MySql/README.md
new file mode 100644
index 0000000000..6ec4a6db88
--- /dev/null
+++ b/src/HealthChecks.MySql/README.md
@@ -0,0 +1,59 @@
+## MySQL Health Check
+
+This health check verifies the ability to communicate with a MySQL Server.
+It uses the provided [MySqlDataSource](https://mysqlconnector.net/api/mysqlconnector/mysqldatasourcetype/) or a connection string to connect to the server.
+
+### Defaults
+
+By default, the `MySqlDataSource` instance is resolved from service provider.
+(This should be the same as the instance being used by the application; do not create a new `MySqlDataSource` just for the health check.)
+The health check will send a MySQL "ping" packet to the server to verify connectivity.
+
+```csharp
+builder.Services
+ .AddMySqlDataSource(builder.Configuration.GetConnectionString("mysql")) // using the MySqlConnector.DependencyInjection package
+ .AddHealthChecks().AddMySql();
+```
+
+### Connection String
+
+You can also specify a connection string directly:
+
+```csharp
+builder.Services.AddHealthChecks().AddMySql(connectionString: "Server=...;User Id=...;Password=...");
+```
+
+This can be useful if you're not using `MySqlDataSource` in your application.
+
+### Customization
+
+You can additionally add the following parameters:
+
+- `healthQuery`: A query to run against the server. If `null` (the default), the health check will send a MySQL "ping" packet to the server.
+- `configure`: An action to configure the `MySqlConnection` object. This is called after the `MySqlConnection` is created but before the connection is opened.
+- `name`: The health check name. The default is `mysql`.
+- `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
+builder.Services
+ .AddMySqlDataSource(builder.Configuration.GetConnectionString("mysql"))
+ .AddHealthChecks().AddMySql(
+ healthQuery: "SELECT 1;",
+ configure: conn => conn.ConnectTimeout = 3,
+ name: "MySQL"
+ );
+```
+
+### Breaking changes
+
+In previous versions, `MySqlHealthCheck` defaulted to testing connectivity by sending a `SELECT 1;` query to the server.
+It has been changed to send a more efficient "ping" packet instead.
+To restore the previous behavior, specify `healthQuery: "SELECT 1;"` when registering the health check.
+
+While not a breaking change, it's now preferred to use `MySqlDataSource` instead of a connection string.
+This allows the health check to use the same connection pool as the rest of the application.
+This can be achieved by calling the `.AddMySql()` overload that has no required parameters.
+The health check assumes that a `MySqlDataSource` instance has been registered with the service provider and will retrieve it automatically.
+
diff --git a/test/HealthChecks.MySql.Tests/DependencyInjection/RegistrationTests.cs b/test/HealthChecks.MySql.Tests/DependencyInjection/RegistrationTests.cs
index 9f3a030da7..15a1627c67 100644
--- a/test/HealthChecks.MySql.Tests/DependencyInjection/RegistrationTests.cs
+++ b/test/HealthChecks.MySql.Tests/DependencyInjection/RegistrationTests.cs
@@ -1,3 +1,5 @@
+using MySqlConnector;
+
namespace HealthChecks.MySql.Tests.DependencyInjection;
public class mysql_registration_should
@@ -18,6 +20,7 @@ public void add_health_check_when_properly_configured()
registration.Name.ShouldBe("mysql");
check.ShouldBeOfType();
}
+
[Fact]
public void add_named_health_check_when_properly_configured()
{
@@ -34,4 +37,22 @@ public void add_named_health_check_when_properly_configured()
registration.Name.ShouldBe("my-mysql-group");
check.ShouldBeOfType();
}
+
+ [Fact]
+ public void add_health_check_for_data_source()
+ {
+ var services = new ServiceCollection();
+ services
+ .AddMySqlDataSource("Server=example")
+ .AddHealthChecks().AddMySql();
+
+ using var serviceProvider = services.BuildServiceProvider();
+ var options = serviceProvider.GetRequiredService>();
+
+ var registration = options.Value.Registrations.First();
+ var check = registration.Factory(serviceProvider);
+
+ registration.Name.ShouldBe("mysql");
+ check.ShouldBeOfType();
+ }
}
diff --git a/test/HealthChecks.MySql.Tests/Functional/MySqlHealthCheckTests.cs b/test/HealthChecks.MySql.Tests/Functional/MySqlHealthCheckTests.cs
index 802b54f85c..affb98e552 100644
--- a/test/HealthChecks.MySql.Tests/Functional/MySqlHealthCheckTests.cs
+++ b/test/HealthChecks.MySql.Tests/Functional/MySqlHealthCheckTests.cs
@@ -1,11 +1,39 @@
using System.Net;
+using MySqlConnector;
namespace HealthChecks.MySql.Tests.Functional;
public class mysql_healthcheck_should
{
[Fact]
- public async Task be_healthy_when_mysql_server_is_available()
+ public async Task be_healthy_when_mysql_server_is_available_using_data_source()
+ {
+ var connectionString = "server=localhost;port=3306;database=information_schema;uid=root;password=Password12!";
+
+ var webHostBuilder = new WebHostBuilder()
+ .ConfigureServices(services =>
+ {
+ services
+ .AddMySqlDataSource(connectionString)
+ .AddHealthChecks().AddMySql(tags: new string[] { "mysql" });
+ })
+ .Configure(app =>
+ {
+ app.UseHealthChecks("/health", new HealthCheckOptions
+ {
+ Predicate = r => r.Tags.Contains("mysql")
+ });
+ });
+
+ 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_when_mysql_server_is_available_using_connection_string()
{
var connectionString = "server=localhost;port=3306;database=information_schema;uid=root;password=Password12!";
@@ -64,10 +92,7 @@ public async Task be_unhealthy_when_mysql_server_is_unavailable_using_options()
var webHostBuilder = new WebHostBuilder()
.ConfigureServices(services =>
{
- var mysqlOptions = new MySqlHealthCheckOptions
- {
- ConnectionString = connectionString
- };
+ var mysqlOptions = new MySqlHealthCheckOptions(connectionString);
services.AddHealthChecks()
.AddMySql(mysqlOptions, tags: new string[] { "mysql" });
})
diff --git a/test/HealthChecks.MySql.Tests/HealthChecks.MySql.Tests.csproj b/test/HealthChecks.MySql.Tests/HealthChecks.MySql.Tests.csproj
index 5bd9b8276d..b4b45abd6d 100644
--- a/test/HealthChecks.MySql.Tests/HealthChecks.MySql.Tests.csproj
+++ b/test/HealthChecks.MySql.Tests/HealthChecks.MySql.Tests.csproj
@@ -1,5 +1,9 @@
+
+
+
+
diff --git a/test/HealthChecks.MySql.Tests/HealthChecks.MySql.approved.txt b/test/HealthChecks.MySql.Tests/HealthChecks.MySql.approved.txt
index f3dfe4a8d5..4dc33b35cd 100644
--- a/test/HealthChecks.MySql.Tests/HealthChecks.MySql.approved.txt
+++ b/test/HealthChecks.MySql.Tests/HealthChecks.MySql.approved.txt
@@ -7,10 +7,12 @@ namespace HealthChecks.MySql
}
public class MySqlHealthCheckOptions
{
- public MySqlHealthCheckOptions() { }
- public string CommandText { get; set; }
+ public MySqlHealthCheckOptions(MySqlConnector.MySqlDataSource dataSource) { }
+ public MySqlHealthCheckOptions(string connectionString) { }
+ public string? CommandText { get; set; }
public System.Action? Configure { get; set; }
- public string ConnectionString { get; set; }
+ public string? ConnectionString { get; }
+ public MySqlConnector.MySqlDataSource? DataSource { get; }
public System.Func