Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Integrating HealthCheck endpoint into swagger (open API) UI on dotnet core

I am using Dotnet Core healthchecks as described here. In short, it looks like this:

First, you configure services like this:

services.AddHealthChecks()
    .AddSqlServer("connectionString", name: "SQlServerHealthCheck")
    ... // Add multiple other checks

Then, you register an endpoint like this:

app.UseHealthChecks("/my/healthCheck/endpoint");

We are also using Swagger (aka Open API) and we see all the endpoints via Swagger UI, but not the health check endpoint.

Is there a way to add this to a controller method so that Swagger picks up the endpoint automatically, or maybe integrate it with swagger in another way?

The best solution I found so far is to add a custom hardcoded endpoint (like described here), but it is not nice to maintain.

like image 787
eddyP23 Avatar asked Jan 25 '19 09:01

eddyP23


People also ask

What should a health check endpoint return?

A successful health check should return 200 OK. Use other standard HTTP status codes, including 500 Internal Error, to signal a failure.


2 Answers

I used this approach and it worked well for me: https://www.codit.eu/blog/documenting-asp-net-core-health-checks-with-openapi

Add a new controller e.g. HealthController and inject the HealthCheckService into the constructor. The HealthCheckService is added as a dependency when you call AddHealthChecks in Startup.cs:

The HealthController should appear in Swagger when you rebuild:

[Route("api/v1/health")]
public class HealthController : Controller
{
    private readonly HealthCheckService _healthCheckService;
    public HealthController(HealthCheckService healthCheckService)
    {
        _healthCheckService = healthCheckService;
    }
     
    /// <summary>
    /// Get Health
    /// </summary>
    /// <remarks>Provides an indication about the health of the API</remarks>
    /// <response code="200">API is healthy</response>
    /// <response code="503">API is unhealthy or in degraded state</response>
    [HttpGet]
    [ProducesResponseType(typeof(HealthReport), (int)HttpStatusCode.OK)]
    [SwaggerOperation(OperationId = "Health_Get")]
    public async Task<IActionResult> Get()
    {
        var report = await _healthCheckService.CheckHealthAsync();

        return report.Status == HealthStatus.Healthy ? Ok(report) : StatusCode((int)HttpStatusCode.ServiceUnavailable, report);
    }
}

One thing I noticed though is the endpoint is still "/health" (or whatever you set it to in Startup.cs) and not "/api/vxx/health" but it will still appear correctly in Swagger.

like image 90
Ciarán Bruen Avatar answered Sep 20 '22 16:09

Ciarán Bruen


Integrating Health Check Endpoint Into Swagger (Open API) UI On .NET 5

namespace <Some-Namespace>
{
    using global::HealthChecks.UI.Core;
    using global::HealthChecks.UI.Core.Data;

    using Microsoft.AspNetCore.Http;
    using Microsoft.Extensions.Options;
    using Microsoft.OpenApi.Any;
    using Microsoft.OpenApi.Models;

    using Swashbuckle.AspNetCore.SwaggerGen;

    using System;
    using System.Collections.Generic;

    using static System.Text.Json.JsonNamingPolicy;

    /// <summary>
    /// 
    /// </summary>
    public class HealthCheckEndpointDocumentFilter : IDocumentFilter
    {
        /// <summary>
        /// 
        /// </summary>
        private readonly global::HealthChecks.UI.Configuration.Options Options;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="Options"></param>
        public HealthCheckEndpointDocumentFilter(IOptions<global::HealthChecks.UI.Configuration.Options> Options)
        {
            this.Options = Options?.Value ?? throw new ArgumentNullException(nameof(Options));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="SwaggerDoc"></param>
        /// <param name="Context"></param>
        public void Apply(OpenApiDocument SwaggerDoc, DocumentFilterContext Context)
        {
            var PathItem = new OpenApiPathItem
            {
                Operations = new Dictionary<OperationType, OpenApiOperation>
                {
                    [OperationType.Get] = new OpenApiOperation
                    {
                        Description = "Returns all the health states used by this Microservice",
                        Tags =
                        {
                            new OpenApiTag
                            {
                                Name = "HealthCheck"
                            }
                        },
                        Responses =
                        {
                            [StatusCodes.Status200OK.ToString()] = new OpenApiResponse
                            {
                                Description = "API is healthy",
                                Content =
                                {
                                    ["application/json"] = new OpenApiMediaType
                                    {
                                        Schema = new OpenApiSchema
                                        {
                                            Reference = new OpenApiReference
                                            {
                                                Id = nameof(HealthCheckExecution),
                                                Type = ReferenceType.Schema,
                                            }
                                        }
                                    }
                                }
                            },
                            [StatusCodes.Status503ServiceUnavailable.ToString()] = new OpenApiResponse
                            {
                                Description = "API is not healthy"
                            }
                        }
                    }
                }
            };

            var HealthCheckSchema = new OpenApiSchema
            {
                Type = "object",
                Properties =
                {
                    [CamelCase.ConvertName(nameof(HealthCheckExecution.Id))] = new OpenApiSchema
                    {
                        Type = "integer",
                        Format = "int32"
                    },
                    [CamelCase.ConvertName(nameof(HealthCheckExecution.Status))] = new OpenApiSchema
                    {
                        Type = "string"
                    },
                    [CamelCase.ConvertName(nameof(HealthCheckExecution.OnStateFrom))] = new OpenApiSchema
                    {
                        Type = "string",
                        Format = "date-time"
                    },
                    [CamelCase.ConvertName(nameof(HealthCheckExecution.LastExecuted))] = new OpenApiSchema
                    {
                        Type = "string",
                        Format = "date-time"
                    },
                    [CamelCase.ConvertName(nameof(HealthCheckExecution.Uri))] = new OpenApiSchema
                    {
                        Type = "string"
                    },
                    [CamelCase.ConvertName(nameof(HealthCheckExecution.Name))] = new OpenApiSchema
                    {
                        Type = "string"
                    },
                    [CamelCase.ConvertName(nameof(HealthCheckExecution.DiscoveryService))] = new OpenApiSchema
                    {
                        Type = "string",
                        Nullable = true
                    },
                    [CamelCase.ConvertName(nameof(HealthCheckExecution.Entries))] = new OpenApiSchema
                    {
                        Type = "array",
                        Items = new OpenApiSchema
                        {
                            Reference = new OpenApiReference
                            {
                                Id = nameof(HealthCheckExecutionEntry),
                                Type = ReferenceType.Schema,
                            }
                        }
                    },
                    [CamelCase.ConvertName(nameof(HealthCheckExecution.History))] = new OpenApiSchema
                    {
                        Type = "array",
                        Items = new OpenApiSchema
                        {
                            Reference = new OpenApiReference
                            {
                                Id = nameof(HealthCheckExecutionHistory),
                                Type = ReferenceType.Schema,
                            }
                        }
                    }
                }
            };

            var HealthCheckEntrySchema = new OpenApiSchema
            {
                Type = "object",

                Properties =
                {
                    [CamelCase.ConvertName(nameof(HealthCheckExecutionEntry.Id))] = new OpenApiSchema
                    {
                        Type = "integer",
                        Format = "int32"
                    },
                    [CamelCase.ConvertName(nameof(HealthCheckExecutionEntry.Name))] = new OpenApiSchema
                    {
                        Type = "string"
                    },
                    [CamelCase.ConvertName(nameof(HealthCheckExecutionEntry.Status))] = new OpenApiSchema
                    {
                        Reference = new OpenApiReference
                        {
                            Id = nameof(UIHealthStatus),
                            Type = ReferenceType.Schema,
                        }
                    },
                    [CamelCase.ConvertName(nameof(HealthCheckExecutionEntry.Description))] = new OpenApiSchema
                    {
                        Type = "string"
                    },
                    [CamelCase.ConvertName(nameof(HealthCheckExecutionEntry.Duration))] = new OpenApiSchema
                    {
                        Type = "string",
                        Format = "[-][d'.']hh':'mm':'ss['.'fffffff]"
                    },
                    [CamelCase.ConvertName(nameof(HealthCheckExecutionEntry.Tags))] = new OpenApiSchema
                    {
                        Type = "array",
                        Items = new OpenApiSchema
                        {
                            Type = "string"
                        }
                    },
                }
            };

            var HealthCheckHistorySchema = new OpenApiSchema
            {
                Type = "object",

                Properties =
                {
                    [CamelCase.ConvertName(nameof(HealthCheckExecutionHistory.Id))] = new OpenApiSchema
                    {
                        Type = "integer",
                        Format = "int32"
                    },
                    [CamelCase.ConvertName(nameof(HealthCheckExecutionHistory.Name))] = new OpenApiSchema
                    {
                        Type = "string"
                    },
                    [CamelCase.ConvertName(nameof(HealthCheckExecutionHistory.Description))] = new OpenApiSchema
                    {
                        Type = "string"
                    },
                    [CamelCase.ConvertName(nameof(HealthCheckExecutionHistory.Status))] = new OpenApiSchema
                    {
                        Reference = new OpenApiReference
                        {
                            Id = nameof(UIHealthStatus),
                            Type = ReferenceType.Schema,
                        }
                    },
                    [CamelCase.ConvertName(nameof(HealthCheckExecutionHistory.On))] = new OpenApiSchema
                    {
                        Type = "string",
                        Format = "date-time"
                    },
                }
            };

            var UIHealthStatusSchema = new OpenApiSchema
            {
                Type = "string",

                Enum =
                {
                    new OpenApiString(UIHealthStatus.Healthy.ToString()),
                    new OpenApiString(UIHealthStatus.Unhealthy.ToString()),
                    new OpenApiString(UIHealthStatus.Degraded.ToString())
                }
            };

            SwaggerDoc.Paths.Add(Options.ApiPath, PathItem);
            SwaggerDoc.Components.Schemas.Add(nameof(HealthCheckExecution), HealthCheckSchema);
            SwaggerDoc.Components.Schemas.Add(nameof(HealthCheckExecutionEntry), HealthCheckEntrySchema);
            SwaggerDoc.Components.Schemas.Add(nameof(HealthCheckExecutionHistory), HealthCheckHistorySchema);
            SwaggerDoc.Components.Schemas.Add(nameof(UIHealthStatus), UIHealthStatusSchema);
        }
    }
}

Filter Settings

Services.AddSwaggerGen(Options =>
{
    Options.SwaggerDoc("v1", new OpenApiInfo
    {
        Version     = "v1",
        Title       = "<Name Api> Api",
        Description = "<Description> HTTP API."
    });

    Options.DocumentFilter<HealthCheckEndpointDocumentFilter>();
});
like image 21
Denis West Avatar answered Sep 19 '22 16:09

Denis West