Why can't this particular configuration be resolved via dependency injection?

I have a .NET Core 6 API project that already successfully loads configurations from appsettings.json into classes and also loads them into my services via dependency injection.

Now I wanted to add another configuration, but I just get the following exception when I call the service (or in this case Healthcheck – type names have been changed):

Unable to resolve service for type ‘BrokenConfigClass’ while attempting to activate ‘HealthCheckUsingBrokenConfig’.

Stacktrace:

   at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.ConstructorMatcher.CreateInstance(IServiceProvider provider)
   at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.CreateInstance(IServiceProvider provider, Type instanceType, Object[] parameters)
   at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.GetServiceOrCreateInstance[T](IServiceProvider provider)
   at Microsoft.Extensions.DependencyInjection.HealthChecksBuilderAddCheckExtensions.<>c__3`1.<AddCheck>b__3_0(IServiceProvider s)
   at Microsoft.Extensions.Diagnostics.HealthChecks.DefaultHealthCheckService.RunCheckAsync(HealthCheckRegistration registration, CancellationToken cancellationToken)
   at Microsoft.Extensions.Diagnostics.HealthChecks.DefaultHealthCheckService.CheckHealthAsync(Func`2 predicate, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Diagnostics.HealthChecks.HealthCheckMiddleware.InvokeAsync(HttpContext httpContext)
   at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at Serilog.AspNetCore.RequestLoggingMiddleware.Invoke(HttpContext httpContext)
[03:31:13 ERR] Connection id "0HMNND6U6RR42", Request id "0HMNND6U6RR42:00000001": An unhandled exception was thrown by the application.

However, my other configurations work and are set up exactly like the new one.

Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    ...

    services.AddConfig(configuration);

    ...

    services
        .AddHealthChecks()
        .AddCheck<HealthCheckUsingBrokenConfig>(nameof(HealthCheckUsingBrokenConfig));

    ...
}

AddConfig is an extension:

public static class ServiceCollectionExtensions
{
    public static IServiceCollection AddConfig(this IServiceCollection services, IConfiguration configuration)
    {
        services.Configure<WorkingConfig1>(configuration.GetSection("Level1:SubLevel1:WorkingConfig1"));
        services.Configure<WorkingConfig2>(configuration.GetSection("Level1:SubLevel2:WorkingConfig2"));
        services.Configure<WorkingConfig3>(configuration.GetSection("Level1:SubLevel3:WorkingConfig3"));
        services.Configure<BrokenConfigClass>(configuration.GetSection("HealthChecks:BrokenConfigClass"));

        return services;
    }
}

appsettings.json

{
    "Level1": {
        "SubLevel1": {
            "WorkingConfig1": {
                "Property1": "Value",
                "Property2": "Value",
                "Property3": "Value"
            }
        },
        "SubLevel2": {
            "WorkingConfig2": {
                "Property1": "Value",
                "Property2": "Value",
                "Property3": "Value"
            }
        },

        "SubLevel3": {
            "WorkingConfig3": {
                "Property1": "Value",
                "Property2": "Value",
                "Property3": "Value"
            }
        }
    },
    "HealthChecks": {
        "BrokenConfigClass": {
            "DelayInMinutes": 60
        }
    }
}

BrokenConfigClass:

public class BrokenConfigClass
{
    public int DelayInMinutes { get; set; }
}

HealthCheckUsingBrokenConfig:

public class HealthCheckUsingBrokenConfig : IHealthCheck
{
    private readonly BrokenConfigClass healthCheckConfig;
    private readonly DatabaseContext databaseContext;

    public HealthCheckUsingBrokenConfig(DatabaseContext databaseContext, BrokenConfigClass healthCheckConfig)
    {
        this.databaseContext = databaseContext;
        this.healthCheckConfig = healthCheckConfig;
    }

    public Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
    {
        // Some logic
    }
}

When I call the healthcheck, I get the exception from above. When I try to load the config in a service, I get the same exception.
But when I load the other configs, everything works fine.

I double and triple checked the path to the section in appsettings.json. I copied the keys and pasted them into the GetSection() call.

When I call the following code, I also get the value 60 (from the appsettings.json):

configuration.GetSection("HealthChecks:BrokenConfigClass").GetValue<int>("DelayInMinutes")

Does anyone see my gross error or can explain what the problem is?

The configurations used here (like WorkingConfig1) have been renamed, but from the structure it should match. If an error should have sneaked in here (as for example in the paths), these should not be considered, since this is definitely not the case in the real code!

>Solution :

The exception appears accurate as nowhere do you explicitly add BrokenConfigClass to the container.

When calling Configure<BrokenConfigClass> you are actually adding IOptions<BrokenConfigClass>.

So either update the target class constructor according

//ctor
public HealthCheckUsingBrokenConfig(DatabaseContext databaseContext, IOptions<BrokenConfigClass> healthCheckConfig)
{
    this.databaseContext = databaseContext;
    this.healthCheckConfig = healthCheckConfig.Value;
}

or explicitly add the type to the container so that it can be resolved and injected

//...
services.Configure<BrokenConfigClass>(configuration.GetSection("HealthChecks:BrokenConfigClass"));

services.AddScoped<BrokenConfigClass>(sp => sp.GetRequiredService<IOptions<BrokenConfigClass>>().Value);

Leave a Reply