Today I’ll show a simple example of how to create a .Net Core Health Endpoint Monitoring Middleware.

If you need to know about the Health Endpoint Monitoring Pattern check: https://msdn.microsoft.com/en-us/library/dn589789.aspx

1. Create the application#


Open a command prompt and run

1    md service.status.middleware
2    cd service.status.middleware
3    dotnet new
4    dotnet restore
5    code .

2. Add the middleware ServiceStatusMiddleware class#


Create a Middleware folder in your project and add a ServiceStatusMiddleware.cs file with the following contents

  1namespace WebApplication
  2{
  3    using System;
  4    using System.Net;
  5    using System.Threading.Tasks;
  6    using Microsoft.AspNetCore.Builder;
  7    using Microsoft.AspNetCore.Http;
  8    using Newtonsoft.Json;
  9
 10    /// <summary>
 11    /// Service Status Middleware used to check the Health of your service.
 12    /// </summary>
 13    public class ServiceStatusMiddleware
 14    {
 15        /// <summary>
 16        /// Next request RequestDelegate
 17        /// </summary>
 18        private readonly RequestDelegate next;
 19
 20        /// <summary>
 21        /// Health check function.
 22        /// </summary>
 23        private readonly Func<Task<bool>> serviceStatusCheck;
 24
 25        /// <summary>
 26        /// ServiceStatus enpoint path 
 27        /// </summary>
 28        private static readonly PathString statePath = new PathString("/_check");
 29
 30        /// <summary>
 31        /// Constructor
 32        /// </summary>
 33        /// 
 34        /// 
 35        public ServiceStatusMiddleware(RequestDelegate next, Func<Task<bool>> serviceStatusCheck)
 36        {
 37            this.next = next;
 38            this.serviceStatusCheck = serviceStatusCheck;
 39        }
 40
 41        /// <summary>
 42        /// Where the middleware magic happens
 43        /// </summary>
 44        /// 
 45        /// <returns>Task</returns>
 46        public async Task Invoke(HttpContext httpContext)
 47        {
 48            // If the path is different from the statePath let the request through the normal pipeline.
 49            if (!httpContext.Request.Path.Equals(statePath))
 50            {
 51                await this.next.Invoke(httpContext);
 52            }
 53            else
 54            {
 55                // If the path is statePath call the health check function.
 56                await CheckAsync(httpContext);
 57            }
 58        }
 59
 60        /// <summary>
 61        /// Call the health check function and set the response.
 62        /// </summary>
 63        /// 
 64        /// <returns>Task</returns>
 65        private async Task CheckAsync(HttpContext httpContext)
 66        {
 67            if (await this.serviceStatusCheck().ConfigureAwait(false))
 68            {
 69                // Service is available.
 70                await WriteResponseAsync(httpContext, HttpStatusCode.OK, new ServiceStatus(true));
 71            }
 72            else
 73            {
 74                // Service is unavailable.
 75                await WriteResponseAsync(httpContext, HttpStatusCode.ServiceUnavailable, new ServiceStatus(false));
 76            }
 77        }
 78
 79        /// <summary>
 80        /// Writes a response of the Service Status Check.
 81        /// </summary>
 82        /// 
 83        /// 
 84        /// 
 85        /// <returns>Task</returns>
 86        private Task WriteResponseAsync(HttpContext httpContext, HttpStatusCode httpStatusCode, ServiceStatus serviceStatus)
 87        {
 88            // Set content type.
 89            httpContext.Response.Headers["Content-Type"] = new[] { "application/json" };
 90
 91            // Minimum set of headers to disable caching of the response.
 92            httpContext.Response.Headers["Cache-Control"] = new[] { "no-cache, no-store, must-revalidate" };
 93            httpContext.Response.Headers["Pragma"] = new[] { "no-cache" };
 94            httpContext.Response.Headers["Expires"] = new[] { "0" };
 95
 96            // Set status code.
 97            httpContext.Response.StatusCode = (int)httpStatusCode;
 98
 99            // Write the content.
100            var content = JsonConvert.SerializeObject(serviceStatus);
101            return httpContext.Response.WriteAsync(content);
102        }
103    }
104
105    /// <summary>
106    /// ServiceStatus to hold the response. 
107    /// </summary>
108    public class ServiceStatus
109    {
110        public ServiceStatus(bool available)
111        {
112            Available = available;
113        }
114
115        /// <summary>
116        /// Tells if the service is available
117        /// </summary>
118        /// <returns>True if the service is available</returns>
119        public bool Available { get; }
120    }
121
122    /// <summary>
123    /// Service Status Middleware Extensions
124    /// </summary>
125    public static class ServiceStatusMiddlewareExtensions
126    {
127        public static IApplicationBuilder UseServiceStatus(
128          this IApplicationBuilder app,
129          Func<Task<bool>> serviceStatusCheck)
130        {
131            app.UseMiddleware<ServiceStatusMiddleware>(serviceStatusCheck);
132
133            return app;
134        }
135    }
136
137}

3. Add the middleware to the pipeline#


In the Startup class find the app.AddMvc line and add the following just above

1// Call the ServiceStatus middleware with a random failing function. 
2// Feel free to replace the function with any check you need.
3Random random = new Random();
4app.UseServiceStatus(() => 
5{
6    return Task.FromResult(random.Next(10) != 1);
7});

4. Run the application#


Open a command prompt and run

1    dotnet run

Browse to: http://localhost:5000/_check and your browser should show

1{"Available":true}

or

1{"Available":false}

depending on the result of the random function we added to the middleware.

Checkout AspNetCore.Health for nice and extensible implementation of the pattern.

Also find a copy of the code here: https://github.com/cmendible/dotnetcore.samples/tree/main/service.status.middleware

Hope it helps!