IHttpClientFactory com .NET

Neste artigo, você aprenderá a usar a IHttpClientFactory interface para criar HttpClient tipos com vários fundamentos do .NET, como DI (injeção de dependência), registro em log e configuração. O HttpClient tipo foi introduzido no .NET Framework 4.5, que foi lançado em 2012. Ou seja, já existe há algum tempo. HttpClient é usado para fazer solicitações HTTP e manipular respostas HTTP de recursos da Web identificados por um Uriarquivo . O protocolo HTTP constitui a grande maioria de todo o tráfego da Internet.

Com princípios modernos de desenvolvimento de aplicativos orientando as práticas recomendadas, o IHttpClientFactory serve como uma abstração de fábrica que pode criar HttpClient instâncias com configurações personalizadas. IHttpClientFactory foi introduzido no .NET Core 2.1. Cargas de trabalho .NET comuns baseadas em HTTP podem tirar proveito do middleware de terceiros resiliente e de tratamento de falhas transitórias com facilidade.

Nota

Se o seu aplicativo requer cookies, talvez seja melhor evitar o uso IHttpClientFactory no seu aplicativo. Para obter formas alternativas de gerenciar clientes, consulte Diretrizes para usar clientes HTTP.

Importante

O gerenciamento do tempo de vida das HttpClient instâncias criadas por IHttpClientFactory é completamente diferente das instâncias criadas manualmente. As estratégias são usar clientes de curta duração criados por IHttpClientFactory ou clientes de longa vida com PooledConnectionLifetime configuração. Para obter mais informações, consulte a seção Gerenciamento do tempo de vida do HttpClient e Diretrizes para o uso de clientes HTTP.

O IHttpClientFactory tipo

Todo o código-fonte de exemplo fornecido neste artigo requer a instalação do Microsoft.Extensions.Http pacote NuGet. Além disso, os exemplos de código demonstram o uso de solicitações HTTP GET para recuperar objetos de usuário Todo da API de espaço reservado {JSON} livre.

Ao chamar qualquer um dos métodos de AddHttpClient extensão, você está adicionando os IHttpClientFactory serviços relacionados ao IServiceCollection. O IHttpClientFactory tipo oferece os seguintes benefícios:

  • Expõe a HttpClient classe como um tipo DI-ready.
  • Fornece um local central para nomear e configurar instâncias lógicas HttpClient .
  • Codifica o conceito de middleware de saída através da delegação de manipuladores em HttpClient.
  • Fornece métodos de extensão para middleware baseado em Polly para tirar proveito da delegação de manipuladores no HttpClient.
  • Gerencia o cache e o tempo de vida das instâncias subjacentes HttpClientHandler . O gerenciamento automático evita problemas comuns de DNS (Sistema de Nomes de Domínio) que ocorrem ao gerenciar manualmente os tempos de HttpClient vida.
  • Adiciona uma experiência de registro configurável (via ILogger) para todas as solicitações enviadas através de clientes criados pela fábrica.

Padrões de consumo

Há várias maneiras de IHttpClientFactory usar em um aplicativo:

A melhor abordagem depende dos requisitos do aplicativo.

Utilização básica

Para registar o IHttpClientFactory, ligue AddHttpClientpara:

using Shared;
using BasicHttp.Example;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Services.AddHttpClient();
builder.Services.AddTransient<TodoService>();

using IHost host = builder.Build();

O consumo de serviços pode exigir o IHttpClientFactory parâmetro as a constructor com DI. O código a seguir usa IHttpClientFactory para criar uma HttpClient instância:

using System.Net.Http.Json;
using System.Text.Json;
using Microsoft.Extensions.Logging;
using Shared;

namespace BasicHttp.Example;

public sealed class TodoService(
    IHttpClientFactory httpClientFactory,
    ILogger<TodoService> logger)
{
    public async Task<Todo[]> GetUserTodosAsync(int userId)
    {
        // Create the client
        using HttpClient client = httpClientFactory.CreateClient();
        
        try
        {
            // Make HTTP GET request
            // Parse JSON response deserialize into Todo types
            Todo[]? todos = await client.GetFromJsonAsync<Todo[]>(
                $"https://jsonplaceholder.typicode.com/todos?userId={userId}",
                new JsonSerializerOptions(JsonSerializerDefaults.Web));

            return todos ?? [];
        }
        catch (Exception ex)
        {
            logger.LogError("Error getting something fun to say: {Error}", ex);
        }

        return [];
    }
}

Usar IHttpClientFactory como no exemplo anterior é uma boa maneira de refatorar um aplicativo existente. Não tem impacto na forma como HttpClient é utilizado. Em locais onde HttpClient as instâncias são criadas em um aplicativo existente, substitua essas ocorrências por chamadas para CreateClient.

Clientes nomeados

Clientes nomeados são uma boa escolha quando:

  • O aplicativo requer muitos usos distintos do HttpClient.
  • Muitas HttpClient instâncias têm configurações diferentes.

A configuração de um nome HttpClient pode ser especificada durante o IServiceCollectionregistro no :

using Shared;
using NamedHttp.Example;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

string? httpClientName = builder.Configuration["TodoHttpClientName"];
ArgumentException.ThrowIfNullOrEmpty(httpClientName);

builder.Services.AddHttpClient(
    httpClientName,
    client =>
    {
        // Set the base address of the named client.
        client.BaseAddress = new Uri("https://jsonplaceholder.typicode.com/");

        // Add a user-agent default request header.
        client.DefaultRequestHeaders.UserAgent.ParseAdd("dotnet-docs");
    });

No código anterior, o cliente é configurado com:

  • Um nome que é retirado da configuração sob o "TodoHttpClientName".
  • O endereço https://jsonplaceholder.typicode.com/base .
  • Um "User-Agent" cabeçalho.

Você pode usar a configuração para especificar nomes de clientes HTTP, o que é útil para evitar nomes incorretos de clientes ao adicionar e criar. Neste exemplo, o arquivo appsettings.json é usado para configurar o nome do cliente HTTP:

{
    "TodoHttpClientName": "JsonPlaceholderApi"
}

É fácil estender essa configuração e armazenar mais detalhes sobre como você gostaria que seu cliente HTTP funcionasse. Para obter mais informações, consulte Configuração no .NET.

Criar cliente

Cada vez CreateClient é chamado:

  • Uma nova instância do HttpClient é criada.
  • A ação de configuração é chamada.

Para criar um cliente nomeado, passe seu nome para CreateClient:

using System.Net.Http.Json;
using System.Text.Json;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Shared;

namespace NamedHttp.Example;

public sealed class TodoService
{
    private readonly IHttpClientFactory _httpClientFactory = null!;
    private readonly IConfiguration _configuration = null!;
    private readonly ILogger<TodoService> _logger = null!;

    public TodoService(
        IHttpClientFactory httpClientFactory,
        IConfiguration configuration,
        ILogger<TodoService> logger) =>
        (_httpClientFactory, _configuration, _logger) =
            (httpClientFactory, configuration, logger);

    public async Task<Todo[]> GetUserTodosAsync(int userId)
    {
        // Create the client
        string? httpClientName = _configuration["TodoHttpClientName"];
        using HttpClient client = _httpClientFactory.CreateClient(httpClientName ?? "");

        try
        {
            // Make HTTP GET request
            // Parse JSON response deserialize into Todo type
            Todo[]? todos = await client.GetFromJsonAsync<Todo[]>(
                $"todos?userId={userId}",
                new JsonSerializerOptions(JsonSerializerDefaults.Web));

            return todos ?? [];
        }
        catch (Exception ex)
        {
            _logger.LogError("Error getting something fun to say: {Error}", ex);
        }

        return [];
    }
}

No código anterior, a solicitação HTTP não precisa especificar um nome de host. O código pode passar apenas o caminho, uma vez que o endereço base configurado para o cliente é usado.

Clientes digitados

Clientes digitados:

  • Forneça os mesmos recursos que os clientes nomeados sem a necessidade de usar cadeias de caracteres como chaves.
  • Forneça ajuda do IntelliSense e do compilador ao consumir clientes.
  • Forneça um único local para configurar e interagir com um determinado HttpClient. Por exemplo, um único cliente tipado pode ser usado:
    • Para um único ponto de extremidade de back-end.
    • Para encapsular toda a lógica que lida com o ponto de extremidade.
  • Trabalhe com DI e pode ser injetado quando necessário no aplicativo.

Um cliente tipado aceita um HttpClient parâmetro em seu construtor:

using System.Net.Http.Json;
using System.Text.Json;
using Microsoft.Extensions.Logging;
using Shared;

namespace TypedHttp.Example;

public sealed class TodoService(
    HttpClient httpClient,
    ILogger<TodoService> logger) : IDisposable
{
    public async Task<Todo[]> GetUserTodosAsync(int userId)
    {
        try
        {
            // Make HTTP GET request
            // Parse JSON response deserialize into Todo type
            Todo[]? todos = await httpClient.GetFromJsonAsync<Todo[]>(
                $"todos?userId={userId}",
                new JsonSerializerOptions(JsonSerializerDefaults.Web));

            return todos ?? [];
        }
        catch (Exception ex)
        {
            logger.LogError("Error getting something fun to say: {Error}", ex);
        }

        return [];
    }

    public void Dispose() => httpClient?.Dispose();
}

No código anterior:

  • A configuração é definida quando o cliente digitado é adicionado à coleção de serviços.
  • O HttpClient é atribuído como uma variável de escopo de classe (campo) e usado com APIs expostas.

Métodos específicos de API podem ser criados para expor HttpClient a funcionalidade. Por exemplo, o GetUserTodosAsync método encapsula código para recuperar objetos específicos Todo do usuário.

O código a seguir chama AddHttpClient para registrar uma classe de cliente digitada:

using Shared;
using TypedHttp.Example;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Services.AddHttpClient<TodoService>(
    client =>
    {
        // Set the base address of the typed client.
        client.BaseAddress = new Uri("https://jsonplaceholder.typicode.com/");

        // Add a user-agent default request header.
        client.DefaultRequestHeaders.UserAgent.ParseAdd("dotnet-docs");
    });

O cliente digitado é registrado como transitório com DI. No código anterior, AddHttpClient registra-se TodoService como um serviço transitório. Este registo utiliza um método de fábrica para:

  1. Crie uma instância de HttpClient.
  2. Crie uma instância de TodoService, passando a instância de HttpClient para seu construtor.

Importante

Usar clientes digitados em serviços singleton pode ser perigoso. Para obter mais informações, consulte a seção Evitar clientes digitados em serviços singleton.

Nota

Ao registrar um cliente tipado com o AddHttpClient<TClient> método, o TClient tipo deve ter um construtor que aceite um HttpClient como parâmetro. Além disso, o TClient tipo não deve ser registrado com o contêiner DI separadamente, pois isso levará ao registro posterior substituindo o primeiro.

Clientes gerados

IHttpClientFactory pode ser usado em combinação com bibliotecas de terceiros, como o Refit. Refit é uma biblioteca REST para .NET. Ele permite definições declarativas de API REST, mapeando métodos de interface para pontos de extremidade. Uma implementação da interface é gerada dinamicamente pelo RestService, usando HttpClient para fazer as chamadas HTTP externas.

Considere o seguinte record tipo:

namespace Shared;

public record class Todo(
    int UserId,
    int Id,
    string Title,
    bool Completed);

O exemplo a seguir depende do Refit.HttpClientFactory pacote NuGet e é uma interface simples:

using Refit;
using Shared;

namespace GeneratedHttp.Example;

public interface ITodoService
{
    [Get("/todos?userId={userId}")]
    Task<Todo[]> GetUserTodosAsync(int userId);
}

A interface C# anterior:

  • Define um método chamado GetUserTodosAsync que retorna uma Task<Todo[]> instância.
  • Declara um Refit.GetAttribute atributo com o caminho e a cadeia de caracteres de consulta para a API externa.

Um cliente tipado pode ser adicionado, usando o Refit para gerar a implementação:

using GeneratedHttp.Example;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Refit;
using Shared;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Services.AddRefitClient<ITodoService>()
    .ConfigureHttpClient(client =>
    {
        // Set the base address of the named client.
        client.BaseAddress = new Uri("https://jsonplaceholder.typicode.com/");

        // Add a user-agent default request header.
        client.DefaultRequestHeaders.UserAgent.ParseAdd("dotnet-docs");
    });

A interface definida pode ser consumida quando necessário, com a implementação fornecida pela DI e Refit.

Fazer solicitações POST, PUT e DELETE

Nos exemplos anteriores, todas as solicitações HTTP usam o verbo GET HTTP. HttpClient também suporta outros verbos HTTP, incluindo:

  • POST
  • PUT
  • DELETE
  • PATCH

Para obter uma lista completa dos verbos HTTP suportados, consulte HttpMethod. Para obter mais informações sobre como fazer solicitações HTTP, consulte Enviar uma solicitação usando HttpClient.

O exemplo a seguir mostra como fazer uma solicitação HTTP POST :

public async Task CreateItemAsync(Item item)
{
    using StringContent json = new(
        JsonSerializer.Serialize(item, new JsonSerializerOptions(JsonSerializerDefaults.Web)),
        Encoding.UTF8,
        MediaTypeNames.Application.Json);

    using HttpResponseMessage httpResponse =
        await httpClient.PostAsync("/api/items", json);

    httpResponse.EnsureSuccessStatusCode();
}

No código anterior, o CreateItemAsync método:

  • Serializa o Item parâmetro para JSON usando System.Text.Json. Isso usa uma instância de para configurar o processo de JsonSerializerOptions serialização.
  • Cria uma instância de para empacotar o JSON serializado StringContent para enviar no corpo da solicitação HTTP.
  • Chamadas PostAsync para enviar o conteúdo JSON para a URL especificada. Esta é uma URL relativa que é adicionada ao HttpClient.BaseAddress.
  • Chamadas EnsureSuccessStatusCode para lançar uma exceção se o código de status da resposta não indicar êxito.

HttpClient também suporta outros tipos de conteúdo. Por exemplo, MultipartContent e StreamContent. Para obter uma lista completa do conteúdo suportado, consulte HttpContent.

O exemplo a seguir mostra uma solicitação HTTP PUT :

public async Task UpdateItemAsync(Item item)
{
    using StringContent json = new(
        JsonSerializer.Serialize(item, new JsonSerializerOptions(JsonSerializerDefaults.Web)),
        Encoding.UTF8,
        MediaTypeNames.Application.Json);

    using HttpResponseMessage httpResponse =
        await httpClient.PutAsync($"/api/items/{item.Id}", json);

    httpResponse.EnsureSuccessStatusCode();
}

O código anterior é muito semelhante ao POST exemplo. O UpdateItemAsync método chama PutAsync em vez de PostAsync.

O exemplo a seguir mostra uma solicitação HTTP DELETE :

public async Task DeleteItemAsync(Guid id)
{
    using HttpResponseMessage httpResponse =
        await httpClient.DeleteAsync($"/api/items/{id}");

    httpResponse.EnsureSuccessStatusCode();
}

No código anterior, o DeleteItemAsync método chama DeleteAsync. Como as solicitações HTTP DELETE normalmente não contêm corpo, o DeleteAsync método não fornece uma sobrecarga que aceita uma instância de HttpContent.

Para saber mais sobre como usar diferentes verbos HTTP com HttpCliento , consulte HttpClient.

HttpClient gestão do tempo de vida

Uma nova HttpClient instância é retornada sempre que CreateClient é chamada no IHttpClientFactory. Uma HttpClientHandler instância é criada por nome de cliente. A fábrica gerencia o tempo de vida das HttpClientHandler instâncias.

IHttpClientFactory Armazena em cache as HttpClientHandler instâncias criadas pela fábrica para reduzir o consumo de recursos. Uma HttpClientHandler instância pode ser reutilizada do cache ao criar uma nova HttpClient instância se seu tempo de vida não tiver expirado.

O armazenamento em cache de manipuladores é desejável, pois cada manipulador normalmente gerencia seu próprio pool de conexões HTTP subjacente. Criar mais manipuladores do que o necessário pode resultar em exaustão do soquete e atrasos de conexão. Alguns manipuladores também mantêm as conexões abertas indefinidamente, o que pode impedir que o manipulador reaja às alterações de DNS.

O tempo de vida padrão do manipulador é de dois minutos. Para substituir o valor padrão, chame SetHandlerLifetime cada cliente, no IServiceCollection:

services.AddHttpClient("Named.Client")
    .SetHandlerLifetime(TimeSpan.FromMinutes(5));

Importante

HttpClient As instâncias criadas por IHttpClientFactory destinam-se a ser de curta duração.

  • Reciclar e recriar HttpMessageHandler's quando sua vida útil expira é essencial para IHttpClientFactory garantir que os manipuladores reajam às alterações de DNS. HttpClient está vinculado a uma instância de manipulador específica após sua criação, portanto, novas HttpClient instâncias devem ser solicitadas em tempo hábil para garantir que o cliente obtenha o manipulador atualizado.

  • O descarte de tais HttpClient instâncias criadas pela fábrica não levará ao esgotamento da tomada, pois seu descarte não desencadeará o HttpMessageHandlerdescarte do . IHttpClientFactory rastreia e elimina os recursos usados para criar HttpClient instâncias, especificamente as HttpMessageHandler instâncias, assim que sua vida útil expira e não HttpClient há mais uso.

Manter uma única HttpClient instância ativa por um longo período é um padrão comum que pode ser usado como uma alternativa ao IHttpClientFactory, no entanto, esse padrão requer configuração adicional, como PooledConnectionLifetime. Você pode usar clientes de longa duração com PooledConnectionLifetime, ou clientes de curta duração criados pela IHttpClientFactory. Para obter informações sobre qual estratégia usar em seu aplicativo, consulte Diretrizes para usar clientes HTTP.

Configure o HttpMessageHandler

Pode ser necessário controlar a configuração do interior HttpMessageHandler utilizado por um cliente.

Um IHttpClientBuilder é retornado ao adicionar clientes nomeados ou digitados. O ConfigurePrimaryHttpMessageHandler método extension pode ser usado para definir um delegado no IServiceCollection. O delegado é usado para criar e configurar o primário HttpMessageHandler usado por esse cliente:

.ConfigurePrimaryHttpMessageHandler(() =>
{
    return new HttpClientHandler
    {
        AllowAutoRedirect = false,
        UseDefaultCredentials = true
    };
});

Configurar o HttClientHandler permite especificar um proxy para a HttpClient instância entre várias outras propriedades do manipulador. Para obter mais informações, consulte Proxy por cliente.

Configuração adicional

Existem várias opções de configuração adicionais para controlar o IHttpClientHandler:

Método Description
AddHttpMessageHandler Adiciona um manipulador de mensagens adicional para um arquivo HttpClient.
AddTypedClient Configura a associação entre o TClient e o nomeado HttpClient associado ao IHttpClientBuilder.
ConfigureHttpClient Adiciona um delegado que será usado para configurar um nome HttpClient.
ConfigurePrimaryHttpMessageHandler Configura o primário HttpMessageHandler do contêiner de injeção de dependência para um arquivo HttpClient.
RedactLoggedHeaders Define a coleção de nomes de cabeçalho HTTP para os quais os valores devem ser editados antes do registro.
SetHandlerLifetime Define o período de tempo durante o qual uma HttpMessageHandler instância pode ser reutilizada. Cada cliente nomeado pode ter seu próprio valor de vida útil do manipulador configurado.
UseSocketsHttpHandler Configura uma instância nova ou adicionada SocketsHttpHandler anteriormente do contêiner de injeção de dependência para ser usada como um manipulador primário para um arquivo HttpClient. (Somente .NET 5+)

Usando IHttpClientFactory junto com SocketsHttpHandler

A SocketsHttpHandler implementação do foi adicionada HttpMessageHandler no .NET Core 2.1, que permite PooledConnectionLifetime ser configurado. Essa configuração é usada para garantir que o manipulador reaja às alterações de DNS, portanto, o uso SocketsHttpHandler é considerado uma alternativa ao uso IHttpClientFactorydo . Para obter mais informações, consulte Diretrizes para usar clientes HTTP.

No entanto, SocketsHttpHandler e IHttpClientFactory pode ser usado em conjunto melhorar a configurabilidade. Ao usar ambas as APIs, você se beneficia da configurabilidade em um nível baixo (por exemplo, usando LocalCertificateSelectionCallback para seleção dinâmica de certificados) e alto nível (por exemplo, aproveitando a integração DI e várias configurações de cliente).

Para usar ambas as APIs:

  1. Especifique SocketsHttpHandler como PrimaryHandler via ConfigurePrimaryHttpMessageHandler, ou UseSocketsHttpHandler (somente .NET 5+).
  2. SocketsHttpHandler.PooledConnectionLifetime Configure com base no intervalo que você espera que o DNS seja atualizado, por exemplo, para um valor que estava anteriormente em HandlerLifetime.
  3. (Opcional) Uma vez que SocketsHttpHandler irá lidar com o pool de conexões e reciclagem, a reciclagem do IHttpClientFactory manipulador no nível não é mais necessária. Você pode desativá-lo definindo HandlerLifetime como Timeout.InfiniteTimeSpan.
services.AddHttpClient(name)
    .UseSocketsHttpHandler((handler, _) =>
        handler.PooledConnectionLifetime = TimeSpan.FromMinutes(2)) // Recreate connection every 2 minutes
    .SetHandlerLifetime(Timeout.InfiniteTimeSpan); // Disable rotation, as it is handled by PooledConnectionLifetime

No exemplo acima, 2 minutos foram escolhidos arbitrariamente para fins de ilustração, alinhando-se a um valor padrão HandlerLifetime . Você deve escolher o valor com base na frequência esperada de DNS ou outras alterações de rede. Para obter mais informações, consulte a seção Comportamento do HttpClient DNS nas diretrizes e a seção Comentários na documentação da PooledConnectionLifetime API.

Evite clientes digitados em serviços singleton

Ao usar a abordagem de cliente nomeado, IHttpClientFactory é injetado em serviços, e HttpClient as instâncias são criadas chamando CreateClient sempre que é HttpClient necessário.

No entanto, com a abordagem de cliente digitado, os clientes digitados são objetos transitórios geralmente injetados em serviços. Isso pode causar um problema porque um cliente digitado pode ser injetado em um serviço singleton.

Importante

Espera-se que os clientes tipados sejam de curta duração no mesmo sentido HttpClient das instâncias criadas por IHttpClientFactory (para obter mais informações, consulte HttpClient Gerenciamento de tempo de vida). Assim que uma instância de cliente tipada é criada, IHttpClientFactory não tem controle sobre ela. Se uma instância de cliente tipada for capturada em um singleton, ela poderá impedir que ela reaja às alterações de DNS, derrotando uma das finalidades do IHttpClientFactory.

Se você precisar usar HttpClient instâncias em um serviço singleton, considere as seguintes opções:

  • Em vez disso, use a abordagem de cliente nomeado, injetando IHttpClientFactory o serviço singleton e recriando HttpClient instâncias quando necessário.
  • Se você precisar da abordagem de cliente digitada, use SocketsHttpHandler com configurado PooledConnectionLifetime como um manipulador primário. Para obter mais informações sobre como usar SocketsHttpHandler com IHttpClientFactoryo , consulte a seção Usando IHttpClientFactory junto com SocketsHttpHandler.

Escopos do manipulador de mensagens em IHttpClientFactory

IHttpClientFactory cria um escopo de DI separado para cada HttpMessageHandler instância. Esses escopos DI são separados dos escopos DI do aplicativo (por exemplo, ASP.NET escopo de solicitação de entrada ou um escopo DI manual criado pelo usuário), portanto, eles não compartilharão instâncias de serviço com escopo. Os escopos do manipulador de mensagens estão vinculados ao tempo de vida do manipulador e podem sobreviver aos escopos do aplicativo, o que pode levar, por exemplo, à reutilização da mesma HttpMessageHandler instância com as mesmas dependências de escopo injetadas entre várias solicitações de entrada.

Diagrama mostrando dois escopos DI do aplicativo e um escopo separado do manipulador de mensagens

Os usuários são fortemente aconselhados a não armazenar em cache informações relacionadas ao escopo (como dados de ) dentro HttpMessageHandler de instâncias e usar dependências com escopo com cuidado para evitar o vazamento de HttpContextinformações confidenciais.

Se você precisar de acesso a um escopo de DI de aplicativo do seu manipulador de mensagens, para autenticação como exemplo, você encapsularia a lógica com reconhecimento de escopo em um transitório DelegatingHandlerseparado e a encapsularia em torno de uma HttpMessageHandler instância do IHttpClientFactory cache. Para acessar a chamada IHttpMessageHandlerFactory.CreateHandler do manipulador para qualquer cliente nomeado registrado. Nesse caso, você mesmo criaria uma HttpClient instância usando o manipulador construído.

Diagrama mostrando como obter acesso aos escopos DI do aplicativo por meio de um manipulador de mensagens transitórias separado e IHttpMessageHandlerFactory

O exemplo a seguir mostra a criação de um HttpClient com reconhecimento de DelegatingHandlerescopo:

if (scopeAwareHandlerType != null)
{
    if (!typeof(DelegatingHandler).IsAssignableFrom(scopeAwareHandlerType))
    {
        throw new ArgumentException($"""
            Scope aware HttpHandler {scopeAwareHandlerType.Name} should
            be assignable to DelegatingHandler
            """);
    }

    // Create top-most delegating handler with scoped dependencies
    scopeAwareHandler = (DelegatingHandler)_scopeServiceProvider.GetRequiredService(scopeAwareHandlerType); // should be transient
    if (scopeAwareHandler.InnerHandler != null)
    {
        throw new ArgumentException($"""
            Inner handler of a delegating handler {scopeAwareHandlerType.Name} should be null.
            Scope aware HttpHandler should be registered as Transient.
            """);
    }
}

// Get or create HttpMessageHandler from HttpClientFactory
HttpMessageHandler handler = _httpMessageHandlerFactory.CreateHandler(name);

if (scopeAwareHandler != null)
{
    scopeAwareHandler.InnerHandler = handler;
    handler = scopeAwareHandler;
}

HttpClient client = new(handler);

Uma outra solução alternativa pode ser seguida com um método de extensão para registrar um registro padrão com reconhecimento de DelegatingHandler escopo e substituir IHttpClientFactory por um serviço transitório com acesso ao escopo atual do aplicativo:

public static IHttpClientBuilder AddScopeAwareHttpHandler<THandler>(
    this IHttpClientBuilder builder) where THandler : DelegatingHandler
{
    builder.Services.TryAddTransient<THandler>();
    if (!builder.Services.Any(sd => sd.ImplementationType == typeof(ScopeAwareHttpClientFactory)))
    {
        // Override default IHttpClientFactory registration
        builder.Services.AddTransient<IHttpClientFactory, ScopeAwareHttpClientFactory>();
    }

    builder.Services.Configure<ScopeAwareHttpClientFactoryOptions>(
        builder.Name, options => options.HttpHandlerType = typeof(THandler));

    return builder;
}

Para obter mais informações, consulte o exemplo completo.

Consulte também