0

I have 2 services that make Http requests with base url and authentication provided by a configuration table in a DbContext.

I want to implement a healthcheck to see if a service call is successful or not and I have the code below for example:

namespace Company.Api.Infrastructure.HealthChecks
{
    using Microsoft.AspNetCore.Http;
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.Extensions.Diagnostics.HealthChecks;
    using Services;
    using System;
    using System.Threading;
    using System.Threading.Tasks;

    public class ExampleServiceHealthCheck : IHealthCheck
    {
        private readonly Guid _sampleOrganizationId = new Guid("8f3a6dd9-6146-4cfa-8ae8-a0fa09998e54");
        private readonly IHttpContextAccessor _httpContextAccessor;

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

        public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
        {
            // Trying to create a scope for the service so I don't get concurrent dbContext access exception.
            var exampleService = _httpContextAccessor.HttpContext.RequestServices.GetRequiredService<ExampleService >();
            var invoices = await exampleService.GetInvoicesAsync(_sampleOrganizationId, false);
            if (invoices == null)
            {
                return HealthCheckResult.Unhealthy("Example service didn't return any invoices.");
            }

            return HealthCheckResult.Healthy("Successfully tested Example service with invoices call.");
        }
    }
}

Using one service is fine but when I add two services which both fetch their configuration from the database I get this error in one of them randomly.

{
  "status": "Unhealthy",
  "results": {
    "DbContext": {
      "status": "Healthy",
      "description": null,
      "data": {}
    },
    "ExampleService": {
      "status": "Healthy",
      "description": "Successfully tested ExampleService with invoices call.",
      "data": {}
    },
    "ExampleService2": {
      "status": "Unhealthy",
      "description": "An attempt was made to use the context while it is being configured. A DbContext instance cannot be used inside \u0027OnConfiguring\u0027 since it is still being configured at this point. This can happen if a second operation is started on this context before a previous operation completed. Any instance members are not guaranteed to be thread safe.",
      "data": {}
    }
  }
}

This is how my healthcheck registration looks like:

public static IServiceCollection AddApplicationHealthChecks(this IServiceCollection services)
{
    services.AddHealthChecks()
        .AddDbContextCheck<DbContext>()
        .AddCheck<ExampleServiceHealthCheck>("ExampleService")
        .AddCheck<ExampleService2HealthCheck>("ExampleService2");

    return services;
}

How can I navigate this error?

BigThinker
  • 81
  • 1
  • 11

1 Answers1

1

I think I fixed it by creating a scope before accessing the service like this: I think that makes sure the services are accessing the database serially and not in parallel causing the error above, and by default the CreateScope method creates a scope if it's missing in places outside of requests.

namespace Company.Api.Infrastructure.HealthChecks
{
    using Microsoft.AspNetCore.Http;
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.Extensions.Diagnostics.HealthChecks;
    using Services;
    using System.Threading;
    using System.Threading.Tasks;

    public class ExampleServiceHealthCheck : IHealthCheck
    {
        private readonly IHttpContextAccessor _httpContextAccessor;

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

        public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
        {
            using var serviceScope = _httpContextAccessor.HttpContext.RequestServices.CreateScope(); // <-- This line
            IExampleService exampleService = serviceScope.ServiceProvider.GetRequiredService<IExampleService>();
            var invoices = await exampleService.GetInvoicesAsync();
            if (invoices == null)
            {
                return HealthCheckResult.Unhealthy("Example service didn't return any invoices.");
            }

            return HealthCheckResult.Healthy("Successfully tested Example service with invoices call.");
        }
    }
}
BigThinker
  • 81
  • 1
  • 11