Megosztás a következőn keresztül:


IHttpClientFactory a .NET-tel

Ebből a cikkből megtudhatja, hogyan hozhat létre IHttpClientFactory típusok különböző .NET-alapokat, például a függőséginjektálást (DI), a naplózást és a konfigurációt az interfész használatávalHttpClient. A HttpClient típust a 2012-ben megjelent .NET-keretrendszer 4.5-ben vezették be. Más szóval, már egy ideje itt van. HttpClient HTTP-kérések készítésére és a http-válaszok kezelésére szolgál a Uriweberőforrások által azonosított . A HTTP protokoll az internetes forgalom túlnyomó többségét teszi ki.

Az ajánlott eljárásokat hajtó modern alkalmazásfejlesztési alapelvek a gyár absztrakciójaként szolgálnak, IHttpClientFactory amely egyéni konfigurációkkal rendelkező példányokat hozhat létre HttpClient . IHttpClientFactory a .NET Core 2.1-ben jelent meg. A gyakori HTTP-alapú .NET-számítási feladatok könnyedén kihasználhatják a rugalmas és átmeneti hibakezelési külső köztes szoftver előnyeit.

Feljegyzés

Ha az alkalmazás cookie-kat igényel, érdemes lehet elkerülni a használatot IHttpClientFactory az alkalmazásban. Az ügyfelek kezelésének alternatív módjaiért tekintse meg a HTTP-ügyfelek használatának irányelveit.

Fontos

A létrehozott HttpClient példányok élettartam-kezelése IHttpClientFactory teljesen eltér a manuálisan létrehozott példányoktól. A stratégiák a beállított vagy hosszú élettartamú ügyfelek által PooledConnectionLifetime További információkért tekintse meg a HttpClient élettartam-kezelési szakaszát és a HTTP-ügyfelek használatának irányelveit.

A IHttpClientFactory típus

A cikkben szereplő összes mintaforráskódhoz telepíteni kell a Microsoft.Extensions.Http NuGet-csomagot. A példakódok emellett azt is bemutatják, hogy a http-kérések GET használatával lekérhetők a felhasználói Todo objektumok az ingyenes {JSON} helyőrző API-ból.

Amikor meghívja bármelyik AddHttpClient bővítménymetelyt, hozzáadja a IHttpClientFactory kapcsolódó szolgáltatásokat a IServiceCollection. A IHttpClientFactory típus a következő előnyöket kínálja:

  • Az osztályt HttpClient DI-kész típusként teszi elérhetővé.
  • Központi helyet biztosít a logikai HttpClient példányok elnevezéséhez és konfigurálásához.
  • Kodifikálja a kimenő köztes szoftver fogalmát a HttpClientdelegáló kezelőinek használatával.
  • A Polly-alapú köztes szoftver bővítménymetelyeket biztosít a kezelők delegálásának előnyeinek kihasználásához.HttpClient
  • Kezeli a mögöttes HttpClientHandler példányok gyorsítótárazását és élettartamát. Az automatikus felügyelet elkerüli az élettartamok manuális kezelésekor HttpClient előforduló gyakori dns-problémákat.
  • Konfigurálható naplózási felületet (via ILogger) ad hozzá a gyár által létrehozott ügyfeleken keresztül küldött összes kéréshez.

Használati minták

Az alkalmazásokban többféleképpen IHttpClientFactory is használható:

A legjobb módszer az alkalmazás követelményeitől függ.

Alapszintű használat

A regisztrációhoz hívja meg a IHttpClientFactorykövetkezőt 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();

A szolgáltatások használatához konstruktorparaméterre lehet szükség a IHttpClientFactory DI használatával. A következő kód egy példány létrehozásához IHttpClientFactory használHttpClient:

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

Az előző példához hasonló használat IHttpClientFactory jó módszer egy meglévő alkalmazás újrabontására. Nincs hatással a használat módjára HttpClient . Azokon a helyeken, ahol HttpClient a példányok egy meglévő alkalmazásban jönnek létre, cserélje le ezeket az előfordulásokat a következő CreateClienthívásokkal: .

Névvel ellátott ügyfelek

A nevesített ügyfelek akkor jó választásnak számítanak, ha:

  • Az alkalmazásnak számos különböző felhasználási módja HttpClientvan.
  • Sok HttpClient példány különböző konfigurációval rendelkezik.

A névvel ellátott HttpClient konfiguráció a következő regisztráció IServiceCollectionsorán adható meg:

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

Az előző kódban az ügyfél a következőkkel van konfigurálva:

  • A konfigurációból lekért név a "TodoHttpClientName".
  • Az alapcím https://jsonplaceholder.typicode.com/.
  • Egy "User-Agent" fejléc.

A konfigurációval megadhatja a HTTP-ügyfélneveket, ami segít elkerülni az ügyfelek helytelen elnevezését a hozzáadáskor és a létrehozáskor. Ebben a példában a appsettings.json fájl használatával konfigurálja a HTTP-ügyfél nevét:

{
    "TodoHttpClientName": "JsonPlaceholderApi"
}

Egyszerűen bővítheti ezt a konfigurációt, és további részleteket tárolhat arról, hogy hogyan szeretné a HTTP-ügyfél működését. További információ: Konfiguráció a .NET-ben.

Ügyfél létrehozása

Minden alkalommal CreateClient a következőt hívjuk meg:

  • Létrejön egy HttpClient új példány.
  • A rendszer meghívja a konfigurációs műveletet.

Névvel ellátott ügyfél létrehozásához adja át a nevét a következőbe 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 [];
    }
}

Az előző kódban a HTTP-kérésnek nem kell állomásnevet megadnia. A kód csak az elérési utat tudja átadni, mivel a rendszer az ügyfélhez konfigurált alapcímet használja.

Beírt ügyfelek

Beírt ügyfelek:

  • A névvel ellátott ügyfelekkel azonos képességeket biztosíthat anélkül, hogy sztringeket kellene használnia kulcsként.
  • Nyújtson segítséget az IntelliSense és a fordító számára az ügyfelek használatakor.
  • Adjon meg egyetlen helyet egy adott HttpClienthely konfigurálásához és használatához. Használhat például egyetlen beírt ügyfelet:
    • Egyetlen háttérvégponthoz.
    • A végponttal kapcsolatos összes logika beágyazása.
  • Együttműködhet a DI-vel, és szükség esetén injektálható az alkalmazásban.

A gépelt ügyfél elfogad egy paramétert HttpClient a konstruktorban:

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

A fenti kód a következőket végzi el:

  • A konfiguráció akkor van beállítva, amikor a beírt ügyfél hozzá lesz adva a szolgáltatásgyűjteményhez.
  • Ez HttpClient osztályhatókörű változóként (mezőként) van hozzárendelve, és a közzétett API-kkal együtt használatos.

Olyan API-specifikus metódusok hozhatók létre, amelyek elérhetővé HttpClient teszik a funkciókat. A metódus például beágyazza a GetUserTodosAsync kódot a felhasználóspecifikus Todo objektumok lekéréséhez.

A következő kódhívások AddHttpClient egy beírt ügyfélosztály regisztrálásához:

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

A beírt ügyfél átmenetiként van regisztrálva a DI-ben. Az előző kódban AddHttpClient átmeneti szolgáltatásként regisztrál TodoService . Ez a regisztráció egy gyári módszert használ a következő célokra:

  1. Hozza létre a HttpClient egy példányát.
  2. Hozzon létre egy példányt TodoService, amely átmegy a példányon a konstruktornak HttpClient .

Fontos

A gépelt ügyfelek használata az önálló szolgáltatásokban veszélyes lehet. További információkért tekintse meg a Beírt ügyfelek elkerülése a singleton services szakaszban található szakaszt.

Feljegyzés

Ha gépelt ügyfelet regisztrál a AddHttpClient<TClient> metódussal, a TClient típusnak olyan konstruktorral kell rendelkeznie, amely paraméterként elfogad egy HttpClient konstruktort. Emellett a TClient típust nem szabad külön regisztrálni a DI-tárolóban, mivel ez azt eredményezi, hogy a későbbi regisztráció felülírja az előbbit.

Létrehozott ügyfelek

IHttpClientFactory külső kódtárakkal, például a Refittel együtt használható. A Refit egy REST-kódtár a .NET-hez. Lehetővé teszi a deklaratív REST API-definíciókat, a végpontokhoz való leképezési felületi metódusokat. A felület implementációját a külső HTTP-hívások végrehajtásával dinamikusan RestServiceHttpClient hozza létre a rendszer.

Vegye figyelembe a következő record típust:

namespace Shared;

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

Az alábbi példa a Refit.HttpClientFactory NuGet-csomagra támaszkodik, és egy egyszerű felület:

using Refit;
using Shared;

namespace GeneratedHttp.Example;

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

Az előző C#-felület:

  • Egy példányt visszaadó GetUserTodosAsync metódust Task<Todo[]> definiál.
  • Deklarál egy Refit.GetAttribute attribútumot a külső API elérési útjával és lekérdezési sztringjével.

Egy beírt ügyfél hozzáadható a Refit használatával a megvalósítás létrehozásához:

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 definiált felület szükség esetén felhasználható a DI és a Refit által biztosított implementációval.

POST, PUT és DELETE kérések létrehozása

Az előző példákban minden HTTP-kérés a GET HTTP-parancsot használja. HttpClient egyéb HTTP-parancsokat is támogat, többek között a következőket:

  • POST
  • PUT
  • DELETE
  • PATCH

A támogatott HTTP-parancsok teljes listáját lásd HttpMethod: . A HTTP-kérések küldéséről további információt a HttpClient használatával történő kérés küldése című témakörben talál.

Az alábbi példa bemutatja, hogyan lehet HTTP-kérést POST küldeni:

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

Az előző kódban a CreateItemAsync metódus:

  • Szerializálja a paramétert Item A JSON használatával System.Text.Json. Ez egy példányt JsonSerializerOptions használ a szerializálási folyamat konfigurálásához.
  • Létrehoz egy példányt StringContent a szerializált JSON becsomagolásához a HTTP-kérés törzsébe való küldéshez.
  • A JSON-tartalomnak a megadott URL-címre való elküldésére irányuló hívások PostAsync . Ez egy relatív URL-cím, amely hozzáadódik a HttpClient.BaseAddresshez.
  • Kivételt jelölő hívások EnsureSuccessStatusCode , ha a válasz állapotkódja nem jelzi a sikert.

HttpClient más típusú tartalmakat is támogat. Például: MultipartContent és StreamContent. A támogatott tartalmak teljes listáját lásd HttpContent: .

Az alábbi példa egy HTTP-kérést PUT mutat be:

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

Az előző kód nagyon hasonlít a POST példához. A UpdateItemAsync metódus hívása PutAsync ahelyett, hogy PostAsync.

Az alábbi példa egy HTTP-kérést DELETE mutat be:

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

    httpResponse.EnsureSuccessStatusCode();
}

Az előző kódban a metódus meghívja a metódust DeleteItemAsyncDeleteAsync. Mivel a HTTP DELETE-kérelmek általában nem tartalmaznak törzset, a DeleteAsync metódus nem biztosít túlterhelést, amely elfogadja a példányt HttpContent.

Ha többet szeretne megtudni a különböző HTTP-parancsok HttpClienthasználatáról, tekintse meg a következőt HttpClient:

HttpClient élettartam-kezelés

A rendszer minden alkalommal új HttpClient példányt ad vissza, amikor CreateClient a rendszer meghívja a IHttpClientFactory. Ügyfélnévenként egy HttpClientHandler példány jön létre. A gyár kezeli a példányok élettartamát HttpClientHandler .

IHttpClientFactory gyorsítótárazza a HttpClientHandler gyár által létrehozott példányokat az erőforrás-felhasználás csökkentése érdekében. A HttpClientHandler példányok újra felhasználhatók a gyorsítótárból egy új HttpClient példány létrehozásakor, ha az élettartama nem járt le.

A kezelők gyorsítótárazása kívánatos, mivel az egyes kezelők általában a saját mögöttes HTTP-kapcsolatkészletét kezelik. A szükségesnél több kezelő létrehozása a szoftvercsatornák kimerülését és a csatlakozási késéseket eredményezheti. Egyes kezelők korlátlan ideig nyitva tartják a kapcsolatokat, ami megakadályozhatja, hogy a kezelő reagáljon a DNS-változásokra.

Az alapértelmezett kezelő élettartama két perc. Az alapértelmezett érték felülbírálásához hívja meg SetHandlerLifetime az egyes ügyfeleket a IServiceCollectionkövetkezőn:

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

Fontos

HttpClienta létrehozott IHttpClientFactory példányok rövid élettartamúak.

  • HttpMessageHandlerA dns-változásokra való reagálás biztosításához IHttpClientFactory elengedhetetlen, hogy a kezelők az élettartamuk lejártakor újra feldolgozhassák azokat. HttpClient létrehozásakor egy adott kezelőpéldányhoz van kötve, ezért az új HttpClient példányokat időben kell kérni annak biztosítása érdekében, hogy az ügyfél megkapja a frissített kezelőt.

  • A gyár által létrehozott ilyen HttpClient példányok megsemmisítése nem vezet aljzatkimerüléshez, mivel az ártalmatlanítása nem váltja ki a HttpMessageHandler. IHttpClientFactorynyomon követi és megsemmisíti a példányok, különösen a HttpClient példányok létrehozásához HttpMessageHandler használt erőforrásokat, amint lejár az élettartamuk, és már nincs HttpClient használatban.

Az egyetlen HttpClient példány hosszú ideig való életben tartása gyakori minta, amely alternatívaként IHttpClientFactory használható, azonban ehhez a mintához további beállításokra van szükség, például .PooledConnectionLifetime Használhatja a hosszú élettartamú ügyfeleket PooledConnectionLifetime, vagy a rövid élettartamú ügyfeleket, amelyeket a következő IHttpClientFactoryhozott létre: . A HTTP-ügyfelek használatának irányelvei című témakörből megtudhatja, hogy melyik stratégiát érdemes használni az alkalmazásban.

Konfigurálja a HttpMessageHandler

Szükség lehet az ügyfél által használt belső HttpMessageHandler konfiguráció szabályozására.

A rendszer névvel ellátott vagy beírt ügyfelek hozzáadásakor ad vissza egy IHttpClientBuilder hibát. A ConfigurePrimaryHttpMessageHandler bővítménymetódussal megadhatja a delegáltat a IServiceCollection. A meghatalmazott az ügyfél által használt elsődleges HttpMessageHandler kiszolgáló létrehozásához és konfigurálásához használható:

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

HttClientHandler A konfigurálás lehetővé teszi a példány proxyjának megadását a HttpClient kezelő különböző egyéb tulajdonságai között. További információ: Proxy per client.

További konfiguráció

A következő konfigurációs lehetőségek IHttpClientHandlerközül választhat:

Metódus Leírás
AddHttpMessageHandler Hozzáad egy további üzenetkezelőt egy elnevezetthez HttpClient.
AddTypedClient Konfigurálja a kötést a TClientHttpClientIHttpClientBuilder.
ConfigureHttpClient Hozzáad egy meghatalmazottat, aki egy elnevezett HttpClientkonfigurálásához lesz használva.
ConfigurePrimaryHttpMessageHandler Konfigurálja az elsődlegest HttpMessageHandler a függőséginjektálási tárolóból egy nevesített HttpClienttárolóhoz.
RedactLoggedHeaders Beállítja azoknak a HTTP-fejlécneveknek a gyűjteményét, amelyek értékeit a naplózás előtt újra ki kell léptetni.
SetHandlerLifetime A példányok újrafelhasználásának időtartamát HttpMessageHandler adja meg. Minden megnevezett ügyfél saját konfigurált kezelői élettartam-értékkel rendelkezhet.
UseSocketsHttpHandler Konfigurál egy új vagy egy korábban hozzáadott SocketsHttpHandler példányt a függőséginjektálási tárolóból úgy, hogy a névvel ellátott HttpClienttároló elsődleges kezelője legyen. (csak.NET 5+)

Az IHttpClientFactory és a SocketsHttpHandler együttes használata

A SocketsHttpHandler megvalósítás a HttpMessageHandler .NET Core 2.1-ben lett hozzáadva, amely lehetővé teszi PooledConnectionLifetime a konfigurálást. Ezzel a beállítással biztosítható, hogy a kezelő reagáljon a DNS-változásokra, ezért a használat SocketsHttpHandler alternatívának minősül IHttpClientFactory. További információt a HTTP-ügyfelek használatának irányelvei című témakörben talál.

A konfigurálhatóság azonban SocketsHttpHandlerIHttpClientFactory együtt is használható. Mindkét API használatával kihasználhatja a konfigurálhatóságot alacsony szinten (például dinamikus tanúsítványkiválasztáshoz) LocalCertificateSelectionCallback és magas szinten (például a DI-integráció és több ügyfélkonfiguráció kihasználásával).

Mindkét API használata:

  1. Adja meg a SocketsHttpHandler PrimaryHandler> vagy ConfigurePrimaryHttpMessageHandler a (csak.NET 5+) lehetőségetUseSocketsHttpHandler.
  2. Állítsa be SocketsHttpHandler.PooledConnectionLifetime a DNS frissítésének várható időköze alapján, például egy korábban megadott HandlerLifetimeértékre.
  3. (Nem kötelező) Mivel SocketsHttpHandler kezelni fogja a kapcsolatkészletezést és az újrahasznosítást, már nincs szükség kezelői újrahasznosításra a IHttpClientFactory szinten. Letilthatja a következő beállítással HandlerLifetimeTimeout.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

A fenti példában a rendszer 2 percet választott tetszőlegesen illusztrációs célokra, az alapértelmezett HandlerLifetime értékhez igazítva. Az értéket a DNS vagy más hálózati változások várható gyakorisága alapján kell kiválasztania. További információkért tekintse meg az irányelvek DNS-viselkedési szakaszát HttpClient és az API dokumentációjának Megjegyzések szakaszát PooledConnectionLifetime .

A gépelt ügyfelek elkerülése az önálló szolgáltatásokban

Az elnevezett ügyfél-megközelítés IHttpClientFactoryrendszer a szolgáltatásokba injektálja a példányokat, és HttpClient minden alkalommal meghívja CreateClient a példányokat, amikor HttpClient szükség van rá.

A gépelt ügyfél megközelítésével azonban a gépelt ügyfelek általában átmeneti objektumok, amelyeket általában a szolgáltatásokba injektálnak. Ez problémát okozhat, mert egy gépelt ügyfél injektálható egy singleton szolgáltatásba.

Fontos

A gépelt ügyfelek várhatóan rövid élettartamúak lesznek, ugyanúgy, mint az általuk HttpClient létrehozott példányok (további információ: HttpClient). Amint létrejön egy beírt ügyfélpéldány, IHttpClientFactory nincs rá vezérlése. Ha egy gépelt ügyfélpéldányt egyetlenton rögzít, az megakadályozhatja, hogy reagáljon a DNS-változásokra, és az egyik célt legyőzze IHttpClientFactory.

Ha példányokat kell használnia HttpClient egy singleton szolgáltatásban, vegye figyelembe a következő lehetőségeket:

  • Ehelyett használja az elnevezett ügyfél megközelítést, injektálva IHttpClientFactory az egyszeri szolgáltatásba, és szükség esetén újra kell dolgoznia HttpClient a példányokat.
  • Ha a beírt ügyfél megközelítést szeretné használni, használja SocketsHttpHandler elsődleges kezelőként konfigurálva PooledConnectionLifetime . A használattal SocketsHttpHandlerIHttpClientFactorykapcsolatos további információkért tekintse meg az IHttpClientFactory és a SocketsHttpHandler együttes használatát ismertető szakaszt.

Üzenetkezelő hatókörök az IHttpClientFactory-ban

IHttpClientFactory minden HttpMessageHandler példányhoz külön DI-hatókört hoz létre. Ezek a DI-hatókörök eltérnek az alkalmazás DI-hatóköreitől (például ASP.NET bejövő kérelem hatókörétől vagy egy felhasználó által létrehozott manuális DI-hatókörtől), így nem osztanak meg hatókörrel rendelkező szolgáltatáspéldányokat. Az Üzenetkezelő hatókörei a kezelő élettartamához vannak kötve, és túlléphetik az alkalmazás hatóköreit, ami például azt eredményezheti, hogy ugyanazt HttpMessageHandler a példányt ugyanazzal az injektált hatókörű függőséggel használja fel több bejövő kérés között.

Két alkalmazás DI-hatókörét és egy külön üzenetkezelő hatókörét bemutató ábra

A felhasználók számára erősen ajánlott , hogy ne gyorsítótárazza a hatókörrel kapcsolatos információkat (például az adatokat HttpContext) a példányokon belül HttpMessageHandler , és körültekintően használja a hatókörrel rendelkező függőségeket a bizalmas információk kiszivárgásának elkerülése érdekében.

Ha az üzenetkezelőtől szeretne hozzáférést kérni egy alkalmazás DI-hatóköréhez, a hitelesítéshez például a hatókör-tudatos logikát egy külön átmenetibe DelegatingHandlerágyazná be, és körbefuttatná egy HttpMessageHandler példány körül a IHttpClientFactory gyorsítótárból. A regisztrált IHttpMessageHandlerFactory.CreateHandler kezelői hívásának elérése. Ebben az esetben saját maga hoz létre egy példányt HttpClient a létrehozott kezelővel.

Ábra az alkalmazás DI-hatóköreihez való hozzáférésről egy külön átmeneti üzenetkezelő és az IHttpMessageHandlerFactory segítségével

Az alábbi példa egy hatókörrel rendelkező HttpClientobjektum DelegatingHandler létrehozását mutatja be:

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

Egy további kerülő megoldás egy bővítménymetódussal követve regisztrálhat egy hatóköralapú DelegatingHandler és felülbírált alapértelmezett IHttpClientFactory regisztrációt egy átmeneti szolgáltatásban, amely hozzáfér az aktuális alkalmazás hatóköréhez:

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

További információt a teljes példában talál.

Kerülje a "gyári alapértelmezett" elsődleges kezelő használatát.

Ebben a szakaszban a "gyári alapértelmezett" elsődleges kezelő kifejezés arra az elsődleges kezelőre vonatkozik, amelyet az alapértelmezett IHttpClientFactory implementáció (pontosabban az alapértelmezett HttpMessageHandlerBuilder implementáció) rendel hozzá abban az esetben, ha egyáltalán nincs konfigurálva.

Feljegyzés

A "gyári alapértelmezett" elsődleges kezelőprogram egy implementációs részlet, amely változhat. ❌ AVOID függ egy adott implementációtól, amelyet "gyári alapértelmezettként" használnak (például HttpClientHandler).

Vannak olyan esetek, amikor ismernie kell az elsődleges kezelő konkrét típusát, különösen akkor, ha osztálykönyvtáron dolgozik. A végfelhasználó konfigurációjának megőrzése mellett érdemes lehet frissíteni például HttpClientHandler-specifikus tulajdonságokat, például ClientCertificates, UseCookiesés UseProxy. Csábító lehet, ha az elsődleges kezelőt a HttpClientHandlerirányítja, ami történt munkával, miközben HttpClientHandler "gyári alapértelmezett" elsődleges kezelőként használták. Az implementáció részleteitől függően azonban az ilyen áthidaló megoldás törékeny és töréshez kötött.

Ahelyett, hogy a "gyári alapértelmezett" elsődleges kezelőre támaszkodna, a ConfigureHttpClientDefaults segítségével beállíthat egy "alkalmazás-szintű" alapértelmezett elsődleges kezelőpéldányt:

// 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) { ... }
    });

Másik lehetőségként érdemes lehet ellenőrizni az elsődleges kezelő típusát, és konfigurálni az ügyféltanúsítványokhoz hasonló jellemzőket csak a jól ismert támogató típusok esetében (valószínűleg HttpClientHandler és 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) { ... }
    });

Lásd még