Настройте HttpClientFactory для использования данных из текущего контекста запроса

С новым HttpClientFactory в ASP.NET Core 2.1 довольно легко настраивать настраиваемые HTTP-клиенты с такими вещами, как базовые URL-адреса, заголовки по умолчанию и т. д.

Однако я не нашел способа централизовать конфигурацию, который позволяет мне вставлять заголовки из текущего контекста запроса. Например, рассмотрим службу, вызываемую с заголовком Authorization, который я также хочу передать всем базовым службам. Было бы здорово иметь возможность настроить это в .AddHttpClient() вызове services в классе Startup, но я не могу понять, как получить оттуда контекст запроса.

Любые идеи?


person Tomas Aschan    schedule 16.07.2018    source источник
comment
Не возражаете, если я позаимствую часть вашего контента для своего поста и изменю его так, чтобы он относился к сбору файлов cookie? Кстати, отличный вопрос.   -  person Stephen Patten    schedule 06.09.2019


Ответы (2)


Работа над этим ответом привела меня к нескольким ответам. Я думаю, что первый подход - это то, что вы ищете, второй - хорошая альтернатива.

Чтобы настроить несколько клиентов, вы можете использовать назвал клиентов. Эти клиенты зарегистрированы как временные. Используйте DI, чтобы получить службу, имеющую доступ к контексту запроса.

Для этого нам понадобится IHttpContextAccessor. В этом случае вам не нужно регистрировать его самостоятельно, потому что Identity уже делает это за вас.

В противном случае добавьте следующую строку в автозагрузку:

services.AddHttpContextAccessor();

Затем мы можем настроить названный клиент «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
});

Вызовите клиента так:

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");
    }
}

Другой вариант - использовать Типизированные клиенты. Вот небольшой пример. Для получения полного примера проверьте ссылка.

Зарегистрируйте IHttpContextAccessor:

services.AddHttpContextAccessor();

Создайте типизированного клиента. Я добавил две опции для добавления настроек. Один через контекст запроса и один через одноэлементный класс:

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;
    }
}

Зарегистрируйте клиента:

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

Обратите внимание, что приведенный ниже код является лишь примером. Поскольку токен не может быть сохранен в клиенте, вы можете вместо этого использовать общий HttpClientSettings:

services.AddSingleton<HttpClientSettings>();

Где HttpClientSettings:

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

Вы можете использовать клиент так:

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");

    }
}
person Ruard van Elburg    schedule 21.07.2018
comment
Вы на правильном пути, но у вас отсутствует доступ к текущему контексту запроса, что, как я считаю, хочет OP. - person Nkosi; 22.07.2018
comment
В моем примере предполагается, что где-то токен копируется из контекста запроса в HttpClientSettings. - person Ruard van Elburg; 22.07.2018
comment
@RuardvanElburg ... и это половина проблемы, с которой я борюсь. Было бы замечательно, если бы вы могли расширить свой ответ, обрисовав в общих чертах хороший подход для достижения этого :) - person Tomas Aschan; 23.07.2018
comment
@TomasLycken Мне было интересно, поможет ли вам этот ответ или чего-то еще не хватает? - person Ruard van Elburg; 26.07.2018
comment
@RuardvanElburg: Очень хорошо, спасибо! Я просто не заметил твоих правок :) - person Tomas Aschan; 27.07.2018
comment
У меня есть 3 разных базовых адреса. И на основе параметра, переданного из jquery, мне нужно вызвать удаленный API. Как я могу использовать HttpClientFactory для достижения желаемой функциональности? - person Priya; 14.11.2018
comment
Я хочу добавить, что services.BuildServiceProvider() создает новый пул служб при каждом вызове. Таким образом, одноэлементные службы, которые вы получаете от этого поставщика услуг в методе AddHttpClient, не будут такими же, как те, которые вы получаете в остальной части приложения. В этом случае вы получите нужного поставщика услуг в качестве параметра в методе AddHttpClient, например: services.AddHttpClient("github", (serviceProvider, c) =>. - person Pascal R.; 16.08.2019

Начиная с .NET Core 3.0 вы можете использовать HeaderPropagation.

ConfigureServices в Startup.cs

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

Настроить в Startup.cs

app.UseHeaderPropagation();

И это автоматически распространит заголовок авторизации. Вы также можете использовать его для других заголовков.

person wast    schedule 21.07.2021