Sdílet prostřednictvím


IHttpClientFactory v kombinaci s .NET

V tomto článku se dozvíte, jak pomocí IHttpClientFactory rozhraní vytvářet HttpClient typy s různými základy .NET, jako je injektáž závislostí (DI), protokolování a konfigurace. Typ HttpClient byl zaveden v rozhraní .NET Framework 4.5, který byl vydán v roce 2012. Jinými slovy, je to už nějakou dobu. HttpClient slouží k vytváření požadavků HTTP a zpracování odpovědí HTTP z webových prostředků identifikovaných pomocí Uri. Protokol HTTP tvoří velkou většinu veškerého internetového provozu.

Díky moderním principům vývoje aplikací, které určují osvědčené postupy, IHttpClientFactory slouží jako tovární abstrakce, jež může vytvářet instance HttpClient s vlastními konfiguracemi. IHttpClientFactory byla zavedena v .NET Core 2.1. Běžné úlohy .NET založené na protokolu HTTP můžou snadno využívat odolný a přechodný middleware pro zpracování chyb třetích stran.

Výstraha

Pokud vaše aplikace vyžaduje cookies, doporučuje se vyhnout se použití IHttpClientFactory. Sdružování HttpMessageHandler instancí vede ke sdílení CookieContainer objektů. Nepředvídané CookieContainer sdílení může vést k úniku souborů cookie mezi nesouvisejícími částmi aplikace. Navíc, když HandlerLifetime vyprší, zprostředkovatel se recykluje, což znamená, že všechny cookies uložené v jeho CookieContainer se ztratí. Alternativní způsoby správy klientů najdete v tématu Pokyny pro používání klientů HTTP.

Důležité

Správa životnosti instancí vytvořených HttpClientIHttpClientFactory se zcela liší od instancí vytvořených ručně. Strategie jsou buď použití krátkodobých klientů vytvořených IHttpClientFactory, nebo dlouhodobých klientů s PooledConnectionLifetime nastavením. Další informace najdete v části Správa životnosti HttpClient a Pokyny pro používání HTTP klientů.

Typ IHttpClientFactory

Veškerý ukázkový zdrojový kód uvedený v tomto článku vyžaduje instalaci Microsoft.Extensions.Http balíčku NuGet. Příklady kódu navíc ukazují použití požadavků HTTP GET k načtení uživatelských Todo objektů z bezplatného zástupného rozhraní API {JSON}.

Když voláte libovolnou z metod rozšíření AddHttpClient, přidáváte IHttpClientFactory a související služby do IServiceCollection. Typ IHttpClientFactory nabízí následující výhody:

  • Vystavuje třídu HttpClient jako typ připravený pro DI-ready.
  • Poskytuje centrální umístění pro pojmenování a konfiguraci logických instancí HttpClient.
  • Kodifikuje koncept odchozího middlewaru prostřednictvím delegování obslužných rutin v HttpClient.
  • Poskytuje rozšiřující metody pro Polly založený middleware, které využívají delegování obslužných rutin v HttpClient.
  • Spravuje ukládání do mezipaměti a životnost základních HttpClientHandler instancí. Automatická správa zabraňuje běžným problémům DNS (Domain Name System), ke kterým dochází při ruční správě HttpClient životnosti.
  • Přidá konfigurovatelné záznamování (prostřednictvím ILogger) pro všechny požadavky odeslané prostřednictvím klientů vytvořených v továrně.

Spotřební vzorce

V aplikaci můžete použít několik způsobů IHttpClientFactory :

Nejlepší přístup závisí na požadavcích aplikace.

Základní použití

Chcete-li zaregistrovat IHttpClientFactory, zavolejte AddHttpClient:

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

Využívání služeb může vyžadovat parametr konstruktoru IHttpClientFactory s DI. Následující kód používá IHttpClientFactory k vytvoření HttpClient instance:

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

Použití IHttpClientFactory podobné jako v předchozím příkladu je dobrým způsobem, jak refaktorovat existující aplikaci. Nemá žádný vliv na způsob HttpClient použití. V místech, kde se instance HttpClient vytvářejí v existující aplikaci, nahraďte výskyty voláním CreateClient.

Pojmenovaní klienti

Pojmenovaní klienti jsou dobrou volbou, když:

  • Aplikace vyžaduje mnoho různých použití HttpClient.
  • Mnoho HttpClient instancí má různé konfigurace.

Konfiguraci pro pojmenovaný HttpClient lze zadat během registrace na IServiceCollection.

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

V předchozím kódu je klient nakonfigurovaný takto:

  • Název, který je vytáhnut z konfigurace pod "TodoHttpClientName".
  • Základní adresa https://jsonplaceholder.typicode.com/.
  • Záhlaví "User-Agent".

Konfiguraci můžete použít k určení názvů klientů HTTP, což je užitečné, abyste se vyhnuli nesprávnému přejmenování klientů při přidávání a vytváření. V tomto příkladu se k konfiguraci názvu klienta HTTP používá soubor appsettings.json :

{
    "TodoHttpClientName": "JsonPlaceholderApi"
}

Tuto konfiguraci můžete snadno rozšířit a uložit další podrobnosti o tom, jak má klient HTTP fungovat. Další informace naleznete v tématu Konfigurace v .NET.

Vytvoření klienta

CreateClient je volán pokaždé:

  • Vytvoří se nová instance HttpClient .
  • Akce konfigurace je vyvolána.

Pokud chcete vytvořit pojmenovaného klienta, předejte jeho název do 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"];
        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 [];
    }
}

V předchozím kódu požadavek HTTP nemusí zadávat název hostitele. Kód může předat pouze cestu, protože se používá základní adresa nakonfigurovaná pro klienta.

Typoví klienti

Typoví klienti:

  • Poskytněte stejné možnosti jako pojmenovaní klienti bez nutnosti používat řetězce jako klíče.
  • Poskytněte nápovědu IntelliSense a pomoc kompilátoru při používání klienty.
  • Zadejte jedno umístění pro konfiguraci a interakci s určitým HttpClient. Může se například použít jeden typ klienta:
    • Pro jeden backendový koncový bod.
    • Zapouzdřit veškerou logiku, která se zabývá koncovými body.
  • Pracujte s DI a může být implementováno tam, kde je to vyžadováno v aplikaci.

Typovaný klient přijímá parametr HttpClient v jeho konstruktoru.

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)
{
    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 [];
    }
}

V předchozím kódu:

  • Konfigurace se nastaví při přidání zadaného klienta do kolekce služeb.
  • HttpClient je přiřazena jako proměnná ve třídním rozsahu (pole) a používá se s vystavenými API.

Metody specifické pro rozhraní API je možné vytvořit, které zpřístupňují HttpClient funkce. Například metoda GetUserTodosAsync zapouzdřuje kód pro načtení uživatelsky specifických objektů Todo.

Následující kód volá AddHttpClient k registraci typové třídy klienta:

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

Zadaný klient je zaregistrovaný jako přechodný s DI. V předchozím kódu AddHttpClient se zaregistruje TodoService jako přechodná služba. Tato registrace používá tovární metodu k:

  1. Vytvořte instanci HttpClient.
  2. Vytvořte instanci TodoService, přičemž předáte instanci HttpClient do jejího konstruktoru.

Důležité

Používání typových klientů v singletonových službách může být nebezpečné. Další informace najdete v části Vyhnout se typed klientům v singleton services.

Poznámka:

Při registraci zadaného klienta metodou AddHttpClient<TClient> typu TClient musí mít typ konstruktor, který přijímá jako parametr HttpClient. Kromě toho by se typ TClient neměl registrovat u kontejneru DI samostatně, protože to povede k pozdější registraci přepisující předchozí.

Vygenerované klienty

IHttpClientFactory lze použít v kombinaci s knihovnami třetích stran, jako je například Refit. Refit je knihovna REST pro .NET. Umožňuje deklarativní definice rozhraní REST API, metody mapování rozhraní na koncové body. Implementace rozhraní je generována dynamicky pomocí RestService, který využívá HttpClient k provedení externích volání HTTP.

Zvažte následující record typ:

namespace Shared;

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

Následující příklad spoléhá na Refit.HttpClientFactory balíček NuGet a je to jednoduché rozhraní:

using Refit;
using Shared;

namespace GeneratedHttp.Example;

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

Předchozí rozhraní jazyka C#:

  • Definuje metodu s názvem GetUserTodosAsync , která vrací Task<Todo[]> instanci.
  • Deklaruje Refit.GetAttribute atribut s cestou a řetězcem dotazu pro externí rozhraní API.

Typový klient je možné přidat pomocí nástroje Refit k vygenerování implementace:

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

Definované rozhraní lze v případě potřeby využívat s implementací, kterou poskytuje DI a Refit.

Vytváření požadavků POST, PUT a DELETE

V předchozích příkladech používají všechny požadavky HTTP metodu GET. HttpClient také podporuje další příkazy HTTP, včetně:

  • POST
  • PUT
  • DELETE
  • PATCH

Úplný seznam podporovaných příkazů HTTP najdete v tématu HttpMethod. Další informace o vytváření požadavků HTTP naleznete v tématu Odeslání požadavku pomocí HttpClient.

Následující příklad ukazuje, jak vytvořit požadavek 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();
}

V předchozím kódu metoda CreateItemAsync:

  • Serializuje Item parametr do FORMÁTU JSON pomocí System.Text.Json. To používá instanci JsonSerializerOptions ke konfiguraci procesu serializace.
  • Vytvoří instanci StringContent pro zabalení serializovaného JSONu k odeslání v těle požadavku HTTP.
  • Volání PostAsync pro odeslání obsahu JSON na zadanou adresu URL Toto je relativní adresa URL, která se přidá do HttpClient.BaseAddress.
  • Zavolá EnsureSuccessStatusCode, aby vyvolal výjimku, pokud stavový kód odpovědi neukazuje na úspěch.

HttpClient podporuje také jiné typy obsahu. Příklad: MultipartContent a StreamContent. Úplný seznam podporovaného obsahu naleznete viz HttpContent.

Následující příklad ukazuje požadavek 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();
}

Předchozí kód je velmi podobný příkladu POST . Metoda UpdateItemAsync volá PutAsync místo PostAsync.

Následující příklad ukazuje požadavek HTTP DELETE :

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

    httpResponse.EnsureSuccessStatusCode();
}

V předchozím kódu DeleteItemAsync metoda volá DeleteAsync. Vzhledem k tomu, že požadavky HTTP DELETE obvykle neobsahují žádný text, DeleteAsync metoda neposkytuje přetížení, které přijímá instanci HttpContent.

Další informace o použití různých příkazů HTTP s HttpClient, viz HttpClient.

HttpClient správa životnosti

Nová HttpClient instance se vrátí pokaždé, když CreateClient je volána na IHttpClientFactory. Pro každý název klienta se vytvoří jedna HttpClientHandler instance. Továrna spravuje životní cykly HttpClientHandler instancí.

IHttpClientFactory HttpClientHandler ukládá instance vytvořené továrnou do mezipaměti, aby se snížila spotřeba prostředků. Instance HttpClientHandler může být znovu použita z mezipaměti při vytváření nové HttpClient instance, pokud jeho životnost nevypršela.

Ukládání obslužných rutin do mezipaměti je žádoucí, protože každá obslužná rutina obvykle spravuje svůj vlastní základní fond připojení HTTP. Vytvoření více obslužných rutin, než je potřeba, může vést k vyčerpání soketů a zpoždění připojení. Některé obslužné rutiny také trvale udržují připojení otevřená, což může obslužné rutině zabránit v reakci na změny DNS.

Výchozí doba platnosti obslužné rutiny je dvě minuty. Pokud chcete přepsat výchozí hodnotu, zavolejte SetHandlerLifetime pro každého klienta na IServiceCollection:

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

Důležité

HttpClient Instance vytvořené pomocí IHttpClientFactory jsou určeny k krátkodobému použití.

  • Recyklace a opětovné vytvoření HttpMessageHandler po jejich vypršení životnosti je nezbytné pro IHttpClientFactory, aby obslužné rutiny reagovaly na změny DNS. HttpClient je svázaný s konkrétní instancí obslužné rutiny při jeho vytvoření, takže nové HttpClient instance by měly být požadovány včas, aby se zajistilo, že klient získá aktualizovanou obslužnou rutinu.

  • Vyřazení takových HttpClient instancí vytvořených továrnou nebude vést k vyčerpání soketů, protože jeho odstranění nespustí odstranění HttpMessageHandler. IHttpClientFactory sleduje a odstraňuje prostředky používané k vytváření HttpClient instancí, konkrétně HttpMessageHandler instancí, jakmile jejich životnost vyprší a už je nepoužívá HttpClient .

Udržování jediné instance při životě po dlouhou dobu je běžný vzor, který lze použít jako alternativu k , ale tento vzor vyžaduje další nastavení, například . Můžete použít buď dlouhodobé klienty s PooledConnectionLifetime, nebo krátkodobé klienty vytvořené pomocí IHttpClientFactory. Informace o tom, jakou strategii použít ve vaší aplikaci, najdete v tématu Pokyny pro používání klientů HTTP.

Nakonfigurujte HttpMessageHandler

Může být nutné řídit konfiguraci vnitřního HttpMessageHandler prostředí používaného klientem.

Je vráceno IHttpClientBuilder při přidávání pojmenovaných či zadaných klientů. Metoda rozšíření ConfigurePrimaryHttpMessageHandler může být použita k definování delegáta na IServiceCollection. Delegát se používá k vytvoření a konfiguraci primárního HttpMessageHandler, který tento klient používá.

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

Konfigurace HttClientHandler umožňuje zadat proxy pro HttpClient instanci mezi různými dalšími vlastnostmi obslužné rutiny. Další informace najdete v tématu Proxy na klienta.

Další konfigurace

Existuje několik dalších možností konfigurace pro řízení IHttpClientHandler:

metoda Popis
AddHttpMessageHandler Přidá další obslužnou rutinu zprávy pro pojmenovaný objekt HttpClient.
AddTypedClient Konfiguruje vazbu mezi TClient a pojmenovanou HttpClient přidruženou k objektu IHttpClientBuilder.
ConfigureHttpClient Přidá delegáta, který se použije ke konfiguraci entity s názvem HttpClient.
ConfigurePrimaryHttpMessageHandler Nakonfiguruje primární HttpMessageHandler z kontejneru pro injektování závislostí pro pojmenovanou HttpClient.
RedactLoggedHeaders Nastaví kolekci názvů hlaviček HTTP, pro které mají být hodnoty před protokolováním skryty.
SetHandlerLifetime Nastaví dobu, po kterou HttpMessageHandler může být instance znovu použita. Každý pojmenovaný klient může mít svou vlastní nakonfigurovanou hodnotu životnosti obsluhy.
UseSocketsHttpHandler Nakonfiguruje novou nebo dříve přidanou SocketsHttpHandler instanci z kontejneru injektáže závislostí tak, aby byla použita jako primární obslužná rutina pro pojmenovanou HttpClient. (pouze pro .NET 5+)

Použití IHttpClientFactory společně se SocketsHttpHandler

Implementace SocketsHttpHandlerHttpMessageHandler byla přidána v .NET Core 2.1, což umožňuje konfigurovat PooledConnectionLifetime. Toto nastavení slouží k zajištění toho, aby obslužná rutina reagovala na změny DNS, takže použití SocketsHttpHandler se považuje za alternativu k použití IHttpClientFactory. Další informace najdete v tématu Pokyny pro používání klientů HTTP.

SocketsHttpHandler a IHttpClientFactory mohou být použity společně k zlepšení konfigurovatelnosti. Pomocí obou těchto rozhraní API můžete využít možnosti konfigurace na nízké úrovni (například pro LocalCertificateSelectionCallback výběr dynamického certifikátu) a vysokou úroveň (například využití integrace DI a několika konfigurací klientů).

Použití obou rozhraní API:

  1. Zadejte SocketsHttpHandler jako PrimaryHandler via ConfigurePrimaryHttpMessageHandlernebo UseSocketsHttpHandler (pouze .NET 5+).
  2. Nastavte SocketsHttpHandler.PooledConnectionLifetime na základě intervalu, který očekáváte, že se DNS aktualizuje, například na hodnotu, která byla dříve v HandlerLifetime.
  3. (Volitelné) Vzhledem k tomu, že SocketsHttpHandler bude zpracovávat sdružování a recyklaci připojení, není na úrovni IHttpClientFactory recyklace obslužné rutiny již nutná. Můžete ho zakázat nastavením HandlerLifetime na 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

V předchozím příkladu byly pro ilustraci vybrány libovolně 2 minuty, které odpovídají výchozí HandlerLifetime hodnotě. Měli byste zvolit hodnotu na základě očekávané frekvence DNS nebo jiných změn sítě. Další informace najdete v části chování DNS v HttpClient pokynech a v části Poznámky v PooledConnectionLifetime dokumentaci k rozhraní API.

Vyhněte se typovým klientům v singletonových službách

Při použití přístupu s pojmenovaným klientem se vloží do služeb a instance IHttpClientFactory se vytvoří voláním HttpClient pokaždé, když je CreateClient potřeba.

Při přístupu typu klienta jsou však typoví klienti přechodnými objekty obvykle vloženými do služeb. To může způsobit problém, protože typový klient lze vložit do singletonové služby.

Důležité

Očekává se, že typoví klienti budou krátkodobí ve stejném smyslu jako HttpClient instance vytvořené pomocí IHttpClientFactory (další informace viz HttpClient správa životnosti). Jakmile se vytvoří zatypovaná instance klienta, IHttpClientFactory nemá nad ní žádnou kontrolu. Pokud je instance typového klienta zachycena v jedináčku, může jí to zabránit reagovat na změny DNS, což narušuje jeden z účelů IHttpClientFactory.

Pokud potřebujete používat HttpClient instance ve službě singleton, zvažte následující možnosti:

  • Místo toho použijte přístup s pojmenovaným klientem, vložte IHttpClientFactory do singleton služby a v případě potřeby znovu vytvořte instance HttpClient.
  • Pokud potřebujete typizovaný přístup klienta, použijte SocketsHttpHandler s nakonfigurovaným PooledConnectionLifetime jako primárního obslužného prvku. Další informace o použití SocketsHttpHandler s IHttpClientFactory naleznete v části Použití IHttpClientFactory společně se SocketsHttpHandler.

Rozsahy zpracovatelů zpráv v IHttpClientFactory

IHttpClientFactory vytvoří pro každou HttpMessageHandler instanci samostatný obor DI. Tyto obory DI jsou oddělené od oborů DI aplikací (například ASP.NET rozsah příchozích požadavků nebo uživatelem vytvořený ruční obor DI), takže nebudou sdílet instance služby s vymezeným oborem. Rozsahy obslužné rutiny zpráv jsou svázány s životností obslužné rutiny a mohou přežít rozsahy aplikace, což může vést například k opakovanému použití stejné instance se stejnými HttpMessageHandler vloženými vymezenými závislostmi mezi několika příchozími požadavky.

Diagram znázorňující dva obory DI aplikace a samostatný obor obslužné rutiny zpráv

Uživatelé důrazně nedoporučuje ukládat informace související s oborem (například data z HttpContext) do mezipaměti v HttpMessageHandler instancích a používat vymezené závislosti s opatrností, aby nedošlo k úniku citlivých informací.

Pokud potřebujete přístup k oboru DI aplikace z obslužné rutiny zprávy, například pro účely autentizace, doporučuje se zapouzdřit logiku řízení oboru do samostatného přechodného DelegatingHandler a obalit ji okolo instance HttpMessageHandler z mezipaměti IHttpClientFactory. Přístup k volání IHttpMessageHandlerFactory.CreateHandler obslužné rutiny pro všechny registrované pojmenované klienty. V takovém případě byste si sami vytvořili instanci HttpClient pomocí sestavené obslužné rutiny.

Diagram znázorňující získání přístupu k oborům DI aplikace prostřednictvím samostatné obslužné rutiny přechodných zpráv a IHttpMessageHandlerFactory

Následující příklad ukazuje, jak vytvořit HttpClient, který je si vědom rozsahu DelegatingHandler.

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

Další řešení může být použito metodou rozšíření pro registraci DelegatingHandler s ohledem na obor a pro přepsání výchozí registrace IHttpClientFactory pomocí přechodné služby, která má přístup k aktuálnímu oboru aplikace.

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

Další informace najdete v úplném příkladu.

Vyhněte se spoléhání na výchozí nastavení primární obslužné rutiny.

V této části termín "výchozí" primární obslužná rutina odkazuje na primární obslužnou rutinu, kterou výchozí implementace IHttpClientFactory (nebo přesněji, výchozí HttpMessageHandlerBuilder implementace) přiřadí, pokud nenakonfigurováno žádným způsobem vůbec.

Poznámka:

Hlavní obslužná rutina v továrním nastavení je implementační detail a může být změněna. ❌ VYHNĚTE se spoléhání na konkrétní implementaci, která se používá jako "tovární nastavení" (například HttpClientHandler).

Existují případy, kdy potřebujete znát konkrétní typ primárního zpracovatele, zejména pokud pracujete na knihovně tříd. Při zachování konfigurace koncového uživatele můžete chtít aktualizovat například vlastnosti specifické pro HttpClientHandler, jako jsou ClientCertificates, UseCookiesa UseProxy. Může být lákavé přetypovat primární obslužnou rutinu na HttpClientHandler, která náhodou fungovala, zatímco byla použita jako "továrně nastavená" primární obslužná rutina. Stejně jako každý kód závislý na detailech implementace je takové alternativní řešení křehké a náchylné k selhání.

Místo spolehnutí se na "výchozí tovární nastavení" pro primární obslužnou rutinu můžete použít ConfigureHttpClientDefaults k nastavení výchozí instance primární obslužné rutiny na úrovni aplikace.

// Contract with the end-user: Only HttpClientHandler is supported.

// --- "Pre-configure" stage ---
// The default is fixed as HttpClientHandler to avoid depending on the "factory-default"
// Primary Handler.
services.ConfigureHttpClientDefaults(b =>
    b.ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler() { UseCookies = false }));

// --- "End-user" stage ---
// IHttpClientBuilder builder = services.AddHttpClient("test", /* ... */);
// ...

// --- "Post-configure" stage ---
// The code can rely on the contract, and cast to HttpClientHandler only.
builder.ConfigurePrimaryHttpMessageHandler((handler, provider) =>
    {
        if (handler is not HttpClientHandler h)
        {
            throw new InvalidOperationException("Only HttpClientHandler is supported");
        }

        h.ClientCertificates.Add(GetClientCert(provider, builder.Name));

        //X509Certificate2 GetClientCert(IServiceProvider p, string name) { ... }
    });

Alternativně můžete zvážit kontrolu typu primární obslužné rutiny a nakonfigurovat specifika, jako jsou klientské certifikáty, pouze ve známých podpůrných typech (s největší pravděpodobností, HttpClientHandler a SocketsHttpHandler):

// --- "End-user" stage ---
// IHttpClientBuilder builder = services.AddHttpClient("test", /* ... */);
// ...

// --- "Post-configure" stage ---
// No contract is in place. Trying to configure main handler types supporting client
// certs, logging and skipping otherwise.
builder.ConfigurePrimaryHttpMessageHandler((handler, provider) =>
    {
        if (handler is HttpClientHandler h)
        {
            h.ClientCertificates.Add(GetClientCert(provider, builder.Name));
        }
        else if (handler is SocketsHttpHandler s)
        {
            s.SslOptions ??= new System.Net.Security.SslClientAuthenticationOptions();
            s.SslOptions.ClientCertificates ??= new X509CertificateCollection();
            s.SslOptions.ClientCertificates!.Add(GetClientCert(provider, builder.Name));
        }
        else
        {
            // Log warning
        }

        //X509Certificate2 GetClientCert(IServiceProvider p, string name) { ... }
    });

Viz také