我正在使用here中所述的Dotnet Core健康检查。简而言之,它看起来像这样:
首先,您需要像这样配置服务:
class User {
use YourTrait {
roles as protected traitRoles;
}
}
然后,您像这样注册一个端点:
services.AddHealthChecks()
.AddSqlServer("connectionString", name: "SQlServerHealthCheck")
... // Add multiple other checks
我们还使用了Swagger(又名Open API),我们通过Swagger UI看到了所有端点,但是没有通过运行状况检查端点。
是否可以将其添加到控制器方法中,以便Swagger自动获取端点,或者可以通过其他方式将其与swagger集成?
到目前为止,我发现最好的解决方案是添加一个自定义的硬编码终结点(like described here),但是维护起来并不好。
答案 0 :(得分:4)
由于Swagger已更新,.NET 2.x和3.1 / Swagger 4.0.0和5.0.0之间存在重大变化
以下是可与5.0.0一起使用的穷人解决方案的版本(请参阅eddyP23答案)。
public class HealthChecksFilter : IDocumentFilter
{
public const string HealthCheckEndpoint = @"/healthcheck";
public void Apply(OpenApiDocument openApiDocument, DocumentFilterContext context)
{
var pathItem = new OpenApiPathItem();
var operation = new OpenApiOperation();
operation.Tags.Add(new OpenApiTag { Name = "ApiHealth" });
var properties = new Dictionary<string, OpenApiSchema>();
properties.Add("status", new OpenApiSchema() { Type = "string" });
properties.Add("errors", new OpenApiSchema() { Type = "array" });
var response = new OpenApiResponse();
response.Content.Add("application/json", new OpenApiMediaType
{
Schema = new OpenApiSchema
{
Type = "object",
AdditionalPropertiesAllowed = true,
Properties = properties,
}
});
operation.Responses.Add("200", response);
pathItem.AddOperation(OperationType.Get, operation);
openApiDocument?.Paths.Add(HealthCheckEndpoint, pathItem);
}
}
答案 1 :(得分:2)
仍在寻找更好的解决方案,但是穷人对这个问题的解决方案看起来像这样:
public const string HealthCheckEndpoint = "/my/healthCheck/endpoint";
public void Apply(SwaggerDocument swaggerDoc, DocumentFilterContext context)
{
var pathItem = new PathItem();
pathItem.Get = new Operation()
{
Tags = new[] { "ApiHealth" },
Produces = new[] { "application/json" }
};
var properties = new Dictionary<string, Schema>();
properties.Add("status", new Schema(){ Type = "string" });
properties.Add("errors", new Schema(){ Type = "array" });
var exampleObject = new { status = "Healthy", errors = new List<string>()};
pathItem.Get.Responses = new Dictionary<string, Response>();
pathItem.Get.Responses.Add("200", new Response() {
Description = "OK",
Schema = new Schema() {
Properties = properties,
Example = exampleObject }});
swaggerDoc.Paths.Add(HealthCheckEndpoint, pathItem);
}
答案 2 :(得分:1)
将健康检查端点集成到 .NET 5 上的 Swagger(开放 API)用户界面
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);
}
}
}
过滤器设置
Services.AddSwaggerGen(Options =>
{
Options.SwaggerDoc("v1", new OpenApiInfo
{
Version = "v1",
Title = "<Name Api> Api",
Description = "<Description> HTTP API."
});
Options.DocumentFilter<HealthCheckEndpointDocumentFilter>();
});
答案 3 :(得分:0)
没有内置的支持,您是手动开发poor man's solution like in the accepted answer还是开发this GitHub issue: NetCore 2.2 - Health Check Support中提到的扩展名
Swashbuckle建立在
ApiExplorer
(ASP.NET Core附带的API元数据组件)之上。如果运行状况检查端点没有因此而浮出水面,那么Swashbuckle将不会使它们浮出水面。这是SB设计的基本方面,不太可能很快改变。
IMO,这听起来像是社区附加软件包的理想选择(请参见https://github.com/domaindrivendev/Swashbuckle.AspNetCore#community-packages)。
如果愿意的话,他们可以启动一个名为
Swashbuckle.AspNetCore.HealthChecks
的新项目,该项目在SwaggerGenOptions
上公开了用于启用该功能的扩展方法-例如EnableHealthCheckDescriptions
。然后,可以在后台将其实现为文档过滤器(请参阅自述文件),该过滤器会将相关的操作说明添加到Swashbuckle生成的Swagger/OAI
文档中。
答案 4 :(得分:0)
我的解决方法是添加以下虚拟控制器。
using HealthChecks.UI.Client;
using Microsoft.AspNetCore.Mvc;
using System;
[Route("[controller]")]
[ApiController]
[Produces("application/json")]
public class HealthController: ControllerBase
{
[HttpGet("")]
public UIHealthReport Health()
{
throw new NotImplementedException("");
}
}
答案 5 :(得分:0)
我使用了这种方法,对我来说效果很好:https://www.codit.eu/blog/documenting-asp-net-core-health-checks-with-openapi
添加新的控制器,例如HealthController并将HealthCheckService注入到构造函数中。当您在Startup.cs中调用AddHealthChecks时,会将HealthCheckService添加为依赖项:
重建时,HealthController应该出现在Swagger中:
[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);
}
}
我注意到的一件事是,端点仍然是“ / health”(或您在Startup.cs中设置的任何值),而不是“ / api / vxx / health”,但它仍会正确显示在Swagger中。
答案 6 :(得分:0)
我将穷人的解决方案升级为更具描述性的文档,它将在 Swashbuckle 5 中正确显示响应类型。我在 Swagger UI 中获取端点,但 Open API 规范中的描述很笨拙。然后我将特定的健康检查数据类型添加到 swagger 文档中。我的解决方案是使用自定义响应编写器。
假设您覆盖了响应:
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapHealthChecks("/heartbeat", new Microsoft.AspNetCore.Diagnostics.HealthChecks.HealthCheckOptions()
{
ResponseWriter = HeartbeatUtility.WriteResponse
}) ;
});
假设您有以下健康检查响应编写器:
public static class HeartbeatUtility
{
public const string Path = "/heartbeat";
public const string ContentType = "application/json; charset=utf-8";
public const string Status = "status";
public const string TotalTime = "totalTime";
public const string Results = "results";
public const string Name = "Name";
public const string Description = "description";
public const string Data = "data";
public static Task WriteResponse(HttpContext context, HealthReport healthReport)
{
context.Response.ContentType = ContentType;
using (var stream = new MemoryStream())
{
using (var writer = new Utf8JsonWriter(stream, CreateJsonOptions()))
{
writer.WriteStartObject();
writer.WriteString(Status, healthReport.Status.ToString("G"));
writer.WriteString(TotalTime, healthReport.TotalDuration.ToString("c"));
if (healthReport.Entries.Count > 0)
writer.WriteEntries(healthReport.Entries);
writer.WriteEndObject();
}
var json = Encoding.UTF8.GetString(stream.ToArray());
return context.Response.WriteAsync(json);
}
}
private static JsonWriterOptions CreateJsonOptions()
{
return new JsonWriterOptions
{
Indented = true
};
}
private static void WriteEntryData(this Utf8JsonWriter writer, IReadOnlyDictionary<string, object> data)
{
writer.WriteStartObject(Data);
foreach (var item in data)
{
writer.WritePropertyName(item.Key);
var type = item.Value?.GetType() ?? typeof(object);
JsonSerializer.Serialize(writer, item.Value, type);
}
writer.WriteEndObject();
}
private static void WriteEntries(this Utf8JsonWriter writer, IReadOnlyDictionary<string, HealthReportEntry> healthReportEntries)
{
writer.WriteStartArray(Results);
foreach (var entry in healthReportEntries)
{
writer.WriteStartObject();
writer.WriteString(Name, entry.Key);
writer.WriteString(Status, entry.Value.Status.ToString("G"));
if (entry.Value.Description != null)
writer.WriteString(Description, entry.Value.Description);
if (entry.Value.Data.Count > 0)
writer.WriteEntryData(entry.Value.Data);
writer.WriteEndObject();
}
writer.WriteEndArray();
}
}
然后你可以有以下 IDocumentFilter 实现:
public class HealthChecksDocumentFilter : IDocumentFilter
{
private const string _name = "Heartbeat";
private const string _operationId = "GetHeartbeat";
private const string _summary = "Get System Heartbeat";
private const string _description = "Get the heartbeat of the system. If the system is OK, status 200 will be returned, else status 503.";
private const string _okCode = "200";
private const string _okDescription = "Healthy";
private const string _notOkCode = "503";
private const string _notOkDescription = "Not Healthy";
private const string _typeString = "string";
private const string _typeArray = "array";
private const string _typeObject = "object";
private const string _applicationJson = "application/json";
private const string _timespanFormat = "[-][d'.']hh':'mm':'ss['.'fffffff]";
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
{
ApplyComponentHealthStatus(swaggerDoc);
ApplyComponentHealthReportEntry(swaggerDoc);
ApplyComponentHealthReport(swaggerDoc);
ApplyPathHeartbeat(swaggerDoc);
}
private IList<IOpenApiAny> GetHealthStatusValues()
{
return typeof(HealthStatus)
.GetEnumValues()
.Cast<object>()
.Select(value => (IOpenApiAny)new OpenApiString(value.ToString()))
.ToList();
}
private void ApplyComponentHealthStatus(OpenApiDocument swaggerDoc)
{
swaggerDoc?.Components.Schemas.Add(nameof(HealthStatus), new OpenApiSchema
{
Type = _typeString,
Enum = GetHealthStatusValues()
});
}
private void ApplyComponentHealthReportEntry(OpenApiDocument swaggerDoc)
{
swaggerDoc?.Components.Schemas.Add(nameof(HealthReportEntry), new OpenApiSchema
{
Type = _typeObject,
Properties = new Dictionary<string, OpenApiSchema>
{
{
HeartbeatUtility.Name,
new OpenApiSchema
{
Type = _typeString
}
},
{
HeartbeatUtility.Status,
new OpenApiSchema
{
Reference = new OpenApiReference
{
Type = ReferenceType.Schema,
Id = nameof(HealthStatus)
}
}
},
{
HeartbeatUtility.Description,
new OpenApiSchema
{
Type = _typeString,
Nullable = true
}
},
{
HeartbeatUtility.Data,
new OpenApiSchema
{
Type = _typeObject,
Nullable = true,
AdditionalProperties = new OpenApiSchema()
}
}
}
});
}
private void ApplyComponentHealthReport(OpenApiDocument swaggerDoc)
{
swaggerDoc?.Components.Schemas.Add(nameof(HealthReport), new OpenApiSchema()
{
Type = _typeObject,
Properties = new Dictionary<string, OpenApiSchema>
{
{
HeartbeatUtility.Status,
new OpenApiSchema
{
Reference = new OpenApiReference
{
Type = ReferenceType.Schema,
Id = nameof(HealthStatus)
}
}
},
{
HeartbeatUtility.TotalTime,
new OpenApiSchema
{
Type = _typeString,
Format = _timespanFormat,
Nullable = true
}
},
{
HeartbeatUtility.Results,
new OpenApiSchema
{
Type = _typeArray,
Nullable = true,
Items = new OpenApiSchema
{
Reference = new OpenApiReference
{
Type = ReferenceType.Schema,
Id = nameof(HealthReportEntry)
}
}
}
}
}
});
}
private void ApplyPathHeartbeat(OpenApiDocument swaggerDoc)
{
swaggerDoc?.Paths.Add(HeartbeatUtility.Path, new OpenApiPathItem
{
Operations = new Dictionary<OperationType, OpenApiOperation>
{
{
OperationType.Get,
new OpenApiOperation
{
Summary = _summary,
Description = _description,
OperationId = _operationId,
Tags = new List<OpenApiTag>
{
new OpenApiTag
{
Name = _name
}
},
Responses = new OpenApiResponses
{
{
_okCode,
new OpenApiResponse
{
Description = _okDescription,
Content = new Dictionary<string, OpenApiMediaType>
{
{
_applicationJson,
new OpenApiMediaType
{
Schema = new OpenApiSchema
{
Reference = new OpenApiReference
{
Type = ReferenceType.Schema,
Id = nameof(HealthReport)
}
}
}
}
}
}
},
{
_notOkCode,
new OpenApiResponse
{
Description = _notOkDescription,
Content = new Dictionary<string, OpenApiMediaType>
{
{
_applicationJson,
new OpenApiMediaType
{
Schema = new OpenApiSchema
{
Reference = new OpenApiReference
{
Type = ReferenceType.Schema,
Id = nameof(HealthReport)
}
}
}
}
}
}
}
}
}
}
}
});
}
}
添加到您的 swaggergen 选项
options.DocumentFilter<HealthChecksDocumentFilter>();