1
votes

I started using Health checks from Asp.net Core, I love them but I could not find an easy way to wire it up with tenant based routing, for example supporting:

  • /health [generic non tenant specific checks]
  • /{tenant}/health [tenant specific check]

If I succeeded with this approach I could have used tags to filter which health check to use, but unfortunately I failed to configure the routing for the request.

app.UseEndpointRouting();
app.UseHealthChecks("/{tenant}/health", new HealthCheckOptions
    {
        ResponseWriter = WriteCustomHealthResponse,
        AllowCachingResponses = false,
        Predicate = _ => _.Tags.Contains("tenant-specific")
    });

The above code is not routing correctly. I explored the possibility to use something like the below:

app.MapWhen(context => 
    context.Request.Method == HttpMethod.Get.Method &&
    context.Request.?ROUTEDATA?.SOMECHECK("/{tenant}/HealthCheck"),
                builder => builder.UseHealthChecks());

But in this case I don't have a way to check if the routing is correct.

1
Why would you need this? The health check is for the application as a whole.Chris Pratt
Well I disagree, in my architecture I can have availability issues affectign only certain tenants, also I have tenant specific databases for example, and various tenant driven configurations, integrations and so on. I want to check multi tenant resources (and more) but also tenant specific, which I don't know necessarily if they exist. Maybe I am overloading the feature but this really give me a nice insight about the system status.Norcino

1 Answers

1
votes

So far the solution I found is to use query string parameters and use the IHttpContextAccessor to search for the tenant parameter. For this purpose I created a base abstract implementation of IHealthCheck.

public abstract class BaseTenantHealthCheck : IHealthCheck
{
    private IHttpContextAccessor _httpContextAccessor;

    public BaseTenantHealthCheck(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor;
    }

    protected string GetTenant()
    {
        return _httpContextAccessor?.HttpContext?.Request?.Query["tenant"].ToString();
    }

    protected bool IsTenantSpecificCheck() => !string.IsNullOrEmpty(GetTenant());

    public abstract Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context,
        CancellationToken cancellationToken = default(CancellationToken));
}

Within the implementation class then I pass the context accessor shown below.

public class MylAvailabilityHealthCheck : BaseTenantHealthCheck
{
    public MyAvailabilityHealthCheck(IOptionsMonitor<MyAvailabilityCheckOptions> options, IHttpContextAccessor httpContextAccessor)
    : base(httpContextAccessor)
    {
        [..]

To access the health check I use:

  • http://{url}/health [for multi tenant checks]
  • http://{url}/health?tenant=TenantName [for tenant specific checks]

I look forward to hear if there is a more elegant way to do this.