diff --git a/build/dependencies.props b/build/dependencies.props
index 845b6b06cc..7ceb24699e 100644
--- a/build/dependencies.props
+++ b/build/dependencies.props
@@ -16,6 +16,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
diff --git a/samples/HealthChecksSample/BasicStartup.cs b/samples/HealthChecksSample/BasicStartup.cs
new file mode 100644
index 0000000000..c89d78c8e5
--- /dev/null
+++ b/samples/HealthChecksSample/BasicStartup.cs
@@ -0,0 +1,35 @@
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace HealthChecksSample
+{
+ // Pass in `--scenario basic` at the command line to run this sample.
+ public class BasicStartup
+ {
+ public void ConfigureServices(IServiceCollection services)
+ {
+ // Registers required services for health checks
+ services.AddHealthChecks();
+ }
+
+ 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'.
+ // - No health checks are registered by default, the app is healthy if it is reachable
+ // - 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.
+ app.UseHealthChecks("/health");
+
+ app.Run(async (context) =>
+ {
+ await context.Response.WriteAsync("Go to /health to see the health status");
+ });
+ }
+ }
+}
diff --git a/samples/HealthChecksSample/CustomWriterStartup.cs b/samples/HealthChecksSample/CustomWriterStartup.cs
new file mode 100644
index 0000000000..87e58b7b7d
--- /dev/null
+++ b/samples/HealthChecksSample/CustomWriterStartup.cs
@@ -0,0 +1,67 @@
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Diagnostics.HealthChecks;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Diagnostics.HealthChecks;
+
+namespace HealthChecksSample
+{
+ // Pass in `--scenario writer` at the command line to run this sample.
+ public class CustomWriterStartup
+ {
+ public void ConfigureServices(IServiceCollection services)
+ {
+ // Registers required services for health checks
+ services.AddHealthChecks();
+
+ // This is an example of registering a custom health check as a service.
+ // All IHealthCheck services will be available to the health check service and
+ // middleware.
+ //
+ // We recommend registering all health checks as Singleton services.
+ services.AddSingleton();
+ }
+
+ public void Configure(IApplicationBuilder app, IHostingEnvironment env)
+ {
+ // This will register the health checks middleware at the URL /health
+ //
+ // This example overrides the HealthCheckResponseWriter to write the health
+ // check result in a totally custom way.
+ app.UseHealthChecks("/health", new HealthCheckOptions()
+ {
+ // This custom writer formats the detailed status as an HTML table.
+ ResponseWriter = WriteResponse,
+ });
+
+ app.Run(async (context) =>
+ {
+ await context.Response.WriteAsync("Go to /health to see the health status");
+ });
+ }
+
+ private static Task WriteResponse(HttpContext httpContext, CompositeHealthCheckResult result)
+ {
+ httpContext.Response.ContentType = "text/html";
+ return httpContext.Response.WriteAsync($@"
+
+
+
+ Everything is {result.Status}
+
+
+
+ | Name | Status |
+
+
+ {string.Join("", result.Results.Select(kvp => $"| {kvp.Key} | {kvp.Value.Status} |
"))}
+
+
+
+");
+ }
+ }
+}
diff --git a/samples/HealthChecksSample/DetailedStatusStartup.cs b/samples/HealthChecksSample/DetailedStatusStartup.cs
new file mode 100644
index 0000000000..76475c5db5
--- /dev/null
+++ b/samples/HealthChecksSample/DetailedStatusStartup.cs
@@ -0,0 +1,68 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Diagnostics.HealthChecks;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Diagnostics.HealthChecks;
+
+namespace HealthChecksSample
+{
+ // Pass in `--scenario detailed` at the command line to run this sample.
+ public class DetailedStatusStartup
+ {
+ public void ConfigureServices(IServiceCollection services)
+ {
+ // Registers required services for health checks
+ services
+ .AddHealthChecks()
+
+ // Registers a custom health check, in this case it will execute an
+ // inline delegate.
+ .AddCheck("GC Info", () =>
+ {
+ // This example will report degraded status if the application is using
+ // more than 1gb of memory.
+ //
+ // Additionally we include some GC info in the reported diagnostics.
+ var allocated = GC.GetTotalMemory(forceFullCollection: false);
+ var data = new Dictionary()
+ {
+ { "Allocated", allocated },
+ { "Gen0Collections", GC.CollectionCount(0) },
+ { "Gen1Collections", GC.CollectionCount(1) },
+ { "Gen2Collections", GC.CollectionCount(2) },
+ };
+
+ // Report degraded status if the allocated memory is >= 1gb (in bytes)
+ var status = allocated >= 1024 * 1024 * 1024 ? HealthCheckStatus.Degraded : HealthCheckStatus.Healthy;
+
+ return Task.FromResult(new HealthCheckResult(
+ status,
+ exception: null,
+ description: "reports degraded status if allocated bytes >= 1gb",
+ data: data));
+ });
+ }
+
+ public void Configure(IApplicationBuilder app, IHostingEnvironment env)
+ {
+ // This will register the health checks middleware at the URL /health
+ //
+ // This example overrides the ResponseWriter to include a detailed
+ // status as JSON. Use this response writer (or create your own) to include
+ // detailed diagnostic information for use by a monitoring system.
+ app.UseHealthChecks("/health", new HealthCheckOptions()
+ {
+ ResponseWriter = HealthCheckResponseWriters.WriteDetailedJson,
+ });
+
+ app.Run(async (context) =>
+ {
+ await context.Response.WriteAsync("Go to /health to see the health status");
+ });
+ }
+ }
+}
diff --git a/samples/HealthChecksSample/GCInfoHealthCheck.cs b/samples/HealthChecksSample/GCInfoHealthCheck.cs
new file mode 100644
index 0000000000..644fdb0a32
--- /dev/null
+++ b/samples/HealthChecksSample/GCInfoHealthCheck.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Diagnostics.HealthChecks;
+
+namespace HealthChecksSample
+{
+ // This is an example of a custom health check that implements IHealthCheck.
+ // This is the same core logic as the DetailedStatusStartup example.
+ // See CustomWriterStartup to see how this is registered.
+ public class GCInfoHealthCheck : IHealthCheck
+ {
+ public string Name { get; } = "GCInfo";
+
+ public Task CheckHealthAsync(CancellationToken cancellationToken = default(CancellationToken))
+ {
+ // This example will report degraded status if the application is using
+ // more than 1gb of memory.
+ //
+ // Additionally we include some GC info in the reported diagnostics.
+ var allocated = GC.GetTotalMemory(forceFullCollection: false);
+ var data = new Dictionary()
+ {
+ { "Allocated", allocated },
+ { "Gen0Collections", GC.CollectionCount(0) },
+ { "Gen1Collections", GC.CollectionCount(1) },
+ { "Gen2Collections", GC.CollectionCount(2) },
+ };
+
+ // Report degraded status if the allocated memory is >= 1gb (in bytes)
+ var status = allocated >= 1024 * 1024 * 1024 ? HealthCheckStatus.Degraded : HealthCheckStatus.Healthy;
+
+ return Task.FromResult(new HealthCheckResult(
+ status,
+ exception: null,
+ description: "reports degraded status if allocated bytes >= 1gb",
+ data: data));
+ }
+ }
+}
diff --git a/samples/HealthChecksSample/HealthChecksSample.csproj b/samples/HealthChecksSample/HealthChecksSample.csproj
index 19ef79337e..2f3c0e6aaa 100644
--- a/samples/HealthChecksSample/HealthChecksSample.csproj
+++ b/samples/HealthChecksSample/HealthChecksSample.csproj
@@ -1,10 +1,13 @@
-
+
- netcoreapp2.0
+
+ netcoreapp2.0;net461
+ netcoreapp2.0
+
diff --git a/samples/HealthChecksSample/LivenessProbeStartup.cs b/samples/HealthChecksSample/LivenessProbeStartup.cs
new file mode 100644
index 0000000000..d7645b5f0e
--- /dev/null
+++ b/samples/HealthChecksSample/LivenessProbeStartup.cs
@@ -0,0 +1,80 @@
+using System;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Diagnostics.HealthChecks;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Diagnostics.HealthChecks;
+
+namespace HealthChecksSample
+{
+ // Pass in `--scenario liveness` at the command line to run this sample.
+ public class LivenessProbeStartup
+ {
+ public void ConfigureServices(IServiceCollection services)
+ {
+ // Registers required services for health checks
+ services
+ .AddHealthChecks()
+ .AddCheck("identity", () => Task.FromResult(HealthCheckResult.Healthy()))
+ .AddCheck(new SlowDependencyHealthCheck());
+ }
+
+ public void Configure(IApplicationBuilder app, IHostingEnvironment env)
+ {
+ // This will register the health checks middleware twice:
+ // - at /health/ready for 'readiness'
+ // - at /health/live for 'liveness'
+ //
+ // Using a separate liveness and readiness check is useful in an environment like Kubernetes
+ // when an application needs to do significant work before accepting requests. Using separate
+ // checks allows the orchestrator to distinguish whether the application is functioning but
+ // not yet ready or if the application has failed to start.
+ //
+ // For instance the liveness check will do a quick set of checks to determine if the process
+ // is functioning correctly.
+ //
+ // The readiness check might do a set of more expensive or time-consuming checks to determine
+ // if all other resources are responding.
+ //
+ // See https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/ for
+ // more details about readiness and liveness probes in Kubernetes.
+ //
+ // In this example, the liveness check will us an 'identity' check that always returns healthy.
+ //
+ // In this example, the readiness check will run all registered checks, include a check with an
+ // long initialization time (15 seconds).
+
+
+ // The readiness check uses all of the registered health checks (default)
+ app.UseHealthChecks("/health/ready", new HealthCheckOptions()
+ {
+ // This sample is using detailed status to make more apparent which checks are being run - any
+ // output format will work with liveness and readiness checks.
+ ResponseWriter = HealthCheckResponseWriters.WriteDetailedJson,
+ });
+
+ // The liveness check uses an 'identity' health check that always returns healty
+ app.UseHealthChecks("/health/live", new HealthCheckOptions()
+ {
+ // Filters the set of health checks run by this middleware
+ HealthCheckNames =
+ {
+ "identity",
+ },
+
+ // This sample is using detailed status to make more apparent which checks are being run - any
+ // output format will work with liveness and readiness checks.
+ ResponseWriter = HealthCheckResponseWriters.WriteDetailedJson,
+ });
+
+ app.Run(async (context) =>
+ {
+ await context.Response.WriteAsync("Go to /health/ready to see the readiness status");
+ await context.Response.WriteAsync(Environment.NewLine);
+ await context.Response.WriteAsync("Go to /health/live to see the liveness status");
+ });
+ }
+ }
+}
diff --git a/samples/HealthChecksSample/Program.cs b/samples/HealthChecksSample/Program.cs
index be3f8bcdf1..4d9455ed2d 100644
--- a/samples/HealthChecksSample/Program.cs
+++ b/samples/HealthChecksSample/Program.cs
@@ -1,23 +1,55 @@
+using System;
+using System.Collections.Generic;
using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
namespace HealthChecksSample
{
public class Program
{
+ private static readonly Dictionary _scenarios;
+
+ static Program()
+ {
+ _scenarios = new Dictionary(StringComparer.OrdinalIgnoreCase)
+ {
+ { "", typeof(BasicStartup) },
+ { "basic", typeof(BasicStartup) },
+ { "detailed", typeof(DetailedStatusStartup) },
+ { "writer", typeof(CustomWriterStartup) },
+ { "liveness", typeof(LivenessProbeStartup) },
+ };
+ }
+
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}
- public static IWebHost BuildWebHost(string[] args) =>
- new WebHostBuilder()
+ public static IWebHost BuildWebHost(string[] args)
+ {
+ var config = new ConfigurationBuilder()
+ .AddEnvironmentVariables(prefix: "ASPNETCORE_")
+ .AddCommandLine(args)
+ .Build();
+
+ var scenario = config["scenario"] ?? string.Empty;
+ if (!_scenarios.TryGetValue(scenario, out var startupType))
+ {
+ startupType = typeof(BasicStartup);
+ }
+
+ return new WebHostBuilder()
+ .UseConfiguration(config)
.ConfigureLogging(builder =>
{
builder.AddConsole();
})
.UseKestrel()
- .UseStartup()
+ .UseStartup(startupType)
.Build();
+ }
+
}
}
diff --git a/samples/HealthChecksSample/SlowDependencyHealthCheck.cs b/samples/HealthChecksSample/SlowDependencyHealthCheck.cs
new file mode 100644
index 0000000000..1ca03f88ae
--- /dev/null
+++ b/samples/HealthChecksSample/SlowDependencyHealthCheck.cs
@@ -0,0 +1,32 @@
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Diagnostics.HealthChecks;
+
+namespace HealthChecksSample
+{
+ // Simulates a health check for an application dependency that takes a while to initialize.
+ // This is part of the readiness/liveness probe sample.
+ public class SlowDependencyHealthCheck : IHealthCheck
+ {
+ public static readonly string HealthCheckName = "slow_dependency";
+
+ private readonly Task _task;
+
+ public SlowDependencyHealthCheck()
+ {
+ _task = Task.Delay(15 * 1000);
+ }
+
+ public string Name => HealthCheckName;
+
+ public Task CheckHealthAsync(CancellationToken cancellationToken = default(CancellationToken))
+ {
+ if (_task.IsCompleted)
+ {
+ return Task.FromResult(HealthCheckResult.Healthy("Dependency is ready"));
+ }
+
+ return Task.FromResult(HealthCheckResult.Unhealthy("Dependency is still initializing"));
+ }
+ }
+}
diff --git a/samples/HealthChecksSample/Startup.cs b/samples/HealthChecksSample/Startup.cs
deleted file mode 100644
index e3bb04150c..0000000000
--- a/samples/HealthChecksSample/Startup.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-using Microsoft.AspNetCore.Builder;
-using Microsoft.AspNetCore.Hosting;
-using Microsoft.AspNetCore.Http;
-using Microsoft.Extensions.DependencyInjection;
-
-namespace HealthChecksSample
-{
- public class Startup
- {
- // This method gets called by the runtime. Use this method to add services to the container.
- // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
- public void ConfigureServices(IServiceCollection services)
- {
- services.AddHealthChecks();
- }
-
- // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
- public void Configure(IApplicationBuilder app, IHostingEnvironment env)
- {
- app.UseHealthChecks("/health");
-
- app.Run(async (context) =>
- {
- await context.Response.WriteAsync("Hello World!");
- });
- }
- }
-}
diff --git a/src/Microsoft.AspNetCore.Diagnostics.HealthChecks/Builder/HealthCheckApplicationBuilderExtensions.cs b/src/Microsoft.AspNetCore.Diagnostics.HealthChecks/Builder/HealthCheckApplicationBuilderExtensions.cs
new file mode 100644
index 0000000000..f1f47b3ba5
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Diagnostics.HealthChecks/Builder/HealthCheckApplicationBuilderExtensions.cs
@@ -0,0 +1,67 @@
+// 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 Microsoft.AspNetCore.Diagnostics.HealthChecks;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Options;
+
+namespace Microsoft.AspNetCore.Builder
+{
+ ///
+ /// extension methods for the .
+ ///
+ public static class HealthCheckApplicationBuilderExtensions
+ {
+ ///
+ /// Adds a middleware that provides health check status.
+ ///
+ /// The .
+ /// The path on which to provide health check status.
+ /// A reference to the after the operation has completed.
+ ///
+ /// The health check middleware will use default settings other than the provided .
+ ///
+ public static IApplicationBuilder UseHealthChecks(this IApplicationBuilder app, PathString path)
+ {
+ if (app == null)
+ {
+ throw new ArgumentNullException(nameof(app));
+ }
+
+ if (!path.HasValue)
+ {
+ throw new ArgumentException("A URL path must be provided", nameof(path));
+ }
+
+ return app.Map(path, b => b.UseMiddleware());
+ }
+
+ ///
+ /// Adds a middleware that provides health check status.
+ ///
+ /// The .
+ /// The path on which to provide health check status.
+ /// A used to configure the middleware.
+ /// A reference to the after the operation has completed.
+ public static IApplicationBuilder UseHealthChecks(this IApplicationBuilder app, PathString path, HealthCheckOptions options)
+ {
+ if (app == null)
+ {
+ throw new ArgumentNullException(nameof(app));
+ }
+
+ if (!path.HasValue)
+ {
+ throw new ArgumentException("A URL path must be provided", nameof(path));
+ }
+
+ if (options == null)
+ {
+ throw new ArgumentNullException(nameof(options));
+ }
+
+ return app.Map(path, b => b.UseMiddleware(Options.Create(options)));
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Diagnostics.HealthChecks/HealthCheckAppBuilderExtensions.cs b/src/Microsoft.AspNetCore.Diagnostics.HealthChecks/HealthCheckAppBuilderExtensions.cs
deleted file mode 100644
index 916ca46df4..0000000000
--- a/src/Microsoft.AspNetCore.Diagnostics.HealthChecks/HealthCheckAppBuilderExtensions.cs
+++ /dev/null
@@ -1,32 +0,0 @@
-// 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 Microsoft.AspNetCore.Diagnostics.HealthChecks;
-using Microsoft.AspNetCore.Http;
-using Microsoft.Extensions.Options;
-
-namespace Microsoft.AspNetCore.Builder
-{
- ///
- /// extension methods for the .
- ///
- public static class HealthCheckAppBuilderExtensions
- {
- ///
- /// Adds a middleware that provides a REST API for requesting health check status.
- ///
- /// The .
- /// The path on which to provide the API.
- /// A reference to the after the operation has completed.
- public static IApplicationBuilder UseHealthChecks(this IApplicationBuilder app, PathString path)
- {
- app = app ?? throw new ArgumentNullException(nameof(app));
-
- return app.UseMiddleware(Options.Create(new HealthCheckOptions()
- {
- Path = path
- }));
- }
- }
-}
diff --git a/src/Microsoft.AspNetCore.Diagnostics.HealthChecks/HealthCheckMiddleware.cs b/src/Microsoft.AspNetCore.Diagnostics.HealthChecks/HealthCheckMiddleware.cs
index 18c10da287..3a220f2595 100644
--- a/src/Microsoft.AspNetCore.Diagnostics.HealthChecks/HealthCheckMiddleware.cs
+++ b/src/Microsoft.AspNetCore.Diagnostics.HealthChecks/HealthCheckMiddleware.cs
@@ -2,14 +2,12 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
-using System.Diagnostics;
+using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Microsoft.Extensions.Options;
-using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
namespace Microsoft.AspNetCore.Diagnostics.HealthChecks
{
@@ -18,62 +16,103 @@ namespace Microsoft.AspNetCore.Diagnostics.HealthChecks
private readonly RequestDelegate _next;
private readonly HealthCheckOptions _healthCheckOptions;
private readonly IHealthCheckService _healthCheckService;
+ private readonly IHealthCheck[] _checks;
- public HealthCheckMiddleware(RequestDelegate next, IOptions healthCheckOptions, IHealthCheckService healthCheckService)
+ public HealthCheckMiddleware(
+ RequestDelegate next,
+ IOptions healthCheckOptions,
+ IHealthCheckService healthCheckService)
{
+ if (next == null)
+ {
+ throw new ArgumentNullException(nameof(next));
+ }
+
+ if (healthCheckOptions == null)
+ {
+ throw new ArgumentNullException(nameof(healthCheckOptions));
+ }
+
+ if (healthCheckService == null)
+ {
+ throw new ArgumentNullException(nameof(healthCheckService));
+ }
+
_next = next;
_healthCheckOptions = healthCheckOptions.Value;
_healthCheckService = healthCheckService;
+
+ _checks = FilterHealthChecks(_healthCheckService.Checks, healthCheckOptions.Value.HealthCheckNames);
}
///
- /// Process an individual request.
+ /// Processes a request.
///
- ///
+ ///
///
- public async Task InvokeAsync(HttpContext context)
+ public async Task InvokeAsync(HttpContext httpContext)
{
- if (context.Request.Path == _healthCheckOptions.Path)
+ if (httpContext == null)
{
- // Get results
- var result = await _healthCheckService.CheckHealthAsync(context.RequestAborted);
+ throw new ArgumentNullException(nameof(httpContext));
+ }
- // Map status to response code
- switch (result.Status)
+ // Get results
+ var result = await _healthCheckService.CheckHealthAsync(_checks, httpContext.RequestAborted);
+
+ // Map status to response code - this is customizable via options.
+ if (!_healthCheckOptions.ResultStatusCodes.TryGetValue(result.Status, out var statusCode))
+ {
+ var message =
+ $"No status code mapping found for {nameof(HealthCheckStatus)} value: {result.Status}." +
+ $"{nameof(HealthCheckOptions)}.{nameof(HealthCheckOptions.ResultStatusCodes)} must contain" +
+ $"an entry for {result.Status}.";
+
+ throw new InvalidOperationException(message);
+ }
+
+ httpContext.Response.StatusCode = statusCode;
+
+ if (_healthCheckOptions.ResponseWriter != null)
+ {
+ await _healthCheckOptions.ResponseWriter(httpContext, result);
+ }
+ }
+
+ private static IHealthCheck[] FilterHealthChecks(
+ IReadOnlyDictionary checks,
+ ISet names)
+ {
+ // If there are no filters then include all checks.
+ if (names.Count == 0)
+ {
+ return checks.Values.ToArray();
+ }
+
+ // Keep track of what we don't find so we can report errors.
+ var notFound = new HashSet(names, StringComparer.OrdinalIgnoreCase);
+ var matches = new List();
+
+ foreach (var kvp in checks)
+ {
+ if (!notFound.Remove(kvp.Key))
{
- case HealthCheckStatus.Failed:
- context.Response.StatusCode = StatusCodes.Status500InternalServerError;
- break;
- case HealthCheckStatus.Unhealthy:
- context.Response.StatusCode = StatusCodes.Status503ServiceUnavailable;
- break;
- case HealthCheckStatus.Degraded:
- // Degraded doesn't mean unhealthy so we return 200, but the content will contain more details
- context.Response.StatusCode = StatusCodes.Status200OK;
- break;
- case HealthCheckStatus.Healthy:
- context.Response.StatusCode = StatusCodes.Status200OK;
- break;
- default:
- // This will only happen when we change HealthCheckStatus and we don't update this.
- Debug.Fail($"Unrecognized HealthCheckStatus value: {result.Status}");
- throw new InvalidOperationException($"Unrecognized HealthCheckStatus value: {result.Status}");
+ // This check was excluded
+ continue;
}
- // Render results to JSON
- var json = new JObject(
- new JProperty("status", result.Status.ToString()),
- new JProperty("results", new JObject(result.Results.Select(pair =>
- new JProperty(pair.Key, new JObject(
- new JProperty("status", pair.Value.Status.ToString()),
- new JProperty("description", pair.Value.Description),
- new JProperty("data", new JObject(pair.Value.Data.Select(p => new JProperty(p.Key, p.Value))))))))));
- await context.Response.WriteAsync(json.ToString(Formatting.None));
+ matches.Add(kvp.Value);
}
- else
+
+ if (notFound.Count > 0)
{
- await _next(context);
+ var message =
+ $"The following health checks were not found: '{string.Join(", ", notFound)}'. " +
+ $"Registered health checks: '{string.Join(", ", checks.Keys)}'.";
+ throw new InvalidOperationException(message);
}
+
+ return matches.ToArray();
}
}
}
diff --git a/src/Microsoft.AspNetCore.Diagnostics.HealthChecks/HealthCheckOptions.cs b/src/Microsoft.AspNetCore.Diagnostics.HealthChecks/HealthCheckOptions.cs
index 8416e6843d..b57374f2ae 100644
--- a/src/Microsoft.AspNetCore.Diagnostics.HealthChecks/HealthCheckOptions.cs
+++ b/src/Microsoft.AspNetCore.Diagnostics.HealthChecks/HealthCheckOptions.cs
@@ -1,7 +1,11 @@
// 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.Collections.Generic;
+using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Diagnostics.HealthChecks;
namespace Microsoft.AspNetCore.Diagnostics.HealthChecks
{
@@ -11,8 +15,32 @@ namespace Microsoft.AspNetCore.Diagnostics.HealthChecks
public class HealthCheckOptions
{
///
- /// Gets or sets the path at which the Health Check results will be available.
+ /// Gets a set of health check names used to filter the set of health checks run.
///
- public PathString Path { get; set; }
+ ///
+ /// If is empty, the will run all
+ /// registered health checks - this is the default behavior. To run a subset of health checks,
+ /// add the names of the desired health checks.
+ ///
+ public ISet HealthCheckNames { get; } = new HashSet(StringComparer.OrdinalIgnoreCase);
+
+ public IDictionary ResultStatusCodes { get; } = new Dictionary()
+ {
+ { HealthCheckStatus.Healthy, StatusCodes.Status200OK },
+ { HealthCheckStatus.Degraded, StatusCodes.Status200OK },
+ { HealthCheckStatus.Unhealthy, StatusCodes.Status503ServiceUnavailable },
+
+ // This means that a health check failed, so 500 is appropriate. This is an error.
+ { HealthCheckStatus.Failed, StatusCodes.Status500InternalServerError },
+ };
+
+ ///
+ /// Gets or sets a delegate used to write the response.
+ ///
+ ///
+ /// The default value is a delegate that will write a minimal text/plain response with the value
+ /// of as a string.
+ ///
+ public Func ResponseWriter { get; set; } = HealthCheckResponseWriters.WriteMinimalPlaintext;
}
}
diff --git a/src/Microsoft.AspNetCore.Diagnostics.HealthChecks/HealthCheckResponseWriters.cs b/src/Microsoft.AspNetCore.Diagnostics.HealthChecks/HealthCheckResponseWriters.cs
new file mode 100644
index 0000000000..95ddf00ac8
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Diagnostics.HealthChecks/HealthCheckResponseWriters.cs
@@ -0,0 +1,56 @@
+// 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.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Diagnostics.HealthChecks;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace Microsoft.AspNetCore.Diagnostics.HealthChecks
+{
+ public static class HealthCheckResponseWriters
+ {
+ public static Task WriteMinimalPlaintext(HttpContext httpContext, CompositeHealthCheckResult result)
+ {
+ if (httpContext == null)
+ {
+ throw new ArgumentNullException(nameof(httpContext));
+ }
+
+ if (result == null)
+ {
+ throw new ArgumentNullException(nameof(result));
+ }
+
+ httpContext.Response.ContentType = "text/plain";
+ return httpContext.Response.WriteAsync(result.Status.ToString());
+ }
+
+ public static Task WriteDetailedJson(HttpContext httpContext, CompositeHealthCheckResult result)
+ {
+ if (httpContext == null)
+ {
+ throw new ArgumentNullException(nameof(httpContext));
+ }
+
+ if (result == null)
+ {
+ throw new ArgumentNullException(nameof(result));
+ }
+
+ httpContext.Response.ContentType = "application/json";
+
+ var json = new JObject(
+ new JProperty("status", result.Status.ToString()),
+ new JProperty("results", new JObject(result.Results.Select(pair =>
+ new JProperty(pair.Key, new JObject(
+ new JProperty("status", pair.Value.Status.ToString()),
+ new JProperty("description", pair.Value.Description),
+ new JProperty("data", new JObject(pair.Value.Data.Select(p => new JProperty(p.Key, p.Value))))))))));
+ return httpContext.Response.WriteAsync(json.ToString(Formatting.Indented));
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Diagnostics.HealthChecks/baseline.netcore.json b/src/Microsoft.AspNetCore.Diagnostics.HealthChecks/baseline.netcore.json
index c0e8deddd1..d089d2470d 100644
--- a/src/Microsoft.AspNetCore.Diagnostics.HealthChecks/baseline.netcore.json
+++ b/src/Microsoft.AspNetCore.Diagnostics.HealthChecks/baseline.netcore.json
@@ -1,115 +1,5 @@
{
"AssemblyIdentity": "Microsoft.AspNetCore.Diagnostics.HealthChecks, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
"Types": [
- {
- "Name": "Microsoft.AspNetCore.Diagnostics.HealthChecks.HealthCheckMiddleware",
- "Visibility": "Public",
- "Kind": "Class",
- "ImplementedInterfaces": [],
- "Members": [
- {
- "Kind": "Method",
- "Name": "InvokeAsync",
- "Parameters": [
- {
- "Name": "context",
- "Type": "Microsoft.AspNetCore.Http.HttpContext"
- }
- ],
- "ReturnType": "System.Threading.Tasks.Task",
- "Visibility": "Public",
- "GenericParameter": []
- },
- {
- "Kind": "Constructor",
- "Name": ".ctor",
- "Parameters": [
- {
- "Name": "next",
- "Type": "Microsoft.AspNetCore.Http.RequestDelegate"
- },
- {
- "Name": "healthCheckOptions",
- "Type": "Microsoft.Extensions.Options.IOptions"
- },
- {
- "Name": "healthCheckService",
- "Type": "Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheckService"
- }
- ],
- "Visibility": "Public",
- "GenericParameter": []
- }
- ],
- "GenericParameters": []
- },
- {
- "Name": "Microsoft.AspNetCore.Diagnostics.HealthChecks.HealthCheckOptions",
- "Visibility": "Public",
- "Kind": "Class",
- "ImplementedInterfaces": [],
- "Members": [
- {
- "Kind": "Method",
- "Name": "get_Path",
- "Parameters": [],
- "ReturnType": "Microsoft.AspNetCore.Http.PathString",
- "Visibility": "Public",
- "GenericParameter": []
- },
- {
- "Kind": "Method",
- "Name": "set_Path",
- "Parameters": [
- {
- "Name": "value",
- "Type": "Microsoft.AspNetCore.Http.PathString"
- }
- ],
- "ReturnType": "System.Void",
- "Visibility": "Public",
- "GenericParameter": []
- },
- {
- "Kind": "Constructor",
- "Name": ".ctor",
- "Parameters": [],
- "Visibility": "Public",
- "GenericParameter": []
- }
- ],
- "GenericParameters": []
- },
- {
- "Name": "Microsoft.AspNetCore.Builder.HealthCheckAppBuilderExtensions",
- "Visibility": "Public",
- "Kind": "Class",
- "Abstract": true,
- "Static": true,
- "Sealed": true,
- "ImplementedInterfaces": [],
- "Members": [
- {
- "Kind": "Method",
- "Name": "UseHealthChecks",
- "Parameters": [
- {
- "Name": "app",
- "Type": "Microsoft.AspNetCore.Builder.IApplicationBuilder"
- },
- {
- "Name": "path",
- "Type": "Microsoft.AspNetCore.Http.PathString"
- }
- ],
- "ReturnType": "Microsoft.AspNetCore.Builder.IApplicationBuilder",
- "Static": true,
- "Extension": true,
- "Visibility": "Public",
- "GenericParameter": []
- }
- ],
- "GenericParameters": []
- }
]
}
\ No newline at end of file
diff --git a/src/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions/baseline.netcore.json b/src/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions/baseline.netcore.json
index 7792748d55..871db4c089 100644
--- a/src/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions/baseline.netcore.json
+++ b/src/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions/baseline.netcore.json
@@ -1,377 +1,5 @@
{
"AssemblyIdentity": "Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
"Types": [
- {
- "Name": "Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult",
- "Visibility": "Public",
- "Kind": "Struct",
- "Sealed": true,
- "ImplementedInterfaces": [],
- "Members": [
- {
- "Kind": "Method",
- "Name": "get_Status",
- "Parameters": [],
- "ReturnType": "Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckStatus",
- "Visibility": "Public",
- "GenericParameter": []
- },
- {
- "Kind": "Method",
- "Name": "get_Exception",
- "Parameters": [],
- "ReturnType": "System.Exception",
- "Visibility": "Public",
- "GenericParameter": []
- },
- {
- "Kind": "Method",
- "Name": "get_Description",
- "Parameters": [],
- "ReturnType": "System.String",
- "Visibility": "Public",
- "GenericParameter": []
- },
- {
- "Kind": "Method",
- "Name": "get_Data",
- "Parameters": [],
- "ReturnType": "System.Collections.Generic.IReadOnlyDictionary",
- "Visibility": "Public",
- "GenericParameter": []
- },
- {
- "Kind": "Method",
- "Name": "Unhealthy",
- "Parameters": [],
- "ReturnType": "Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult",
- "Static": true,
- "Visibility": "Public",
- "GenericParameter": []
- },
- {
- "Kind": "Method",
- "Name": "Unhealthy",
- "Parameters": [
- {
- "Name": "description",
- "Type": "System.String"
- }
- ],
- "ReturnType": "Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult",
- "Static": true,
- "Visibility": "Public",
- "GenericParameter": []
- },
- {
- "Kind": "Method",
- "Name": "Unhealthy",
- "Parameters": [
- {
- "Name": "description",
- "Type": "System.String"
- },
- {
- "Name": "data",
- "Type": "System.Collections.Generic.IReadOnlyDictionary"
- }
- ],
- "ReturnType": "Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult",
- "Static": true,
- "Visibility": "Public",
- "GenericParameter": []
- },
- {
- "Kind": "Method",
- "Name": "Unhealthy",
- "Parameters": [
- {
- "Name": "exception",
- "Type": "System.Exception"
- }
- ],
- "ReturnType": "Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult",
- "Static": true,
- "Visibility": "Public",
- "GenericParameter": []
- },
- {
- "Kind": "Method",
- "Name": "Unhealthy",
- "Parameters": [
- {
- "Name": "description",
- "Type": "System.String"
- },
- {
- "Name": "exception",
- "Type": "System.Exception"
- }
- ],
- "ReturnType": "Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult",
- "Static": true,
- "Visibility": "Public",
- "GenericParameter": []
- },
- {
- "Kind": "Method",
- "Name": "Unhealthy",
- "Parameters": [
- {
- "Name": "description",
- "Type": "System.String"
- },
- {
- "Name": "exception",
- "Type": "System.Exception"
- },
- {
- "Name": "data",
- "Type": "System.Collections.Generic.IReadOnlyDictionary"
- }
- ],
- "ReturnType": "Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult",
- "Static": true,
- "Visibility": "Public",
- "GenericParameter": []
- },
- {
- "Kind": "Method",
- "Name": "Healthy",
- "Parameters": [],
- "ReturnType": "Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult",
- "Static": true,
- "Visibility": "Public",
- "GenericParameter": []
- },
- {
- "Kind": "Method",
- "Name": "Healthy",
- "Parameters": [
- {
- "Name": "description",
- "Type": "System.String"
- }
- ],
- "ReturnType": "Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult",
- "Static": true,
- "Visibility": "Public",
- "GenericParameter": []
- },
- {
- "Kind": "Method",
- "Name": "Healthy",
- "Parameters": [
- {
- "Name": "description",
- "Type": "System.String"
- },
- {
- "Name": "data",
- "Type": "System.Collections.Generic.IReadOnlyDictionary"
- }
- ],
- "ReturnType": "Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult",
- "Static": true,
- "Visibility": "Public",
- "GenericParameter": []
- },
- {
- "Kind": "Method",
- "Name": "Degraded",
- "Parameters": [],
- "ReturnType": "Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult",
- "Static": true,
- "Visibility": "Public",
- "GenericParameter": []
- },
- {
- "Kind": "Method",
- "Name": "Degraded",
- "Parameters": [
- {
- "Name": "description",
- "Type": "System.String"
- }
- ],
- "ReturnType": "Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult",
- "Static": true,
- "Visibility": "Public",
- "GenericParameter": []
- },
- {
- "Kind": "Method",
- "Name": "Degraded",
- "Parameters": [
- {
- "Name": "description",
- "Type": "System.String"
- },
- {
- "Name": "data",
- "Type": "System.Collections.Generic.IReadOnlyDictionary"
- }
- ],
- "ReturnType": "Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult",
- "Static": true,
- "Visibility": "Public",
- "GenericParameter": []
- },
- {
- "Kind": "Method",
- "Name": "Degraded",
- "Parameters": [
- {
- "Name": "exception",
- "Type": "System.Exception"
- }
- ],
- "ReturnType": "Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult",
- "Static": true,
- "Visibility": "Public",
- "GenericParameter": []
- },
- {
- "Kind": "Method",
- "Name": "Degraded",
- "Parameters": [
- {
- "Name": "description",
- "Type": "System.String"
- },
- {
- "Name": "exception",
- "Type": "System.Exception"
- }
- ],
- "ReturnType": "Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult",
- "Static": true,
- "Visibility": "Public",
- "GenericParameter": []
- },
- {
- "Kind": "Method",
- "Name": "Degraded",
- "Parameters": [
- {
- "Name": "description",
- "Type": "System.String"
- },
- {
- "Name": "exception",
- "Type": "System.Exception"
- },
- {
- "Name": "data",
- "Type": "System.Collections.Generic.IReadOnlyDictionary"
- }
- ],
- "ReturnType": "Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult",
- "Static": true,
- "Visibility": "Public",
- "GenericParameter": []
- },
- {
- "Kind": "Constructor",
- "Name": ".ctor",
- "Parameters": [
- {
- "Name": "status",
- "Type": "Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckStatus"
- },
- {
- "Name": "exception",
- "Type": "System.Exception"
- },
- {
- "Name": "description",
- "Type": "System.String"
- },
- {
- "Name": "data",
- "Type": "System.Collections.Generic.IReadOnlyDictionary"
- }
- ],
- "Visibility": "Public",
- "GenericParameter": []
- }
- ],
- "GenericParameters": []
- },
- {
- "Name": "Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckStatus",
- "Visibility": "Public",
- "Kind": "Enumeration",
- "Sealed": true,
- "ImplementedInterfaces": [],
- "Members": [
- {
- "Kind": "Field",
- "Name": "Unknown",
- "Parameters": [],
- "GenericParameter": [],
- "Literal": "0"
- },
- {
- "Kind": "Field",
- "Name": "Failed",
- "Parameters": [],
- "GenericParameter": [],
- "Literal": "1"
- },
- {
- "Kind": "Field",
- "Name": "Unhealthy",
- "Parameters": [],
- "GenericParameter": [],
- "Literal": "2"
- },
- {
- "Kind": "Field",
- "Name": "Degraded",
- "Parameters": [],
- "GenericParameter": [],
- "Literal": "3"
- },
- {
- "Kind": "Field",
- "Name": "Healthy",
- "Parameters": [],
- "GenericParameter": [],
- "Literal": "4"
- }
- ],
- "GenericParameters": []
- },
- {
- "Name": "Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck",
- "Visibility": "Public",
- "Kind": "Interface",
- "Abstract": true,
- "ImplementedInterfaces": [],
- "Members": [
- {
- "Kind": "Method",
- "Name": "get_Name",
- "Parameters": [],
- "ReturnType": "System.String",
- "GenericParameter": []
- },
- {
- "Kind": "Method",
- "Name": "CheckHealthAsync",
- "Parameters": [
- {
- "Name": "cancellationToken",
- "Type": "System.Threading.CancellationToken",
- "DefaultValue": "default(System.Threading.CancellationToken)"
- }
- ],
- "ReturnType": "System.Threading.Tasks.Task",
- "GenericParameter": []
- }
- ],
- "GenericParameters": []
- }
]
}
\ No newline at end of file
diff --git a/src/Microsoft.Extensions.Diagnostics.HealthChecks/HealthCheck.cs b/src/Microsoft.Extensions.Diagnostics.HealthChecks/HealthCheck.cs
index e72910df33..da6a92d062 100644
--- a/src/Microsoft.Extensions.Diagnostics.HealthChecks/HealthCheck.cs
+++ b/src/Microsoft.Extensions.Diagnostics.HealthChecks/HealthCheck.cs
@@ -11,15 +11,10 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks
/// A simple implementation of which uses a provided delegate to
/// implement the check.
///
- public class HealthCheck : IHealthCheck
+ public sealed class HealthCheck : IHealthCheck
{
private readonly Func> _check;
- ///
- /// Gets the name of the health check, which should indicate the component being checked.
- ///
- public string Name { get; }
-
///
/// Create an instance of from the specified and .
///
@@ -27,10 +22,15 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks
/// A delegate which provides the code to execute when the health check is run.
public HealthCheck(string name, Func> check)
{
- Name = name;
- _check = check;
+ Name = name ?? throw new ArgumentNullException(nameof(name));
+ _check = check ?? throw new ArgumentNullException(nameof(check));
}
+ ///
+ /// Gets the name of the health check, which should indicate the component being checked.
+ ///
+ public string Name { get; }
+
///
/// Runs the health check, returning the status of the component being checked.
///
diff --git a/src/Microsoft.Extensions.Diagnostics.HealthChecks/HealthChecksBuilderAddCheckExtensions.cs b/src/Microsoft.Extensions.Diagnostics.HealthChecks/HealthChecksBuilderAddCheckExtensions.cs
index 3b3ee1eb54..b2f931fcd3 100644
--- a/src/Microsoft.Extensions.Diagnostics.HealthChecks/HealthChecksBuilderAddCheckExtensions.cs
+++ b/src/Microsoft.Extensions.Diagnostics.HealthChecks/HealthChecksBuilderAddCheckExtensions.cs
@@ -22,8 +22,22 @@ namespace Microsoft.Extensions.DependencyInjection
/// The .
public static IHealthChecksBuilder AddCheck(this IHealthChecksBuilder builder, string name, Func> check)
{
- builder.Services.AddSingleton(services => new HealthCheck(name, check));
- return builder;
+ if (builder == null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+
+ if (name == null)
+ {
+ throw new ArgumentNullException(nameof(name));
+ }
+
+ if (check == null)
+ {
+ throw new ArgumentNullException(nameof(check));
+ }
+
+ return builder.AddCheck(new HealthCheck(name, check));
}
///
@@ -33,7 +47,46 @@ namespace Microsoft.Extensions.DependencyInjection
/// The name of the health check, which should indicate the component being checked.
/// A delegate which provides the code to execute when the health check is run.
/// The .
- public static IHealthChecksBuilder AddCheck(this IHealthChecksBuilder builder, string name, Func> check) =>
- builder.AddCheck(name, _ => check());
+ public static IHealthChecksBuilder AddCheck(this IHealthChecksBuilder builder, string name, Func> check)
+ {
+ if (builder == null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+
+ if (name == null)
+ {
+ throw new ArgumentNullException(nameof(name));
+ }
+
+ if (check == null)
+ {
+ throw new ArgumentNullException(nameof(check));
+ }
+
+ return builder.AddCheck(name, _ => check());
+ }
+
+ ///
+ /// Adds a new health check with the implementation.
+ ///
+ /// The to add the check to.
+ /// An implementation.
+ /// The .
+ public static IHealthChecksBuilder AddCheck(this IHealthChecksBuilder builder, IHealthCheck check)
+ {
+ if (builder == null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+
+ if (check == null)
+ {
+ throw new ArgumentNullException(nameof(check));
+ }
+
+ builder.Services.AddSingleton(check);
+ return builder;
+ }
}
}
diff --git a/src/Microsoft.Extensions.Diagnostics.HealthChecks/baseline.netcore.json b/src/Microsoft.Extensions.Diagnostics.HealthChecks/baseline.netcore.json
index 4938a12605..cb2fe053f1 100644
--- a/src/Microsoft.Extensions.Diagnostics.HealthChecks/baseline.netcore.json
+++ b/src/Microsoft.Extensions.Diagnostics.HealthChecks/baseline.netcore.json
@@ -1,334 +1,5 @@
{
"AssemblyIdentity": "Microsoft.Extensions.Diagnostics.HealthChecks, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
"Types": [
- {
- "Name": "Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions",
- "Visibility": "Public",
- "Kind": "Class",
- "Abstract": true,
- "Static": true,
- "Sealed": true,
- "ImplementedInterfaces": [],
- "Members": [
- {
- "Kind": "Method",
- "Name": "AddCheck",
- "Parameters": [
- {
- "Name": "builder",
- "Type": "Microsoft.Extensions.Diagnostics.HealthChecks.IHealthChecksBuilder"
- },
- {
- "Name": "name",
- "Type": "System.String"
- },
- {
- "Name": "check",
- "Type": "System.Func>"
- }
- ],
- "ReturnType": "Microsoft.Extensions.Diagnostics.HealthChecks.IHealthChecksBuilder",
- "Static": true,
- "Extension": true,
- "Visibility": "Public",
- "GenericParameter": []
- },
- {
- "Kind": "Method",
- "Name": "AddCheck",
- "Parameters": [
- {
- "Name": "builder",
- "Type": "Microsoft.Extensions.Diagnostics.HealthChecks.IHealthChecksBuilder"
- },
- {
- "Name": "name",
- "Type": "System.String"
- },
- {
- "Name": "check",
- "Type": "System.Func>"
- }
- ],
- "ReturnType": "Microsoft.Extensions.Diagnostics.HealthChecks.IHealthChecksBuilder",
- "Static": true,
- "Extension": true,
- "Visibility": "Public",
- "GenericParameter": []
- }
- ],
- "GenericParameters": []
- },
- {
- "Name": "Microsoft.Extensions.DependencyInjection.HealthCheckServiceCollectionExtensions",
- "Visibility": "Public",
- "Kind": "Class",
- "Abstract": true,
- "Static": true,
- "Sealed": true,
- "ImplementedInterfaces": [],
- "Members": [
- {
- "Kind": "Method",
- "Name": "AddHealthChecks",
- "Parameters": [
- {
- "Name": "services",
- "Type": "Microsoft.Extensions.DependencyInjection.IServiceCollection"
- }
- ],
- "ReturnType": "Microsoft.Extensions.Diagnostics.HealthChecks.IHealthChecksBuilder",
- "Static": true,
- "Extension": true,
- "Visibility": "Public",
- "GenericParameter": []
- }
- ],
- "GenericParameters": []
- },
- {
- "Name": "Microsoft.Extensions.Diagnostics.HealthChecks.CompositeHealthCheckResult",
- "Visibility": "Public",
- "Kind": "Class",
- "ImplementedInterfaces": [],
- "Members": [
- {
- "Kind": "Method",
- "Name": "get_Results",
- "Parameters": [],
- "ReturnType": "System.Collections.Generic.IReadOnlyDictionary",
- "Visibility": "Public",
- "GenericParameter": []
- },
- {
- "Kind": "Method",
- "Name": "get_Status",
- "Parameters": [],
- "ReturnType": "Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckStatus",
- "Visibility": "Public",
- "GenericParameter": []
- },
- {
- "Kind": "Constructor",
- "Name": ".ctor",
- "Parameters": [
- {
- "Name": "results",
- "Type": "System.Collections.Generic.IReadOnlyDictionary"
- }
- ],
- "Visibility": "Public",
- "GenericParameter": []
- }
- ],
- "GenericParameters": []
- },
- {
- "Name": "Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheck",
- "Visibility": "Public",
- "Kind": "Class",
- "ImplementedInterfaces": [
- "Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck"
- ],
- "Members": [
- {
- "Kind": "Method",
- "Name": "get_Name",
- "Parameters": [],
- "ReturnType": "System.String",
- "Sealed": true,
- "Virtual": true,
- "ImplementedInterface": "Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck",
- "Visibility": "Public",
- "GenericParameter": []
- },
- {
- "Kind": "Method",
- "Name": "CheckHealthAsync",
- "Parameters": [
- {
- "Name": "cancellationToken",
- "Type": "System.Threading.CancellationToken",
- "DefaultValue": "default(System.Threading.CancellationToken)"
- }
- ],
- "ReturnType": "System.Threading.Tasks.Task",
- "Sealed": true,
- "Virtual": true,
- "ImplementedInterface": "Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck",
- "Visibility": "Public",
- "GenericParameter": []
- },
- {
- "Kind": "Constructor",
- "Name": ".ctor",
- "Parameters": [
- {
- "Name": "name",
- "Type": "System.String"
- },
- {
- "Name": "check",
- "Type": "System.Func>"
- }
- ],
- "Visibility": "Public",
- "GenericParameter": []
- }
- ],
- "GenericParameters": []
- },
- {
- "Name": "Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckService",
- "Visibility": "Public",
- "Kind": "Class",
- "ImplementedInterfaces": [
- "Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheckService"
- ],
- "Members": [
- {
- "Kind": "Method",
- "Name": "get_Checks",
- "Parameters": [],
- "ReturnType": "System.Collections.Generic.IReadOnlyDictionary",
- "Sealed": true,
- "Virtual": true,
- "ImplementedInterface": "Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheckService",
- "Visibility": "Public",
- "GenericParameter": []
- },
- {
- "Kind": "Method",
- "Name": "CheckHealthAsync",
- "Parameters": [
- {
- "Name": "cancellationToken",
- "Type": "System.Threading.CancellationToken",
- "DefaultValue": "default(System.Threading.CancellationToken)"
- }
- ],
- "ReturnType": "System.Threading.Tasks.Task",
- "Sealed": true,
- "Virtual": true,
- "ImplementedInterface": "Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheckService",
- "Visibility": "Public",
- "GenericParameter": []
- },
- {
- "Kind": "Method",
- "Name": "CheckHealthAsync",
- "Parameters": [
- {
- "Name": "checks",
- "Type": "System.Collections.Generic.IEnumerable"
- },
- {
- "Name": "cancellationToken",
- "Type": "System.Threading.CancellationToken",
- "DefaultValue": "default(System.Threading.CancellationToken)"
- }
- ],
- "ReturnType": "System.Threading.Tasks.Task",
- "Sealed": true,
- "Virtual": true,
- "ImplementedInterface": "Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheckService",
- "Visibility": "Public",
- "GenericParameter": []
- },
- {
- "Kind": "Constructor",
- "Name": ".ctor",
- "Parameters": [
- {
- "Name": "healthChecks",
- "Type": "System.Collections.Generic.IEnumerable"
- }
- ],
- "Visibility": "Public",
- "GenericParameter": []
- },
- {
- "Kind": "Constructor",
- "Name": ".ctor",
- "Parameters": [
- {
- "Name": "healthChecks",
- "Type": "System.Collections.Generic.IEnumerable"
- },
- {
- "Name": "logger",
- "Type": "Microsoft.Extensions.Logging.ILogger"
- }
- ],
- "Visibility": "Public",
- "GenericParameter": []
- }
- ],
- "GenericParameters": []
- },
- {
- "Name": "Microsoft.Extensions.Diagnostics.HealthChecks.IHealthChecksBuilder",
- "Visibility": "Public",
- "Kind": "Interface",
- "Abstract": true,
- "ImplementedInterfaces": [],
- "Members": [
- {
- "Kind": "Method",
- "Name": "get_Services",
- "Parameters": [],
- "ReturnType": "Microsoft.Extensions.DependencyInjection.IServiceCollection",
- "GenericParameter": []
- }
- ],
- "GenericParameters": []
- },
- {
- "Name": "Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheckService",
- "Visibility": "Public",
- "Kind": "Interface",
- "Abstract": true,
- "ImplementedInterfaces": [],
- "Members": [
- {
- "Kind": "Method",
- "Name": "get_Checks",
- "Parameters": [],
- "ReturnType": "System.Collections.Generic.IReadOnlyDictionary",
- "GenericParameter": []
- },
- {
- "Kind": "Method",
- "Name": "CheckHealthAsync",
- "Parameters": [
- {
- "Name": "cancellationToken",
- "Type": "System.Threading.CancellationToken",
- "DefaultValue": "default(System.Threading.CancellationToken)"
- }
- ],
- "ReturnType": "System.Threading.Tasks.Task",
- "GenericParameter": []
- },
- {
- "Kind": "Method",
- "Name": "CheckHealthAsync",
- "Parameters": [
- {
- "Name": "checks",
- "Type": "System.Collections.Generic.IEnumerable"
- },
- {
- "Name": "cancellationToken",
- "Type": "System.Threading.CancellationToken",
- "DefaultValue": "default(System.Threading.CancellationToken)"
- }
- ],
- "ReturnType": "System.Threading.Tasks.Task",
- "GenericParameter": []
- }
- ],
- "GenericParameters": []
- }
]
}
\ No newline at end of file
diff --git a/test/Microsoft.AspNetCore.Diagnostics.HealthChecks.Tests/HealthCheckMiddlewareSampleTest.cs b/test/Microsoft.AspNetCore.Diagnostics.HealthChecks.Tests/HealthCheckMiddlewareSampleTest.cs
new file mode 100644
index 0000000000..4e716ed3b9
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Diagnostics.HealthChecks.Tests/HealthCheckMiddlewareSampleTest.cs
@@ -0,0 +1,127 @@
+// 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.Net;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.TestHost;
+using Newtonsoft.Json;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Diagnostics.HealthChecks
+{
+ public class HealthCheckMiddlewareSampleTest
+ {
+ [Fact]
+ public async Task BasicStartup()
+ {
+ var builder = new WebHostBuilder()
+ .UseStartup();
+
+ var server = new TestServer(builder);
+ var client = server.CreateClient();
+
+ var response = await client.GetAsync("/health");
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal("text/plain", response.Content.Headers.ContentType.ToString());
+ Assert.Equal("Healthy", await response.Content.ReadAsStringAsync());
+ }
+
+ [Fact]
+ public async Task CustomWriterStartup()
+ {
+ var builder = new WebHostBuilder()
+ .UseStartup();
+
+ var server = new TestServer(builder);
+ var client = server.CreateClient();
+
+ var response = await client.GetAsync("/health");
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal("text/html", response.Content.Headers.ContentType.ToString());
+
+ // Ignoring the body since it contains a bunch of statistics
+ }
+
+ [Fact]
+ public async Task DetailedStatusStartup()
+ {
+ var builder = new WebHostBuilder()
+ .UseStartup();
+
+ var server = new TestServer(builder);
+ var client = server.CreateClient();
+
+ var response = await client.GetAsync("/health");
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal("application/json", response.Content.Headers.ContentType.ToString());
+
+ // Ignoring the body since it contains a bunch of statistics
+ }
+
+ [Fact]
+ public async Task LivenessProbeStartup_Liveness()
+ {
+ var expectedJson = JsonConvert.SerializeObject(new
+ {
+ status = "Healthy",
+ results = new
+ {
+ identity = new
+ {
+ status = "Healthy",
+ description = "",
+ data = new { }
+ },
+ },
+ }, Formatting.Indented);
+
+ var builder = new WebHostBuilder()
+ .UseStartup();
+
+ var server = new TestServer(builder);
+ var client = server.CreateClient();
+
+ var response = await client.GetAsync("/health/live");
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal("application/json", response.Content.Headers.ContentType.ToString());
+ Assert.Equal(expectedJson, await response.Content.ReadAsStringAsync());
+ }
+
+ [Fact]
+ public async Task LivenessProbeStartup_Readiness()
+ {
+ var expectedJson = JsonConvert.SerializeObject(new
+ {
+ status = "Unhealthy",
+ results = new
+ {
+ identity = new
+ {
+ status = "Healthy",
+ description = "",
+ data = new { }
+ },
+ slow_dependency = new
+ {
+ status = "Unhealthy",
+ description = "Dependency is still initializing",
+ data = new { }
+ },
+ },
+ }, Formatting.Indented);
+
+ var builder = new WebHostBuilder()
+ .UseStartup();
+
+ var server = new TestServer(builder);
+ var client = server.CreateClient();
+
+ var response = await client.GetAsync("/health/ready");
+ Assert.Equal(HttpStatusCode.ServiceUnavailable, response.StatusCode);
+ Assert.Equal("application/json", response.Content.Headers.ContentType.ToString());
+ Assert.Equal(expectedJson, await response.Content.ReadAsStringAsync());
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Diagnostics.HealthChecks.Tests/HealthCheckMiddlewareTests.cs b/test/Microsoft.AspNetCore.Diagnostics.HealthChecks.Tests/HealthCheckMiddlewareTests.cs
index c6aaee52ff..74d23c81bf 100644
--- a/test/Microsoft.AspNetCore.Diagnostics.HealthChecks.Tests/HealthCheckMiddlewareTests.cs
+++ b/test/Microsoft.AspNetCore.Diagnostics.HealthChecks.Tests/HealthCheckMiddlewareTests.cs
@@ -17,10 +17,8 @@ namespace Microsoft.AspNetCore.Diagnostics.HealthChecks
{
public class HealthCheckMiddlewareTests
{
- [Theory]
- [InlineData("/frob")]
- [InlineData("/health/")] // Match is exact, for now at least
- public async Task IgnoresRequestThatDoesNotMatchPath(string requestPath)
+ [Fact] // Matches based on '.Map'
+ public async Task IgnoresRequestThatDoesNotMatchPath()
{
var builder = new WebHostBuilder()
.Configure(app =>
@@ -34,26 +32,190 @@ namespace Microsoft.AspNetCore.Diagnostics.HealthChecks
var server = new TestServer(builder);
var client = server.CreateClient();
- var response = await client.GetAsync(requestPath);
+ var response = await client.GetAsync("/frob");
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
- [Theory]
- [InlineData("/health")]
- [InlineData("/Health")]
- [InlineData("/HEALTH")]
- public async Task ReturnsEmptyHealthyRequestIfNoHealthChecksRegistered(string requestPath)
+ [Fact] // Matches based on '.Map'
+ public async Task MatchIsCaseInsensitive()
+ {
+ var builder = new WebHostBuilder()
+ .Configure(app =>
+ {
+ app.UseHealthChecks("/health");
+ })
+ .ConfigureServices(services =>
+ {
+ services.AddHealthChecks();
+ });
+ var server = new TestServer(builder);
+ var client = server.CreateClient();
+
+ var response = await client.GetAsync("/HEALTH");
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ }
+
+ [Fact]
+ public async Task ReturnsPlainTextStatus()
+ {
+ var builder = new WebHostBuilder()
+ .Configure(app =>
+ {
+ app.UseHealthChecks("/health");
+ })
+ .ConfigureServices(services =>
+ {
+ services.AddHealthChecks();
+ });
+ var server = new TestServer(builder);
+ var client = server.CreateClient();
+
+ var response = await client.GetAsync("/health");
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+
+ Assert.Equal("text/plain", response.Content.Headers.ContentType.ToString());
+ Assert.Equal("Healthy", await response.Content.ReadAsStringAsync());
+ }
+
+ [Fact]
+ public async Task StatusCodeIs200IfNoChecks()
+ {
+ var builder = new WebHostBuilder()
+ .Configure(app =>
+ {
+ app.UseHealthChecks("/health");
+ })
+ .ConfigureServices(services =>
+ {
+ services.AddHealthChecks();
+ });
+ var server = new TestServer(builder);
+ var client = server.CreateClient();
+
+ var response = await client.GetAsync("/health");
+
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal("text/plain", response.Content.Headers.ContentType.ToString());
+ Assert.Equal("Healthy", await response.Content.ReadAsStringAsync());
+ }
+
+
+ [Fact]
+ public async Task StatusCodeIs200IfAllChecksHealthy()
+ {
+ var builder = new WebHostBuilder()
+ .Configure(app =>
+ {
+ app.UseHealthChecks("/health");
+ })
+ .ConfigureServices(services =>
+ {
+ services.AddHealthChecks()
+ .AddCheck("Foo", () => Task.FromResult(HealthCheckResult.Healthy("A-ok!")))
+ .AddCheck("Bar", () => Task.FromResult(HealthCheckResult.Healthy("A-ok!")))
+ .AddCheck("Baz", () => Task.FromResult(HealthCheckResult.Healthy("A-ok!")));
+ });
+ var server = new TestServer(builder);
+ var client = server.CreateClient();
+
+ var response = await client.GetAsync("/health");
+
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal("text/plain", response.Content.Headers.ContentType.ToString());
+ Assert.Equal("Healthy", await response.Content.ReadAsStringAsync());
+ }
+
+ [Fact]
+ public async Task StatusCodeIs200IfCheckIsDegraded()
+ {
+ var builder = new WebHostBuilder()
+ .Configure(app =>
+ {
+ app.UseHealthChecks("/health");
+ })
+ .ConfigureServices(services =>
+ {
+ services.AddHealthChecks()
+ .AddCheck("Foo", () => Task.FromResult(HealthCheckResult.Healthy("A-ok!")))
+ .AddCheck("Bar", () => Task.FromResult(HealthCheckResult.Degraded("Not so great.")))
+ .AddCheck("Baz", () => Task.FromResult(HealthCheckResult.Healthy("A-ok!")));
+ });
+ var server = new TestServer(builder);
+ var client = server.CreateClient();
+
+ var response = await client.GetAsync("/health");
+
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal("text/plain", response.Content.Headers.ContentType.ToString());
+ Assert.Equal("Degraded", await response.Content.ReadAsStringAsync());
+ }
+
+ [Fact]
+ public async Task StatusCodeIs503IfCheckIsUnhealthy()
+ {
+ var builder = new WebHostBuilder()
+ .Configure(app =>
+ {
+ app.UseHealthChecks("/health");
+ })
+ .ConfigureServices(services =>
+ {
+ services.AddHealthChecks()
+ .AddCheck("Foo", () => Task.FromResult(HealthCheckResult.Healthy("A-ok!")))
+ .AddCheck("Bar", () => Task.FromResult(HealthCheckResult.Unhealthy("Pretty bad.")))
+ .AddCheck("Baz", () => Task.FromResult(HealthCheckResult.Healthy("A-ok!")));
+ });
+ var server = new TestServer(builder);
+ var client = server.CreateClient();
+
+ var response = await client.GetAsync("/health");
+
+ Assert.Equal(HttpStatusCode.ServiceUnavailable, response.StatusCode);
+ Assert.Equal("text/plain", response.Content.Headers.ContentType.ToString());
+ Assert.Equal("Unhealthy", await response.Content.ReadAsStringAsync());
+ }
+
+ [Fact]
+ public async Task StatusCodeIs500IfCheckIsFailed()
+ {
+ var builder = new WebHostBuilder()
+ .Configure(app =>
+ {
+ app.UseHealthChecks("/health");
+ })
+ .ConfigureServices(services =>
+ {
+ services.AddHealthChecks()
+ .AddCheck("Foo", () => Task.FromResult(HealthCheckResult.Healthy("A-ok!")))
+ .AddCheck("Bar", () => Task.FromResult(new HealthCheckResult(HealthCheckStatus.Failed, null, null, null)))
+ .AddCheck("Baz", () => Task.FromResult(HealthCheckResult.Healthy("A-ok!")));
+ });
+ var server = new TestServer(builder);
+ var client = server.CreateClient();
+
+ var response = await client.GetAsync("/health");
+
+ Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode);
+ Assert.Equal("text/plain", response.Content.Headers.ContentType.ToString());
+ Assert.Equal("Failed", await response.Content.ReadAsStringAsync());
+ }
+
+ [Fact]
+ public async Task DetailedJsonReturnsEmptyHealthyResponseIfNoHealthChecksRegistered()
{
var expectedJson = JsonConvert.SerializeObject(new
{
status = "Healthy",
results = new { }
- }, Formatting.None);
+ }, Formatting.Indented);
var builder = new WebHostBuilder()
.Configure(app =>
{
- app.UseHealthChecks("/health");
+ app.UseHealthChecks("/health", new HealthCheckOptions()
+ {
+ ResponseWriter = HealthCheckResponseWriters.WriteDetailedJson,
+ });
})
.ConfigureServices(services =>
{
@@ -62,14 +224,14 @@ namespace Microsoft.AspNetCore.Diagnostics.HealthChecks
var server = new TestServer(builder);
var client = server.CreateClient();
- var response = await client.GetAsync(requestPath);
+ var response = await client.GetAsync("/health");
var result = await response.Content.ReadAsStringAsync();
Assert.Equal(expectedJson, result);
}
[Fact]
- public async Task ReturnsResultsFromHealthChecks()
+ public async Task DetailedJsonReturnsResultsFromHealthChecks()
{
var expectedJson = JsonConvert.SerializeObject(new
{
@@ -101,12 +263,15 @@ namespace Microsoft.AspNetCore.Diagnostics.HealthChecks
data = new { }
},
},
- }, Formatting.None);
+ }, Formatting.Indented);
var builder = new WebHostBuilder()
.Configure(app =>
{
- app.UseHealthChecks("/health");
+ app.UseHealthChecks("/health", new HealthCheckOptions()
+ {
+ ResponseWriter = HealthCheckResponseWriters.WriteDetailedJson,
+ });
})
.ConfigureServices(services =>
{
@@ -129,78 +294,15 @@ namespace Microsoft.AspNetCore.Diagnostics.HealthChecks
}
[Fact]
- public async Task StatusCodeIs200IfNoChecks()
+ public async Task NoResponseWriterReturnsEmptyBody()
{
var builder = new WebHostBuilder()
.Configure(app =>
{
- app.UseHealthChecks("/health");
- })
- .ConfigureServices(services =>
- {
- services.AddHealthChecks();
- });
- var server = new TestServer(builder);
- var client = server.CreateClient();
-
- var response = await client.GetAsync("/health");
-
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- }
-
- [Fact]
- public async Task StatusCodeIs200IfAllChecksHealthy()
- {
- var builder = new WebHostBuilder()
- .Configure(app =>
- {
- app.UseHealthChecks("/health");
- })
- .ConfigureServices(services =>
- {
- services.AddHealthChecks()
- .AddCheck("Foo", () => Task.FromResult(HealthCheckResult.Healthy("A-ok!")))
- .AddCheck("Bar", () => Task.FromResult(HealthCheckResult.Healthy("A-ok!")))
- .AddCheck("Baz", () => Task.FromResult(HealthCheckResult.Healthy("A-ok!")));
- });
- var server = new TestServer(builder);
- var client = server.CreateClient();
-
- var response = await client.GetAsync("/health");
-
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- }
-
- [Fact]
- public async Task StatusCodeIs200IfCheckIsDegraded()
- {
- var builder = new WebHostBuilder()
- .Configure(app =>
- {
- app.UseHealthChecks("/health");
- })
- .ConfigureServices(services =>
- {
- services.AddHealthChecks()
- .AddCheck("Foo", () => Task.FromResult(HealthCheckResult.Healthy("A-ok!")))
- .AddCheck("Bar", () => Task.FromResult(HealthCheckResult.Degraded("Not so great.")))
- .AddCheck("Baz", () => Task.FromResult(HealthCheckResult.Healthy("A-ok!")));
- });
- var server = new TestServer(builder);
- var client = server.CreateClient();
-
- var response = await client.GetAsync("/health");
-
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- }
-
- [Fact]
- public async Task StatusCodeIs503IfCheckIsUnhealthy()
- {
- var builder = new WebHostBuilder()
- .Configure(app =>
- {
- app.UseHealthChecks("/health");
+ app.UseHealthChecks("/health", new HealthCheckOptions()
+ {
+ ResponseWriter = null,
+ });
})
.ConfigureServices(services =>
{
@@ -215,21 +317,57 @@ namespace Microsoft.AspNetCore.Diagnostics.HealthChecks
var response = await client.GetAsync("/health");
Assert.Equal(HttpStatusCode.ServiceUnavailable, response.StatusCode);
+ Assert.Equal(string.Empty, await response.Content.ReadAsStringAsync());
}
[Fact]
- public async Task StatusCodeIs500IfCheckIsFailed()
+ public async Task CanSetCustomStatusCodes()
{
var builder = new WebHostBuilder()
.Configure(app =>
{
- app.UseHealthChecks("/health");
+ app.UseHealthChecks("/health", new HealthCheckOptions()
+ {
+ ResultStatusCodes =
+ {
+ [HealthCheckStatus.Healthy] = 201,
+ }
+ });
+ })
+ .ConfigureServices(services =>
+ {
+ services.AddHealthChecks();
+ });
+ var server = new TestServer(builder);
+ var client = server.CreateClient();
+
+ var response = await client.GetAsync("/health");
+
+ Assert.Equal(HttpStatusCode.Created, response.StatusCode);
+ Assert.Equal("Healthy", await response.Content.ReadAsStringAsync());
+ }
+
+ [Fact]
+ public async Task CanFilterChecks()
+ {
+ var builder = new WebHostBuilder()
+ .Configure(app =>
+ {
+ app.UseHealthChecks("/health", new HealthCheckOptions()
+ {
+ HealthCheckNames =
+ {
+ "Baz",
+ "FOO",
+ },
+ });
})
.ConfigureServices(services =>
{
services.AddHealthChecks()
.AddCheck("Foo", () => Task.FromResult(HealthCheckResult.Healthy("A-ok!")))
- .AddCheck("Bar", () => Task.FromResult(new HealthCheckResult(HealthCheckStatus.Failed, null, null, null)))
+ // Will get filtered out
+ .AddCheck("Bar", () => Task.FromResult(HealthCheckResult.Unhealthy("A-ok!")))
.AddCheck("Baz", () => Task.FromResult(HealthCheckResult.Healthy("A-ok!")));
});
var server = new TestServer(builder);
@@ -237,7 +375,38 @@ namespace Microsoft.AspNetCore.Diagnostics.HealthChecks
var response = await client.GetAsync("/health");
- Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode);
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal("text/plain", response.Content.Headers.ContentType.ToString());
+ Assert.Equal("Healthy", await response.Content.ReadAsStringAsync());
+ }
+
+ [Fact]
+ public void CanFilterChecks_ThrowsForMissingCheck()
+ {
+ var builder = new WebHostBuilder()
+ .Configure(app =>
+ {
+ app.UseHealthChecks("/health", new HealthCheckOptions()
+ {
+ HealthCheckNames =
+ {
+ "Bazzzzzz",
+ "FOO",
+ },
+ });
+ })
+ .ConfigureServices(services =>
+ {
+ services.AddHealthChecks()
+ .AddCheck("Foo", () => Task.FromResult(HealthCheckResult.Healthy("A-ok!")))
+ .AddCheck("Bar", () => Task.FromResult(HealthCheckResult.Unhealthy("A-ok!")))
+ .AddCheck("Baz", () => Task.FromResult(HealthCheckResult.Healthy("A-ok!")));
+ });
+
+ var ex = Assert.Throws(() => new TestServer(builder));
+ Assert.Equal(
+ "The following health checks were not found: 'Bazzzzzz'. Registered health checks: 'Foo, Bar, Baz'.",
+ ex.Message);
}
}
}
diff --git a/test/Microsoft.AspNetCore.Diagnostics.HealthChecks.Tests/Microsoft.AspNetCore.Diagnostics.HealthChecks.Tests.csproj b/test/Microsoft.AspNetCore.Diagnostics.HealthChecks.Tests/Microsoft.AspNetCore.Diagnostics.HealthChecks.Tests.csproj
index 001b0c0990..8ac9320ae8 100644
--- a/test/Microsoft.AspNetCore.Diagnostics.HealthChecks.Tests/Microsoft.AspNetCore.Diagnostics.HealthChecks.Tests.csproj
+++ b/test/Microsoft.AspNetCore.Diagnostics.HealthChecks.Tests/Microsoft.AspNetCore.Diagnostics.HealthChecks.Tests.csproj
@@ -14,6 +14,7 @@
+