diff --git a/build/dependencies.props b/build/dependencies.props index 7ceb24699e..149d505b18 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -17,6 +17,7 @@ 3.0.0-alpha1-10173 3.0.0-alpha1-10173 3.0.0-alpha1-10173 + 3.0.0-alpha1-10173 3.0.0-alpha1-10173 3.0.0-alpha1-10173 3.0.0-alpha1-10173 @@ -37,6 +38,7 @@ 4.7.49 2.0.3 11.0.2 + 4.6.0-preview1-26725-04 4.6.0-preview1-26727-04 1.7.0-preview1-26727-04 2.3.1 diff --git a/samples/HealthChecksSample/DBHealthStartup.cs b/samples/HealthChecksSample/DBHealthStartup.cs new file mode 100644 index 0000000000..f82f4c54ef --- /dev/null +++ b/samples/HealthChecksSample/DBHealthStartup.cs @@ -0,0 +1,47 @@ + +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace HealthChecksSample +{ + // Pass in `--scenario db` at the command line to run this sample. + public class DBHealthStartup + { + public DBHealthStartup(IConfiguration configuration) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + public void ConfigureServices(IServiceCollection services) + { + // Registers required services for health checks + services.AddHealthChecks() + + // Add a health check for a SQL database + .AddCheck(new SqlConnectionHealthCheck("MyDatabase", Configuration["ConnectionStrings:DefaultConnection"])); + } + + public void Configure(IApplicationBuilder app, IHostingEnvironment env) + { + // This will register the health checks middleware at the URL /health. + // + // By default health checks will return a 200 with 'Healthy' when the database is responsive + // - We've registered a SqlConnectionHealthCheck + // - The default response writer writes the HealthCheckStatus as text/plain content + // + // This is the simplest way to use health checks, it is suitable for systems + // that want to check for 'liveness' of an application with a database. + app.UseHealthChecks("/health"); + + app.Run(async (context) => + { + await context.Response.WriteAsync("Go to /health to see the health status"); + }); + } + } +} diff --git a/samples/HealthChecksSample/DbConnectionHealthCheck.cs b/samples/HealthChecksSample/DbConnectionHealthCheck.cs new file mode 100644 index 0000000000..00d9bd4803 --- /dev/null +++ b/samples/HealthChecksSample/DbConnectionHealthCheck.cs @@ -0,0 +1,64 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Data.Common; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Diagnostics.HealthChecks; + +namespace HealthChecksSample +{ + public abstract class DbConnectionHealthCheck : IHealthCheck + { + protected DbConnectionHealthCheck(string name, string connectionString) + : this(name, connectionString, testQuery: null) + { + } + + protected DbConnectionHealthCheck(string name, string connectionString, string testQuery) + { + Name = name ?? throw new System.ArgumentNullException(nameof(name)); + ConnectionString = connectionString ?? throw new ArgumentNullException(nameof(connectionString)); + TestQuery = testQuery; + } + + public string Name { get; } + + protected string ConnectionString { get; } + + // This sample supports specifying a query to run as a boolean test of whether the database + // is responding. It is important to choose a query that will return quickly or you risk + // overloading the database. + // + // In most cases this is not necessary, but if you find it necessary, choose a simple query such as 'SELECT 1'. + protected string TestQuery { get; } + + protected abstract DbConnection CreateConnection(string connectionString); + + public async Task CheckHealthAsync(CancellationToken cancellationToken = default(CancellationToken)) + { + using (var connection = CreateConnection(ConnectionString)) + { + try + { + await connection.OpenAsync(cancellationToken); + + if (TestQuery != null) + { + var command = connection.CreateCommand(); + command.CommandText = TestQuery; + + await command.ExecuteNonQueryAsync(cancellationToken); + } + } + catch (DbException ex) + { + return HealthCheckResult.Unhealthy(ex); + } + } + + return HealthCheckResult.Healthy(); + } + } +} diff --git a/samples/HealthChecksSample/HealthChecksSample.csproj b/samples/HealthChecksSample/HealthChecksSample.csproj index c8a2fded73..b013e687fa 100644 --- a/samples/HealthChecksSample/HealthChecksSample.csproj +++ b/samples/HealthChecksSample/HealthChecksSample.csproj @@ -8,10 +8,12 @@ + + diff --git a/samples/HealthChecksSample/Program.cs b/samples/HealthChecksSample/Program.cs index a5c804e8f0..d24e0b38c1 100644 --- a/samples/HealthChecksSample/Program.cs +++ b/samples/HealthChecksSample/Program.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; @@ -19,6 +20,7 @@ namespace HealthChecksSample { "writer", typeof(CustomWriterStartup) }, { "liveness", typeof(LivenessProbeStartup) }, { "port", typeof(ManagementPortStartup) }, + { "db", typeof(DBHealthStartup) }, }; } @@ -30,6 +32,8 @@ namespace HealthChecksSample public static IWebHost BuildWebHost(string[] args) { var config = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json") .AddEnvironmentVariables(prefix: "ASPNETCORE_") .AddCommandLine(args) .Build(); diff --git a/samples/HealthChecksSample/SqlConnectionHealthCheck.cs b/samples/HealthChecksSample/SqlConnectionHealthCheck.cs new file mode 100644 index 0000000000..5849da2202 --- /dev/null +++ b/samples/HealthChecksSample/SqlConnectionHealthCheck.cs @@ -0,0 +1,26 @@ +using System.Data.Common; +using System.Data.SqlClient; +using Microsoft.Extensions.Diagnostics.HealthChecks; + +namespace HealthChecksSample +{ + public class SqlConnectionHealthCheck : DbConnectionHealthCheck + { + private static readonly string DefaultTestQuery = "Select 1"; + + public SqlConnectionHealthCheck(string name, string connectionString) + : this(name, connectionString, testQuery: DefaultTestQuery) + { + } + + public SqlConnectionHealthCheck(string name, string connectionString, string testQuery) + : base(name, connectionString, testQuery ?? DefaultTestQuery) + { + } + + protected override DbConnection CreateConnection(string connectionString) + { + return new SqlConnection(connectionString); + } + } +} diff --git a/samples/HealthChecksSample/appsettings.json b/samples/HealthChecksSample/appsettings.json new file mode 100644 index 0000000000..77b5f890a4 --- /dev/null +++ b/samples/HealthChecksSample/appsettings.json @@ -0,0 +1,5 @@ +{ + "ConnectionStrings": { + "DefaultConnection": "Server=(localdb)\\MSSQLLocalDB;Database=HealthCheckSample;Trusted_Connection=True;MultipleActiveResultSets=true;ConnectRetryCount=0" + } +} \ No newline at end of file