如何将 healthcheck 端点添加到 ApiExplorer 以便 Swashbuck 将其包含在生成的 swagger.json 中

br3*_*3nt 5 swagger asp.net-mvc-apiexplorer swashbuckle asp.net-core

我已将Microsoft.AspNetCore.Diagnostics.HealthChecks样式健康检查添加到我的应用程序中,如Microsoft 此处所述

我还使用Swashbuckle生成一个 swagger 文档。然后,我使用NSwag生成客户端 API 供其他应用程序使用。

问题是Startup.csMapHealthChecks中添加的运行状况检查端点没有添加到. 这是一个问题,因为 Swashbuckle 使用它来生成 swagger 文档。ApiExplorer

所以我的问题是将健康检查端点添加到 ApiExplorer 以便 Swashbuckle 可以将其包含在 swagger 文件中的最佳方法是什么?

我尝试手动添加健康检查端点add 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");
    });

    // ...
}
Run Code Online (Sandbox Code Playgroud)

br3*_*3nt 2

我最终创建了一个专门用于返回健康检查的控制器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": {}
        }
      ]
    }
  ]
}
Run Code Online (Sandbox Code Playgroud)

这是一个实际的回应:

{
  "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": []
    }
  ]
}
Run Code Online (Sandbox Code Playgroud)

以下是我的实现。

启动.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" })
        ;

    // ...

}
Run Code Online (Sandbox Code Playgroud)

HealthCheckController.cs

我们的 HealthCheckController 包含一个Index将响应的端点GET api/healthcheck

Index返回一个自定义HealthCheckReport对象,它是实际HealthReport对象的包装器。这使我能够控制返回的数据以及结构。我这样做是因为我想添加其他信息,例如应用程序版本和提交详细信息。

如果您不关心返回数据的格式,则可以返回HealthReport存储在report变量中的对象。您需要将返回类型更改为Task<HealthReport>以及属性中的ProducesResponseType

我使用依赖注入来请求HealthCheckServiceHealthCheckOptions对象。HealthCheckService用于生成实际报告。 使用,以便我可以访问我们在Setup.csHealthCheckOptions中所做的配置。

[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 }),
                };
            }),
        };
    }
}
Run Code Online (Sandbox Code Playgroud)

其余的类用于将HealthCheck对象转换为我想要从GET api/healthchecks端点返回的数据结构。

我仅使用这些对象,因为我对序列化为 JSON 的方式不满意HealthCheck,并且因为我想提供额外的数据。

例如,我添加了其他属性,例如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[] { };
}
Run Code Online (Sandbox Code Playgroud)

健康检查.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[] { };
}

Run Code Online (Sandbox Code Playgroud)

健康检查数据.cs

public class HealthCheckData
{
    public string Key { get; set; }
    public object Value { get; set; }
}
Run Code Online (Sandbox Code Playgroud)