我已将Microsoft.AspNetCore.Diagnostics.HealthChecks
样式的健康检查添加到我的应用程序中,Microsoft在此处对此进行了说明。
我还使用Swashbuckle生成了一个swagger文档。然后我使用NSwag生成一个客户端API供我的其他应用程序使用。
问题是在Startup.cs中添加了MapHealthChecks
的健康检查端点没有添加到ApiExplorer
。这是一个问题,因为Swashbuckle使用它来生成swagger文档。
那么,我的问题是,将健康检查端点添加到ApiExplorer中,以便Swashbuckle可以将其包含在swagger文件中的最佳方式是什么?
我已经尝试手动添加健康检查端点添加ApiExplorer(下面的代码(。应用程序成功运行,但swagger文档不包含端点。
// from Startup.cs
public virtual void ConfigureServices(IServiceCollection services)
{
// ...
// add swagger
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" });
});
// add healthchecks
services
.AddHealthChecks()
.AddDbContextCheck<DatabaseDomain.DbContext>(tags: new[] { "db" })
;
// ...
}
public virtual void Configure(IApplicationBuilder app, IHostEnvironment env, IApiDescriptionGroupCollectionProvider apiExplorer)
{
// ...
app.UseEndpoints(endpoints =>
{
endpoints.
.MapHealthChecks("/healthcheck", new HealthCheckOptions
{
Predicate = _ => true, // allow all healthchecks
AllowCachingResponses = false,
// custom writer to return health check results as JSON
ResponseWriter = (context, result) => {
context.Response.ContentType = "application/json";
// serialize the health check results
var json = System.Text.Json.JsonSerializer.Serialize(new
{
// my custom response object
});
return context.Response.WriteAsync(json);
},
})
.RequireAuthorization()
;
});
// attempt to get the healthcheck endpoint to ApiExplorer
var healthcheckDescription = new ApiDescription
{
HttpMethod = "GET",
RelativePath = "/healthcheck",
};
healthcheckDescription.SupportedRequestFormats.Add(new ApiRequestFormat
{
MediaType = "application/json"
});
healthcheckDescription.SupportedResponseTypes.Add(new ApiResponseType
{
IsDefaultResponse = true,
StatusCode = (int)HttpStatusCode.OK,
ApiResponseFormats = new List<ApiResponseFormat> {
new ApiResponseFormat
{
MediaType = "application/json"
}
}
});
apiExplorer.ApiDescriptionGroups.Items.Append(new ApiDescriptionGroup("HealthCheck", new List<ApiDescription> { healthcheckDescription }));
// configure swagger
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
});
// ...
}
我最终创建了一个控制器,专门用于返回GET api/healthchecks
的健康检查。
这使我能够提供有关端点返回的数据类型的信息,并控制返回数据的格式。
这是Swagger UI给出的示例响应:
{
"status": "string",
"totalDurationMs": 0,
"apiVersion": "string",
"apiVersionDescription": "string",
"healthChecks": [
{
"name": "string",
"status": "string",
"description": "string",
"durationMs": 0,
"tags": ["string"],
"data": [
{
"key": "string",
"value": {}
}
]
}
]
}
这是一个实际的回应:
{
"status": "Healthy",
"totalDurationMs": 82,
"apiVersion": "0.0.4-rc",
"apiVersionDescription": "0.0.3 at commit 2b188d3 [25 ahead] on branch release/0.0.4 (0.0.4-rc)",
"healthChecks": [
{
"name": "DbContext",
"status": "Healthy",
"description": null,
"durationMs": 72,
"tags": ["db"],
"data": []
}
]
}
以下是我的实现。
启动.cs
public virtual void ConfigureServices(IServiceCollection services)
{
// ...
// add swagger
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" });
});
// This allows me to access the HealthCheckOptions in my controllers
services
.AddSingleton(services => new HealthCheckOptions
{
Predicate = _ => true, // allow all healthchecks
AllowCachingResponses = false,
})
;
// add healthchecks
services
.AddHealthChecks()
.AddDbContextCheck<DatabaseDomain.DbContext>(tags: new[] { "db" })
;
// ...
}
HealthCheckController.cs
我们的HealthCheckController包含一个将响应GET api/healthcheck
的Index
端点。
Index
返回一个自定义HealthCheckReport
对象,该对象是实际HealthReport
对象的包装器。这使我能够控制返回的数据以及结构。我这样做是因为我想添加其他信息,如应用程序版本和提交详细信息。
如果您不关心返回的数据的格式,那么可以返回存储在report
变量中的HealthReport
对象。您需要将返回类型更改为Task<HealthReport>
以及ProducesResponseType
属性中的。
我使用依赖注入来请求HealthCheckService
和HealthCheckOptions
对象。HealthCheckService
用于生成实际报告。使用HealthCheckOptions
,这样我就可以访问Setup.cs.中的配置
[Route("api/[controller]")]
[ApiController]
public class HealthCheckController : AppController
{
[HttpGet(Name = "Healthcheck")]
[ProducesResponseType(typeof(HealthCheckReport), (int)HttpStatusCode.OK)]
[ProducesResponseType(typeof(HealthCheckReport), (int)HttpStatusCode.ServiceUnavailable)]
public async Task<HealthCheckReport> Index(
[FromServices] HealthCheckService healthCheckService,
[FromServices] HealthCheckOptions healthCheckOptions
)
{
var report = await healthCheckService.CheckHealthAsync(healthCheckOptions.Predicate, HttpContext.RequestAborted);
Response.StatusCode = healthCheckOptions.ResultStatusCodes[report.Status];
Response.ContentType = "application/json";
// if you want you can instead return `report`, but you would
// also need to change the return type to Task<HealthReport>
// as well as the in the ProducesResponseType attributes.
return new HealthCheckReport
{
Status = report.Status.ToString(),
TotalDurationMs = report.TotalDuration.Milliseconds,
HealthChecks = report.Entries.Select(pair =>
{
var entry = pair.Value;
return new HealthCheck
{
Name = pair.Key,
Status = entry.Status.ToString(),
Description = entry.Description,
DurationMs = entry.Duration.Milliseconds,
Tags = entry.Tags,
Data = entry.Data.Select(p => new HealthCheckData { Key = p.Key, Value = p.Value }),
};
}),
};
}
}
剩下的类用于将HealthCheck
对象转换为我想要从GET api/healthchecks
端点返回的数据结构。
我之所以使用这些对象,是因为我对HealthCheck
如何序列化为JSON不满意,而且我想提供额外的数据。
例如,我添加了ApiVersion
等附加属性,这样我就可以知道我的应用程序部署了什么版本。
HealthCheckReport.cs
public class HealthCheckReport
{
public string Status { get; set; }
public int TotalDurationMs { get; set; }
public string ApiVersion => Startup.SemanticVersion;
public string ApiVersionDescription => Startup.InformationalVersion;
public IEnumerable<HealthCheck> HealthChecks { get; set; } = new HealthCheck[] { };
}
HealthCheck.cs
public class HealthCheck
{
public string Name { get; set; }
public string Status { get; set; }
public string Description { get; set; }
public int DurationMs { get; set; }
public IEnumerable<string> Tags { get; set; } = new string[] { };
public IEnumerable<HealthCheckData> Data { get; set; } = new HealthCheckData[] { };
}
HealthCheckData.cs
public class HealthCheckData
{
public string Key { get; set; }
public object Value { get; set; }
}