Search code examples
c#.net-coredependency-injectionautofacdotnet-httpclient

DelegatingHandler Dependency Injection


I have a class AuthorizationHandler that is derived from System.Net.Http.DelegatingHandler. It has a DI constuctor and overrides the SendAsync method to retrieve an authorization token from AuthenticationService before every request.

public class AuthorizationHandler : DelegatingHandler
{
    private readonly IAuthenticationService _service;
    public AuthorizationHandler(IAuthenticationService service)
    {
        _service = service;
    }

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        request.Headers.Authorization =
            new AuthenticationHeaderValue("Authorization", await _service.GetAccessToken());
        return await base.SendAsync(request, cancellationToken);
    }
}

However I receive the following DI registration issue whenever IHttpClientFactory.CreateClient() is invoked:

System.InvalidOperationException: No service for type 'MyProject.AuthenticationService.AuthorizationHandler' has been registered.

The following is a relevant snippet of my Autofac configuration.

...    
// Creates an IoC container and registers infrastructure modules 
var containerBuilder = new ContainerBuilder();
...
containerBuilder.RegisterType<AuthorizationHandler>().InstancePerDependency();
// ALSO TRIED
// containerBuilder.RegisterType<AuthorizationHandler>().As<DelegatingHandler>().InstancePerDependency();
containerBuilder.RegisterType<AuthenticationService>().As<IAuthenticationService>().InstancePerLifetimeScope();
containerBuilder.Register<IHttpClientFactory>(_ =>
{
    var services = new ServiceCollection();
    services.AddHttpClient("MyApi", client =>
        {
            client.BaseAddress = new Uri("https://example.com/authenticate");
            client.DefaultRequestHeaders.TryAddWithoutValidation("x-api-key",
                "MY-API-KEY");
        })
        .AddHttpMessageHandler<AuthorizationHandler>();
        // ALSO TRIED
        // .AddHttpMessageHandler((provider) => provider.GetRequiredService<AuthorizationHandler>());
    var provider = services.BuildServiceProvider();
    return provider.GetRequiredService<IHttpClientFactory>();
}).SingleInstance();

Solution

  • It appears you fell victim to one of the classic blunders. The most famous is never get involved in a land war in Asia. But only slightly less well known is this: You can't mix-and-match Autofac registrations with Microsoft.Extensions.DependencyInjection registrations.

    I mean, I get it, it would not be awesome to have to replicate some of that .AddHttpClient stuff. But I also see in the sample snippet this:

    containerBuilder.Register<IHttpClientFactory>(_ =>
    {
        services.AddHttpClient("MyApi", client =>
    

    Which means this is a closure over services, which gets... temporarily built? and then still ends up contributing to a different container (Autofac), which will then back the DI for the rest of the calls?

    Based on the snippet I have to assume you've integrated Autofac as the backing DI container and, as part of that, are using the AutofacServiceProviderFactory at app startup (as noted in the docs). If you're not doing that, and/or not following the docs, then the rest of the container setup - how you're actually building it, where services gets created, etc. - is all pretty relevant to the issue at hand and is not part of the question/repro. (That's why minimal reproducible example is so important. What's here is not reproducible and is a step past minimal.)

    Making the assumption that you're setting things up in a sort of supported way, the general order of operations in a standard ASP.NET Core app is:

    1. Create IServiceCollection services.
    2. Call ConfigureServices(services) to get registrations into the Microsoft format.
    3. Call the AutofacServiceProviderFactory to get a ContainerBuilder. This will...
      1. Create a ContainerBuilder.
      2. Convert the Microsoft format registrations into Autofac registrations and put those in the ContainerBuilder. (builder.Populate(services))
      3. Return that ContainerBuilder with all the Microsoft registrations already in it.
    4. Call ConfigureContainer(builder) with that ContainerBuilder.
    5. Hand that ContainerBuilder back to AutofacServiceProviderFactory to...
      1. Create the container - builder.Build().
      2. Wrap that in an IServiceProvider interface.

    So if you have interesting/odd stuff like "mix and match Microsoft registrations inside a delegate that you're registering in Autofac," you're going to get really painful-to-troubleshoot problems like you're describing now. You're kind of working with two different containers, right? You have

    • The Autofac one, which is built from the set of services before ConfigureContainer ran along with the stuff from ConfigureContainer; AND
    • An odd "partial container" that is being accessed from inside an Autofac registration delegate, which has the stuff from ConfigureServices and the stuff from inside the delegate, but is not the same as the set of registrations being used to back the rest of the application.

    Don't mix and match. Unwind all that and do not register new services or make tiny temporary containers inside delegates.

    I can't promise this will fix the problem, but it will probably make it far, far easier to troubleshoot.