From 5f76b35de05831e8ff504c160134636c762668d2 Mon Sep 17 00:00:00 2001 From: Luke Latham <1622880+guardrex@users.noreply.github.com> Date: Sun, 15 Dec 2019 13:26:22 -0600 Subject: [PATCH 1/5] HealthChecks sample updates --- aspnetcore/host-and-deploy/health-checks.md | 42 +++++-------- .../3.x/HealthChecksSample/BasicStartup.cs | 1 - .../HealthChecksSample/CustomWriterStartup.cs | 63 ++++++++++++++++--- .../3.x/HealthChecksSample/DBHealthStartup.cs | 1 - .../DbContextHealthStartup.cs | 1 - .../LivenessProbeStartup.cs | 2 - .../ManagementPortStartup.cs | 1 - .../3.x/HealthChecksSample/SampleApp.csproj | 11 ++-- 8 files changed, 76 insertions(+), 46 deletions(-) diff --git a/aspnetcore/host-and-deploy/health-checks.md b/aspnetcore/host-and-deploy/health-checks.md index 9964aed80f43..efe693269e48 100644 --- a/aspnetcore/host-and-deploy/health-checks.md +++ b/aspnetcore/host-and-deploy/health-checks.md @@ -5,7 +5,7 @@ description: Learn how to set up health checks for ASP.NET Core infrastructure, monikerRange: '>= aspnetcore-2.2' ms.author: riande ms.custom: mvc -ms.date: 11/13/2019 +ms.date: 12/15/2019 uid: host-and-deploy/health-checks --- # Health checks in ASP.NET Core @@ -294,9 +294,7 @@ app.UseEndpoints(endpoints => ### Customize output -The option gets or sets a delegate used to write the response. - -In `Startup.Configure`: +In `Startup.Configure`, set the [HealthCheckOptions.ResponseWriter](xref:Microsoft.AspNetCore.Diagnostics.HealthChecks.HealthCheckOptions.ResponseWriter) option to a delegate for writing the response: ```csharp app.UseEndpoints(endpoints => @@ -308,27 +306,19 @@ app.UseEndpoints(endpoints => }); ``` -The default delegate writes a minimal plaintext response with the string value of [HealthReport.Status](xref:Microsoft.Extensions.Diagnostics.HealthChecks.HealthReport.Status). The following custom delegate, `WriteResponse`, outputs a custom JSON response: +The default delegate writes a minimal plaintext response with the string value of [HealthReport.Status](xref:Microsoft.Extensions.Diagnostics.HealthChecks.HealthReport.Status). The following custom delegates output a custom JSON response. -```csharp -private static Task WriteResponse(HttpContext httpContext, HealthReport result) -{ - httpContext.Response.ContentType = "application/json"; +The first example from the sample app demonstrates how to use : - var json = new JObject( - new JProperty("status", result.Status.ToString()), - new JProperty("results", new JObject(result.Entries.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)); -} -``` +[!code-csharp[](health-checks/samples/3.x/HealthChecksSample/CustomWriterStartup.cs?name=snippet_WriteResponse_SystemTextJson)] -The health checks system doesn't provide built-in support for complex JSON return formats because the format is specific to your choice of monitoring system. Feel free to customize the `JObject` in the preceding example as necessary to meet your needs. +The second example demonstrates how to use [Newtonsoft.Json](https://www.nuget.org/packages/Newtonsoft.Json/): + +[!code-csharp[](health-checks/samples/3.x/HealthChecksSample/CustomWriterStartup.cs?name=snippet_WriteResponse_NewtonSoftJson)] + +In the sample app, comment out the `SYSTEM_TEXT_JSON` [preprocessor directive](xref:index#preprocessor-directives-in-sample-code) in *CustomWriterStartup.cs* to enable the `Newtonsoft.Json` version of `WriteResponse`. + +The health checks API doesn't provide built-in support for complex JSON return formats because the format is specific to your choice of monitoring system. Customize the response in the preceding examples as needed. For more information on JSON serialization with `System.Text.Json`, see [How to serialize and deserialize JSON in .NET](/dotnet/standard/serialization/system-text-json-how-to). ## Database probe @@ -525,11 +515,11 @@ The sample app demonstrates a memory health check with a custom response writer. Register health check services with in `Startup.ConfigureServices`. Instead of enabling the health check by passing it to , the `MemoryHealthCheck` is registered as a service. All registered services are available to the health check services and middleware. We recommend registering health check services as Singleton services. -In the sample app (*CustomWriterStartup.cs*): +In *CustomWriterStartup.cs* of the sample app: [!code-csharp[](health-checks/samples/3.x/HealthChecksSample/CustomWriterStartup.cs?name=snippet_ConfigureServices&highlight=4)] -A health check endpoint is created by calling `MapHealthChecks` in `Startup.Configure`. A `WriteResponse` delegate is provided to the `ResponseWriter` property to output a custom JSON response when the health check executes: +A health check endpoint is created by calling `MapHealthChecks` in `Startup.Configure`. A `WriteResponse` delegate is provided to the property to output a custom JSON response when the health check executes: ```csharp app.UseEndpoints(endpoints => @@ -541,9 +531,7 @@ app.UseEndpoints(endpoints => } ``` -The `WriteResponse` method formats the `CompositeHealthCheckResult` into a JSON object and yields JSON output for the health check response: - -[!code-csharp[](health-checks/samples/3.x/HealthChecksSample/CustomWriterStartup.cs?name=snippet_WriteResponse)] +A `WriteResponse` delegate formats the `CompositeHealthCheckResult` into a JSON object and yields JSON output for the health check response. For more information, see the [Customize output](#customize-output) section. To run the metric-based probe with custom response writer output using the sample app, execute the following command from the project's folder in a command shell: diff --git a/aspnetcore/host-and-deploy/health-checks/samples/3.x/HealthChecksSample/BasicStartup.cs b/aspnetcore/host-and-deploy/health-checks/samples/3.x/HealthChecksSample/BasicStartup.cs index 4881165caaab..43c35f6cbb86 100644 --- a/aspnetcore/host-and-deploy/health-checks/samples/3.x/HealthChecksSample/BasicStartup.cs +++ b/aspnetcore/host-and-deploy/health-checks/samples/3.x/HealthChecksSample/BasicStartup.cs @@ -1,5 +1,4 @@ using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; diff --git a/aspnetcore/host-and-deploy/health-checks/samples/3.x/HealthChecksSample/CustomWriterStartup.cs b/aspnetcore/host-and-deploy/health-checks/samples/3.x/HealthChecksSample/CustomWriterStartup.cs index 86dcc5963142..868b22099173 100644 --- a/aspnetcore/host-and-deploy/health-checks/samples/3.x/HealthChecksSample/CustomWriterStartup.cs +++ b/aspnetcore/host-and-deploy/health-checks/samples/3.x/HealthChecksSample/CustomWriterStartup.cs @@ -1,8 +1,13 @@ -using System.Linq; +#define SYSTEM_TEXT_JSON + +using System; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.Json; 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; @@ -49,11 +54,53 @@ await context.Response.WriteAsync( }); } - #region snippet_WriteResponse - private static Task WriteResponse(HttpContext httpContext, - HealthReport result) +#if SYSTEM_TEXT_JSON + #region snippet_WriteResponse_SystemTextJson + private static Task WriteResponse(HttpContext context, HealthReport result) + { + context.Response.ContentType = "application/json"; + + var options = new JsonWriterOptions + { + Indented = true + }; + + using (var stream = new MemoryStream()) + { + using (var writer = new Utf8JsonWriter(stream, options)) + { + writer.WriteStartObject(); + writer.WriteString("status", result.Status.ToString()); + writer.WriteStartObject("results"); + foreach (var entry in result.Entries) + { + writer.WriteStartObject(entry.Key); + writer.WriteString("status", entry.Value.Status.ToString()); + writer.WriteString("description", entry.Value.Description); + writer.WriteStartObject("data"); + foreach (var item in entry.Value.Data) + { + var value = Convert.ToDecimal(item.Value); + writer.WriteNumber(item.Key, value); + } + writer.WriteEndObject(); + writer.WriteEndObject(); + } + writer.WriteEndObject(); + writer.WriteEndObject(); + } + + var json = Encoding.UTF8.GetString(stream.ToArray()); + + return context.Response.WriteAsync(json); + } + } + #endregion +#else + #region snippet_WriteResponse_NewtonSoftJson + private static Task WriteResponse(HttpContext context, HealthReport result) { - httpContext.Response.ContentType = "application/json"; + context.Response.ContentType = "application/json"; var json = new JObject( new JProperty("status", result.Status.ToString()), @@ -63,9 +110,11 @@ private static Task WriteResponse(HttpContext httpContext, 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( + + return context.Response.WriteAsync( json.ToString(Formatting.Indented)); } #endregion +#endif } } diff --git a/aspnetcore/host-and-deploy/health-checks/samples/3.x/HealthChecksSample/DBHealthStartup.cs b/aspnetcore/host-and-deploy/health-checks/samples/3.x/HealthChecksSample/DBHealthStartup.cs index e0fec2cc0693..f2abdb686f21 100644 --- a/aspnetcore/host-and-deploy/health-checks/samples/3.x/HealthChecksSample/DBHealthStartup.cs +++ b/aspnetcore/host-and-deploy/health-checks/samples/3.x/HealthChecksSample/DBHealthStartup.cs @@ -1,5 +1,4 @@ using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; diff --git a/aspnetcore/host-and-deploy/health-checks/samples/3.x/HealthChecksSample/DbContextHealthStartup.cs b/aspnetcore/host-and-deploy/health-checks/samples/3.x/HealthChecksSample/DbContextHealthStartup.cs index c5880b5d6c15..edb23b9ec57e 100644 --- a/aspnetcore/host-and-deploy/health-checks/samples/3.x/HealthChecksSample/DbContextHealthStartup.cs +++ b/aspnetcore/host-and-deploy/health-checks/samples/3.x/HealthChecksSample/DbContextHealthStartup.cs @@ -1,6 +1,5 @@ using System; using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; diff --git a/aspnetcore/host-and-deploy/health-checks/samples/3.x/HealthChecksSample/LivenessProbeStartup.cs b/aspnetcore/host-and-deploy/health-checks/samples/3.x/HealthChecksSample/LivenessProbeStartup.cs index cc7f99678f79..e44d14cb1c1d 100644 --- a/aspnetcore/host-and-deploy/health-checks/samples/3.x/HealthChecksSample/LivenessProbeStartup.cs +++ b/aspnetcore/host-and-deploy/health-checks/samples/3.x/HealthChecksSample/LivenessProbeStartup.cs @@ -3,9 +3,7 @@ using Microsoft.AspNetCore.Diagnostics.HealthChecks; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Diagnostics.HealthChecks; -using Microsoft.Extensions.Hosting; using SampleApp.Services; namespace SampleApp diff --git a/aspnetcore/host-and-deploy/health-checks/samples/3.x/HealthChecksSample/ManagementPortStartup.cs b/aspnetcore/host-and-deploy/health-checks/samples/3.x/HealthChecksSample/ManagementPortStartup.cs index fc66a0366f73..56f01f4f150d 100644 --- a/aspnetcore/host-and-deploy/health-checks/samples/3.x/HealthChecksSample/ManagementPortStartup.cs +++ b/aspnetcore/host-and-deploy/health-checks/samples/3.x/HealthChecksSample/ManagementPortStartup.cs @@ -1,5 +1,4 @@ using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; diff --git a/aspnetcore/host-and-deploy/health-checks/samples/3.x/HealthChecksSample/SampleApp.csproj b/aspnetcore/host-and-deploy/health-checks/samples/3.x/HealthChecksSample/SampleApp.csproj index 2e54e4e19ea2..117c1d993eea 100644 --- a/aspnetcore/host-and-deploy/health-checks/samples/3.x/HealthChecksSample/SampleApp.csproj +++ b/aspnetcore/host-and-deploy/health-checks/samples/3.x/HealthChecksSample/SampleApp.csproj @@ -1,15 +1,14 @@ - netcoreapp3.0 + netcoreapp3.1 - - - - - + + + + From 05fa7a18ef5766d113e6f21df6ae53d8e74b84c5 Mon Sep 17 00:00:00 2001 From: Luke Latham <1622880+guardrex@users.noreply.github.com> Date: Sun, 15 Dec 2019 13:34:45 -0600 Subject: [PATCH 2/5] Nit --- aspnetcore/host-and-deploy/health-checks.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aspnetcore/host-and-deploy/health-checks.md b/aspnetcore/host-and-deploy/health-checks.md index efe693269e48..e96a4e7e6b30 100644 --- a/aspnetcore/host-and-deploy/health-checks.md +++ b/aspnetcore/host-and-deploy/health-checks.md @@ -531,7 +531,7 @@ app.UseEndpoints(endpoints => } ``` -A `WriteResponse` delegate formats the `CompositeHealthCheckResult` into a JSON object and yields JSON output for the health check response. For more information, see the [Customize output](#customize-output) section. +The `WriteResponse` delegate formats the `CompositeHealthCheckResult` into a JSON object and yields JSON output for the health check response. For more information, see the [Customize output](#customize-output) section. To run the metric-based probe with custom response writer output using the sample app, execute the following command from the project's folder in a command shell: From 854ebd3e8f423abde53ffbe45b80337a0d0b7446 Mon Sep 17 00:00:00 2001 From: Luke Latham <1622880+guardrex@users.noreply.github.com> Date: Mon, 16 Dec 2019 10:44:08 -0600 Subject: [PATCH 3/5] React to feedback --- .../samples/2.x/HealthChecksSample/CustomWriterStartup.cs | 2 +- .../samples/3.x/HealthChecksSample/CustomWriterStartup.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/aspnetcore/host-and-deploy/health-checks/samples/2.x/HealthChecksSample/CustomWriterStartup.cs b/aspnetcore/host-and-deploy/health-checks/samples/2.x/HealthChecksSample/CustomWriterStartup.cs index 79e082321685..8da835a797f2 100644 --- a/aspnetcore/host-and-deploy/health-checks/samples/2.x/HealthChecksSample/CustomWriterStartup.cs +++ b/aspnetcore/host-and-deploy/health-checks/samples/2.x/HealthChecksSample/CustomWriterStartup.cs @@ -48,7 +48,7 @@ await context.Response.WriteAsync( private static Task WriteResponse(HttpContext httpContext, HealthReport result) { - httpContext.Response.ContentType = "application/json"; + httpContext.Response.ContentType = "application/json; charset=utf-8"; var json = new JObject( new JProperty("status", result.Status.ToString()), diff --git a/aspnetcore/host-and-deploy/health-checks/samples/3.x/HealthChecksSample/CustomWriterStartup.cs b/aspnetcore/host-and-deploy/health-checks/samples/3.x/HealthChecksSample/CustomWriterStartup.cs index 868b22099173..75490aa63b11 100644 --- a/aspnetcore/host-and-deploy/health-checks/samples/3.x/HealthChecksSample/CustomWriterStartup.cs +++ b/aspnetcore/host-and-deploy/health-checks/samples/3.x/HealthChecksSample/CustomWriterStartup.cs @@ -58,7 +58,7 @@ await context.Response.WriteAsync( #region snippet_WriteResponse_SystemTextJson private static Task WriteResponse(HttpContext context, HealthReport result) { - context.Response.ContentType = "application/json"; + context.Response.ContentType = "application/json; charset=utf-8"; var options = new JsonWriterOptions { From a1697f331cc1cccdfc40b1b65bcbb3b8083eb7bd Mon Sep 17 00:00:00 2001 From: Luke Latham <1622880+guardrex@users.noreply.github.com> Date: Mon, 16 Dec 2019 10:49:49 -0600 Subject: [PATCH 4/5] React to feedback --- .../samples/3.x/HealthChecksSample/CustomWriterStartup.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/aspnetcore/host-and-deploy/health-checks/samples/3.x/HealthChecksSample/CustomWriterStartup.cs b/aspnetcore/host-and-deploy/health-checks/samples/3.x/HealthChecksSample/CustomWriterStartup.cs index 75490aa63b11..3aa411b0956d 100644 --- a/aspnetcore/host-and-deploy/health-checks/samples/3.x/HealthChecksSample/CustomWriterStartup.cs +++ b/aspnetcore/host-and-deploy/health-checks/samples/3.x/HealthChecksSample/CustomWriterStartup.cs @@ -80,8 +80,10 @@ private static Task WriteResponse(HttpContext context, HealthReport result) writer.WriteStartObject("data"); foreach (var item in entry.Value.Data) { - var value = Convert.ToDecimal(item.Value); - writer.WriteNumber(item.Key, value); + writer.WritePropertyName(item.Key); + JsonSerializer.Serialize( + writer, item.Value, item.Value?.GetType() ?? + typeof(object)); } writer.WriteEndObject(); writer.WriteEndObject(); From ac562b8df1c5bdeda1f2f7f53eedb36081b0e903 Mon Sep 17 00:00:00 2001 From: Luke Latham <1622880+guardrex@users.noreply.github.com> Date: Mon, 16 Dec 2019 10:56:23 -0600 Subject: [PATCH 5/5] Update --- .../samples/3.x/HealthChecksSample/CustomWriterStartup.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/aspnetcore/host-and-deploy/health-checks/samples/3.x/HealthChecksSample/CustomWriterStartup.cs b/aspnetcore/host-and-deploy/health-checks/samples/3.x/HealthChecksSample/CustomWriterStartup.cs index 3aa411b0956d..dd529aa29a9c 100644 --- a/aspnetcore/host-and-deploy/health-checks/samples/3.x/HealthChecksSample/CustomWriterStartup.cs +++ b/aspnetcore/host-and-deploy/health-checks/samples/3.x/HealthChecksSample/CustomWriterStartup.cs @@ -3,16 +3,20 @@ using System; using System.IO; using System.Linq; +#if SYSTEM_TEXT_JSON using System.Text; using System.Text.Json; +#endif using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Diagnostics.HealthChecks; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Diagnostics.HealthChecks; +#if !SYSTEM_TEXT_JSON using Newtonsoft.Json; using Newtonsoft.Json.Linq; +#endif namespace SampleApp {