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.
A successful health check should return 200 OK. Use other standard HTTP status codes, including 500 Internal Error, to signal a failure.
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.
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>();
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With