34

With the new HttpClientFactory in ASP.NET Core 2.1, it's quite easy to configure custom HTTP clients with things like base urls, default headers etc.

However, I haven't found a way to centralize configuration that lets me inject headers from the current request context. For example, consider a service called with an Authorization header, which I wish to pass on to all the underlying services as well. It would be awesome to be able to configure this in the .AddHttpClient() call on services in the Startup class, but I can't figure out how to get at the request context from there.

Any ideas?

1
  • Mind if I borrow some of your content for my own post and change it to be specifically around the cookies collection? Great question BTW. Commented Sep 6, 2019 at 15:58

2 Answers 2

44

Working on this answer lead me to multiple answers. I think the first approach is what you are looking for, the second is a good alternative.

In order to configure multiple clients you can use named clients. These clients are registered as transient. Use DI to get the service that has access to the request context.

For that we need IHttpContextAccessor. In this case you don't have to register it yourself, because Identity already does that for you.

Otherwise add the following line in startup:

services.AddHttpContextAccessor();

Next we can configure the named client "github":

services.AddHttpClient("github", c =>
{
    // access the DI container
    var serviceProvider = services.BuildServiceProvider();
    // Find the HttpContextAccessor service
    var httpContextAccessor = serviceProvider.GetService<IHttpContextAccessor>();
    // Get the bearer token from the request context (header)
    var bearerToken = httpContextAccessor.HttpContext.Request
                          .Headers["Authorization"]
                          .FirstOrDefault(h => h.StartsWith("bearer ", StringComparison.InvariantCultureIgnoreCase));

    // Add authorization if found
    if (bearerToken != null)
        c.DefaultRequestHeaders.Add("Authorization", bearerToken);

     // Other settings
    c.BaseAddress = new Uri("https://api.github.com/");
    c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json"); // Github API versioning
    c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample"); // Github requires a user-agent
});

Call the client like this:

public class MyController : ControllerBase
{
    private readonly IHttpClientFactory _clientFactory;

    public MyController(IHttpClientFactory clientFactory)
    {
        _clientFactory = clientFactory;
    }

    public async Task<ActionResult> StartCall()
    {
        var client = _clientFactory.CreateClient("github");
        var response = await client.GetAsync("/repos/aspnet/docs/issues");
    }
}

Another option is to use Typed clients. Here's a short example. For a full example check the link.

Register IHttpContextAccessor:

services.AddHttpContextAccessor();

Create a typed client. I've added two options to add settings. One through the request context and one through a singleton class:

public class GitHubService
{
    public HttpClient Client { get; }

    public GitHubService(HttpClient client, HttpClientSettings httpClientSettings, IHttpContextAccessor httpContextAccessor)
    {
        var bearerToken = httpContextAccessor.HttpContext.Request
                              .Headers["Authorization"]
                              .FirstOrDefault(h => h.StartsWith("bearer ", StringComparison.InvariantCultureIgnoreCase));

        // Add authorization if found
        if (bearerToken != null)
            client.DefaultRequestHeaders.Add("Authorization", bearerToken);

        // Or the value from httpClientSettings:
        client.DefaultRequestHeaders.Add("Authorization", httpClientSettings.BearerToken);

        client.BaseAddress = new Uri("https://api.github.com/");
        client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json"); // GitHub API versioning
        client.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample"); // GitHub requires a user-agent

        Client = client;
    }
}

Register the client:

// The typed client is registered as transient with DI.
services.AddHttpClient<GitHubService>();

Please note, the code below is just an example. Since the token can't be persisted in the client, you can use the shared HttpClientSettings instead:

services.AddSingleton<HttpClientSettings>();

Where HttpClientSettings is:

public class HttpClientSettings
{
    public string BearerToken { get; set; }
}

You can use the client like this:

public class MyController : ControllerBase
{
    private readonly GitHubService _gitHubService;

    public MyController(GitHubService gitHubService)
    {
        _gitHubService = gitHubService;
    }

    public async Task<ActionResult> StartCall()
    {
        var response = await _gitHubService.Client.GetAsync("/repos/aspnet/docs/issues");

    }
}
Sign up to request clarification or add additional context in comments.

8 Comments

You are on the right track, but missing access to the current request context, which is what I believe the OP wants
My example assumes that somewhere the token is copied from the request context to the HttpClientSettings.
@RuardvanElburg ...and that's the half of the problem I'm struggling with. It would be wonderful if you could extend your answer, outlining a good approach for achieving that as well :)
@TomasLycken I was wondering, does this answer help you or is something still missing?
I want to add that services.BuildServiceProvider() creates a new "service pool" on each call. So the singleton services you get with this service provider in the AddHttpClient method won't be the same as the ones you get in the rest of the application. In this case you'd get the "right" service provider as a parameter in the AddHttpClient method like this: services.AddHttpClient("github", (serviceProvider, c) =>.
|
10

Since .NET Core 3.0 you can use HeaderPropagation.

ConfigureServices in Startup.cs

services.AddHeaderPropagation(o =>
        {
            o.Headers.Add("Authorization");
        });
services.AddHttpClient<YourTypedHttpClient>().AddHeaderPropagation();

Configure in Startup.cs

app.UseHeaderPropagation();

And this will automatically propagate Authorization header. You can also use it for other headers as well.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.