3

我已经Microsoft.AspNetCore.Diagnostics.HealthChecks向我的应用程序添加了样式健康检查,正如Microsoft 在这里记录的那样

我也在使用Swashbuckle来生成一个招摇的文档。然后我使用NSwag生成一个客户端 API 供我的其他应用程序使用。

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

所以我的问题是将 healthcheck 端点添加到 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");
    });

    // ...
}
4

1 回答 1

1

我最终创建了一个控制器,专门用于在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" })
        ;

    // ...

}

健康检查控制器.cs

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

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

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

我使用依赖注入来请求HealthCheckServiceHealthCheckOptions对象。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端点返回的数据结构。

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

例如,我添加了额外的属性,例如ApiVersion,这样我就可以知道部署了我的应用程序的哪个版本。

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

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

健康检查数据.cs

public class HealthCheckData
{
    public string Key { get; set; }
    public object Value { get; set; }
}
于 2020-10-26T01:21:55.450 回答