Share via


IHttpClientFactory med .NET

I den här artikeln får du lära dig hur du använder IHttpClientFactory för att skapa HttpClient typer med olika .NET-grunder, till exempel beroendeinmatning (DI), loggning och konfiguration. Typen HttpClient introducerades i .NET Framework 4.5, som släpptes 2012. Med andra ord har det funnits ett tag. HttpClient används för att göra HTTP-begäranden och hantera HTTP-svar från webbresurser som identifieras av en Uri. HTTP-protokollet utgör den största delen av all Internettrafik.

Med moderna principer för programutveckling som ger bästa praxis IHttpClientFactory fungerar den som en fabriksabstraktion som kan skapa HttpClient instanser med anpassade konfigurationer. IHttpClientFactory introducerades i .NET Core 2.1. Vanliga HTTP-baserade .NET-arbetsbelastningar kan enkelt dra nytta av elastiska och tillfälliga felhanteringsmellanprogram från tredje part.

Kommentar

Om din app kräver cookies kan det vara bättre att undvika att använda IHttpClientFactory i din app. Alternativa sätt att hantera klienter finns i Riktlinjer för användning av HTTP-klienter.

Viktigt!

Livslängdshantering av HttpClient instanser som skapats av IHttpClientFactory skiljer sig helt från instanser som skapas manuellt. Strategierna är att använda antingen kortlivade klienter som skapats av IHttpClientFactory eller långlivade klienter med PooledConnectionLifetime konfiguration. Mer information finns i avsnittet HttpClient lifetime management (HttpClient-livslängdshantering ) och Riktlinjer för användning av HTTP-klienter.

Typ IHttpClientFactory

All exempelkällkod i den här artikeln förlitar sig på Microsoft.Extensions.Http NuGet-paketet. Dessutom görs HTTP-begäranden till det kostnadsfria {JSON}-GETplatshållar-API:et för att hämta användarobjektTodo.

När du anropar någon av tilläggsmetoderna AddHttpClient lägger du till och IHttpClientFactory relaterade tjänster till IServiceCollection. Typen IHttpClientFactory erbjuder följande fördelar:

  • Exponerar HttpClient klassen som en DI-redo typ.
  • Tillhandahåller en central plats för namngivning och konfigurering av logiska HttpClient instanser.
  • Kodifierar begreppet utgående mellanprogram via delegering av hanterare i HttpClient.
  • Tillhandahåller tilläggsmetoder för Polly-baserade mellanprogram för att dra nytta av delegering av hanterare i HttpClient.
  • Hanterar cachelagring och livslängd för underliggande HttpClientHandler instanser. Automatisk hantering undviker vanliga DNS-problem (Domain Name System) som uppstår när livslängden hanteras HttpClient manuellt.
  • Lägger till en konfigurerbar loggningsupplevelse (via ILogger) för alla begäranden som skickas via klienter som skapats av fabriken.

Konsumtionsmönster

Det finns flera sätt IHttpClientFactory att använda i en app:

Den bästa metoden beror på appens krav.

Grundläggande användning

Om du vill registrera IHttpClientFactoryanropar du 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();

Användning av tjänster kan kräva parametern IHttpClientFactory som konstruktor med DI. Följande kod använder IHttpClientFactory för att skapa en HttpClient instans:

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

Att använda IHttpClientFactory som i föregående exempel är ett bra sätt att omstrukturera en befintlig app. Det har ingen inverkan på hur HttpClient används. På platser där HttpClient instanser skapas i en befintlig app ersätter du dessa förekomster med anrop till CreateClient.

Namngivna klienter

Namngivna klienter är ett bra val när:

  • Appen kräver många distinkta användningsområden för HttpClient.
  • Många HttpClient instanser har olika konfigurationer.

Konfiguration för ett namngivet HttpClient kan anges under registreringen på 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");
    });

I föregående kod konfigureras klienten med:

  • Ett namn som hämtas från konfigurationen "TodoHttpClientName"under .
  • Basadressen https://jsonplaceholder.typicode.com/.
  • En "User-Agent" rubrik.

Du kan använda konfigurationen för att ange HTTP-klientnamn, vilket är användbart för att undvika att felbedöma klienter när du lägger till och skapar. I det här exemplet används appsettings.json-filen för att konfigurera HTTP-klientnamnet:

{
    "TodoHttpClientName": "JsonPlaceholderApi"
}

Det är enkelt att utöka den här konfigurationen och lagra mer information om hur du vill att HTTP-klienten ska fungera. Mer information finns i Konfiguration i .NET.

Skapa en klient

Varje gång CreateClient anropas:

  • En ny instans av HttpClient skapas.
  • Konfigurationsåtgärden anropas.

Om du vill skapa en namngiven klient skickar du namnet till 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 [];
    }
}

I föregående kod behöver HTTP-begäran inte ange ett värdnamn. Koden kan bara skicka sökvägen eftersom basadressen som konfigurerats för klienten används.

Inskrivna klienter

Inskrivna klienter:

  • Ge samma funktioner som namngivna klienter utan att behöva använda strängar som nycklar.
  • Ge IntelliSense och kompilatorn hjälp när du använder klienter.
  • Ange en enda plats för att konfigurera och interagera med en viss HttpClient. Till exempel kan en enskild typad klient användas:
    • För en enskild serverdelsslutpunkt.
    • Om du vill kapsla in all logik som hanterar slutpunkten.
  • Arbeta med DI och kan matas in där det behövs i appen.

En typad klient accepterar en HttpClient parameter i konstruktorn:

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

I koden ovan:

  • Konfigurationen anges när den inskrivna klienten läggs till i tjänstsamlingen.
  • HttpClient Tilldelas som en klassomfattningsvariabel (fält) och används med exponerade API:er.

API-specifika metoder kan skapas som exponerar HttpClient funktioner. Metoden kapslar till exempel GetUserTodosAsync in kod för att hämta användarspecifika Todo objekt.

Följande kod anropar AddHttpClient för att registrera en typad klientklass:

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

Den inskrivna klienten är registrerad som tillfälligt med DI. I föregående kod AddHttpClient registreras TodoService som en tillfällig tjänst. Den här registreringen använder en fabriksmetod för att:

  1. Skapa en instans av HttpClient.
  2. Skapa en instans av TodoServiceoch skicka in instansen av HttpClient till konstruktorn.

Viktigt!

Det kan vara farligt att använda inskrivna klienter i singleton-tjänster. Mer information finns i avsnittet Undvik inskrivna klienter i singleton-tjänster .

Kommentar

När du registrerar en skriven AddHttpClient<TClient> klient med metoden TClient måste typen ha en konstruktor som accepterar en HttpClient parameter. Dessutom TClient bör typen inte registreras separat med DI-containern.

Namngivna och inskrivna klienter

Namngivna klienter och inskrivna klienter har sina egna styrkor och svagheter. Det finns ett sätt att kombinera dessa två klienttyper för att få ut det bästa av båda världarna.

Det primära användningsfallet är följande: Använd samma typerade klient men mot olika domäner. Du har till exempel en primär och en sekundär tjänst och de visar exakt samma funktioner. Det innebär att du kan använda samma typgivna klient för att omsluta HttpClient användningen för att utfärda begäranden, bearbeta svar och hantera fel. Exakt samma kod används men med olika konfigurationer (olika basadresser, timeout och autentiseringsuppgifter till exempel).

I följande exempel används samma TodoService typskyddade klient som visades under avsnittet inskrivna klienter .

Registrera först de namngivna och inskrivna klienterna.

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

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Services.AddHttpClient<TodoService>("primary"
    client =>
    {
        // Configure the primary typed client
        client.BaseAddress = new Uri("https://primary-host-address.com/");
        client.Timeout = TimeSpan.FromSeconds(3);
    });

// Register the same typed client but with different settings
builder.Services.AddHttpClient<TodoService>("secondary"
    client =>
    {
        // Configure the secondary typed client
        client.BaseAddress = new Uri("https://secondary-host-address.com/");
        client.Timeout = TimeSpan.FromSeconds(10);
    });

I koden ovan:

  • Det första AddHttpClient anropet registrerar en TodoService skriven klient under namnet primary . Den underliggande HttpClient pekar på den primära tjänsten och har en kort tidsgräns.
  • Det andra AddHttpClient anropet registrerar en TodoService skriven klient under namnet secondary . Den underliggande HttpClient pekar på den sekundära tjänsten och har en längre tidsgräns.
using IHost host = builder.Build();

// Fetch an IHttpClientFactory instance to create a named client
IHttpClientFactory namedClientFactory =
    host.Services.GetRequiredService<IHttpClientFactory>();

// Fetch an ITypedHttpClientFactory<TodoService> instance to create a named and typed client
ITypedHttpClientFactory<TodoService> typedClientFactory  =
    host.Services.GetRequiredService<ITypedHttpClientFactory<TodoService>>();

// Create a TodoService instance against the primary host
var primaryClient = namedClientFactory.CreateClient("primary");
var todoService = typedClientFactory.CreateClient(primaryClient);

I koden ovan:

  • En IHttpClientFactory instans hämtas från DI-containern för att kunna skapa namngivna klienter via dess CreateClient metod.
  • En ITypedHttpClientFactory<TodoService> instans hämtas från DI-containern för att kunna skapa inskrivna klienter via dess CreateClient metod.
    • Den här CreateClient överlagringen tog emot ett namngivet HttpClient (med rätt konfiguration) som parameter.
    • Den skapade todoService är konfigurerad för att använda den primära tjänsten.

Kommentar

Typen IHttpClientFactory finns i System.Net.Http namnrymderna medan ITypedHttpClientFactory typen inuti Microsoft.Extensions.Http.

Viktigt!

Använd implementeringsklassen (i föregående exempel TodoService) som typparameter för ITypedHttpClientFactory. Även om du har en abstraktion (t.ex ITodoService . gränssnitt) måste du fortfarande använda implementeringen. Om du av misstag använder abstraktionen (ITodoService) kommer den när du anropar dess CreateClient att utlösa en InvalidOperationException.

try
{
    Todo[] todos = await todoService.GetUserTodosAsync(4);
}
catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException)
{
    // The request timed out against the primary host

    // Create a TodoService instance against the secondary host
    var fallbackClient = namedClientFactory.CreateClient("secondary");
    var todoFallbackService = typedClientFactory.CreateClient(fallbackClient);

    // Issue request against the secondary host
    Todo[] todos = await todoFallbackService.GetUserTodosAsync(4);
}

I koden ovan:

  • Den försöker utfärda en begäran mot den primära tjänsten.
  • Om tidsgränsen för begäran överskrids (tar längre tid än 3 sekunder) genererar den en TaskCanceledException med en TimeoutException inre.
  • Vid timeout skapas och används en ny klient som nu riktar sig till den sekundära tjänsten.

Genererade klienter

IHttpClientFactory kan användas i kombination med bibliotek från tredje part, till exempel Refit. Refit är ett REST-bibliotek för .NET. Det möjliggör deklarativa REST API-definitioner, mappning av gränssnittsmetoder till slutpunkter. En implementering av gränssnittet genereras dynamiskt av , med hjälp HttpClient av RestServiceför att göra externa HTTP-anrop.

Tänk på följande record typ:

namespace Shared;

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

Följande exempel förlitar sig på Refit.HttpClientFactory NuGet-paketet och är ett enkelt gränssnitt:

using Refit;
using Shared;

namespace GeneratedHttp.Example;

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

Föregående C#-gränssnitt:

  • Definierar en metod med namnet GetUserTodosAsync som returnerar en Task<Todo[]> instans.
  • Deklarerar ett Refit.GetAttribute attribut med sökvägen och frågesträngen till det externa API:et.

En typad klient kan läggas till med Hjälp av Refit för att generera implementeringen:

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

Det definierade gränssnittet kan användas vid behov, med implementeringen som tillhandahålls av DI och Refit.

Göra POST-, PUT- och DELETE-begäranden

I föregående exempel använder alla HTTP-begäranden HTTP-verbet GET . HttpClient stöder även andra HTTP-verb, inklusive:

  • POST
  • PUT
  • DELETE
  • PATCH

En fullständig lista över HTTP-verb som stöds finns i HttpMethod. Mer information om hur du gör HTTP-begäranden finns i Skicka en begäran med hjälp av HttpClient.

I följande exempel visas hur du gör en HTTP-begäran 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();
}

I föregående kod, CreateItemAsync metoden:

  • Serialiserar parametern Item till JSON med .System.Text.Json Detta använder en instans av JsonSerializerOptions för att konfigurera serialiseringsprocessen.
  • Skapar en instans av StringContent för att paketera den serialiserade JSON för att skicka i HTTP-begärandetexten.
  • Anropar PostAsync för att skicka JSON-innehållet till den angivna URL:en. Det här är en relativ URL som läggs till i HttpClient.BaseAddress.
  • Anrop EnsureSuccessStatusCode för att utlösa ett undantag om svarsstatuskoden inte indikerar att åtgärden lyckades.

HttpClient stöder även andra typer av innehåll. Till exempel MultipartContent och StreamContent. En fullständig lista över innehåll som stöds finns i HttpContent.

I följande exempel visas en HTTP-begäran 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();
}

Föregående kod liknar exemplet.POST Metoden UpdateItemAsync anropar PutAsync i stället för PostAsync.

I följande exempel visas en HTTP-begäran DELETE :

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

    httpResponse.EnsureSuccessStatusCode();
}

I föregående kod DeleteItemAsync anropar DeleteAsyncmetoden . Eftersom HTTP DELETE-begäranden vanligtvis inte innehåller någon brödtext tillhandahåller DeleteAsync metoden inte någon överlagring som accepterar en instans av HttpContent.

Mer information om hur du använder olika HTTP-verb med HttpClientfinns i HttpClient.

HttpClient livslängdshantering

En ny HttpClient instans returneras varje gång CreateClient anropas på IHttpClientFactory. En HttpClientHandler instans skapas per klientnamn. Fabriken hanterar livslängden för HttpClientHandler instanserna.

IHttpClientFactory cachelagrar de HttpClientHandler instanser som skapats av fabriken för att minska resursförbrukningen. En HttpClientHandler instans kan återanvändas från cachen när du skapar en ny HttpClient instans om dess livslängd inte har upphört att gälla.

Cachelagring av hanterare är önskvärt eftersom varje hanterare vanligtvis hanterar sin egen underliggande HTTP-anslutningspool. Att skapa fler hanterare än nödvändigt kan leda till socketöverbelastning och anslutningsfördröjningar. Vissa hanterare håller även anslutningarna öppna på obestämd tid, vilket kan hindra hanteraren från att reagera på DNS-ändringar.

Standardhanterarlivslängden är två minuter. Om du vill åsidosätta standardvärdet anropar du SetHandlerLifetime för varje klient på :IServiceCollection

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

Viktigt!

HttpClient instanser som skapats av IHttpClientFactory är avsedda att vara kortvariga.

  • Återvinning och återskapande HttpMessageHandlerav "s när deras livslängd upphör att gälla är viktigt för IHttpClientFactory att säkerställa att hanterare reagerar på DNS-ändringar. HttpClient är knuten till en specifik hanterarinstans när den skapas, så nya HttpClient instanser bör begäras i tid för att säkerställa att klienten hämtar den uppdaterade hanteraren.

  • Att ta bort sådana HttpClient instanser som skapats av fabriken leder inte till socketöverbelastning, eftersom dess bortskaffande inte utlöser bortskaffandet av HttpMessageHandler. IHttpClientFactory spårar och tar bort resurser som används för att skapa HttpClient instanser, särskilt HttpMessageHandler instanserna, så snart deras livslängd går ut och de inte HttpClient längre används.

Att hålla en enskild HttpClient instans vid liv under en längre tid är ett vanligt mönster som kan användas som ett alternativ till , men det här mönstret kräver ytterligare konfiguration, till IHttpClientFactoryexempel PooledConnectionLifetime. Du kan använda antingen långlivade klienter med PooledConnectionLifetimeeller kortlivade klienter som skapats av IHttpClientFactory. Information om vilken strategi som ska användas i din app finns i Riktlinjer för att använda HTTP-klienter.

Konfigurera HttpMessageHandler

Det kan vara nödvändigt att styra konfigurationen av det inre HttpMessageHandler som används av en klient.

En IHttpClientBuilder returneras när du lägger till namngivna eller inskrivna klienter. Tilläggsmetoden ConfigurePrimaryHttpMessageHandler kan användas för att definiera ett ombud för IServiceCollection. Ombudet används för att skapa och konfigurera den primära HttpMessageHandler som används av klienten:

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

Genom att HttClientHandler konfigurera kan du ange en proxy för instansen HttpClient bland olika andra egenskaper för hanteraren. Mer information finns i Proxy per klient.

Ytterligare konfiguration

Det finns flera ytterligare konfigurationsalternativ för att styra IHttpClientHandler:

Metod beskrivning
AddHttpMessageHandler Lägger till ytterligare en meddelandehanterare för en med namnet HttpClient.
AddTypedClient Konfigurerar bindningen mellan TClient och det namngivna som HttpClient är associerat med IHttpClientBuilder.
ConfigureHttpClient Lägger till ett ombud som ska användas för att konfigurera en namngiven HttpClient.
ConfigureHttpMessageHandlerBuilder Lägger till ett ombud som ska användas för att konfigurera meddelandehanterare med hjälp av HttpMessageHandlerBuilder för en namngiven HttpClient.
ConfigurePrimaryHttpMessageHandler Konfigurerar den primära HttpMessageHandler från containern för beroendeinmatning för en namngiven HttpClient.
RedactLoggedHeaders Anger samlingen med HTTP-huvudnamn som värden ska redigeras för innan du loggar.
SetHandlerLifetime Anger hur lång tid en HttpMessageHandler instans kan återanvändas. Varje namngiven klient kan ha ett eget konfigurerat hanterares livslängdsvärde.

Använda IHttpClientFactory tillsammans med SocketsHttpHandler

Implementeringen SocketsHttpHandler av HttpMessageHandler lades till i .NET Core 2.1, vilket gör att det går PooledConnectionLifetime att konfigurera. Den här inställningen används för att säkerställa att hanteraren reagerar på DNS-ändringar, så att använda SocketsHttpHandler anses vara ett alternativ till att använda IHttpClientFactory. Mer information finns i Riktlinjer för användning av HTTP-klienter.

SocketsHttpHandler Men och IHttpClientFactory kan användas tillsammans för att förbättra konfigurerbarheten. Genom att använda båda dessa API:er drar du nytta av konfigurerbarhet på både en låg nivå (till exempel med dynamiskt LocalCertificateSelectionCallback val av certifikat) och en hög nivå (till exempel genom att använda DI-integrering och flera klientkonfigurationer).

Så här använder du båda API:erna:

  1. Ange SocketsHttpHandler som PrimaryHandler och konfigurera dess PooledConnectionLifetime (till exempel till ett värde som tidigare fanns i HandlerLifetime).
  2. Som SocketsHttpHandler hanterar anslutningspooler och återvinning behövs inte hanteringsåtervinning på nivån IHttpClientFactory längre. Du kan inaktivera det genom att ange HandlerLifetime till Timeout.InfiniteTimeSpan.
services.AddHttpClient(name)
    .ConfigurePrimaryHttpMessageHandler(() =>
    {
        return new SocketsHttpHandler()
        {
            PooledConnectionLifetime = TimeSpan.FromMinutes(2)
        };
    })
    .SetHandlerLifetime(Timeout.InfiniteTimeSpan); // Disable rotation, as it is handled by PooledConnectionLifetime

Undvik inskrivna klienter i singleton-tjänster

När du använder den namngivna klientmetoden IHttpClientFactory matas in i tjänster och HttpClient instanser skapas genom att anropa CreateClient varje gång en HttpClient behövs.

Men med den typerade klientmetoden är inskrivna klienter tillfälliga objekt som vanligtvis matas in i tjänster. Det kan orsaka ett problem eftersom en skriven klient kan matas in i en singleton-tjänst.

Viktigt!

Inskrivna klienter förväntas vara kortvariga i samma mening som instanser som HttpClient skapats av IHttpClientFactory (mer information HttpClient finns i livslängdshantering). Så snart en typinstans av klienten har skapats IHttpClientFactory har den ingen kontroll över den. Om en skriven klientinstans fångas i en singleton kan den förhindra att den reagerar på DNS-ändringar, vilket motverkar ett av IHttpClientFactorysyftena med .

Om du behöver använda HttpClient instanser i en singleton-tjänst bör du överväga följande alternativ:

  • Använd den namngivna klientmetoden i stället, mata in IHttpClientFactory singleton-tjänsten och återskapa HttpClient instanser vid behov.
  • Om du behöver den typerade klientmetoden använder du SocketsHttpHandler med konfigurerad PooledConnectionLifetime som primär hanterare. Mer information om hur du använder SocketsHttpHandler med finns i avsnittet Använda IHttpClientFactory tillsammans med SocketsHttpHandlerIHttpClientFactory.

Omfång för meddelandehanterare i IHttpClientFactory

IHttpClientFactory skapar ett separat DI-omfång per varje HttpMessageHandler instans. Dessa DI-omfång är åtskilda från program-DI-omfång (till exempel ASP.NET omfång för inkommande begäran eller ett användarskapat manuellt DI-omfång), så de delar inte begränsade tjänstinstanser. Omfång för meddelandehanterare är knutna till hanterarens livslängd och kan överleva programomfattningar, vilket kan leda till att till exempel återanvända samma HttpMessageHandler instans med samma inmatade omfångsberoenden mellan flera inkommande begäranden.

Diagram som visar två program-DI-omfång och ett separat meddelandehanteraromfång

Användare rekommenderas starkt att inte cachelagras omfångsrelaterad information (till exempel data från HttpContext) inuti HttpMessageHandler instanser och använda begränsade beroenden med försiktighet för att undvika att läcka känslig information.

Om du behöver åtkomst till ett app-DI-omfång från meddelandehanteraren, för autentisering som exempel, kapslar du in omfångsmedveten logik i en separat tillfällig DelegatingHandleroch omsluter den runt en HttpMessageHandler instans från cachen IHttpClientFactory . För att få åtkomst till hanterarens anrop IHttpMessageHandlerFactory.CreateHandler för alla registrerade namngivna klienter. I så fall skulle du skapa en HttpClient instans själv med hjälp av den konstruerade hanteraren.

Diagram som visar åtkomst till app-DI-omfång via en separat tillfällig meddelandehanterare och IHttpMessageHandlerFactory

I följande exempel visas hur du skapar en HttpClient med ett omfångsmedveten 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);

En ytterligare lösning kan följa med en tilläggsmetod för att registrera en omfångsmedveten DelegatingHandler och åsidosättande standardregistrering IHttpClientFactory av en tillfällig tjänst med åtkomst till det aktuella appomfånget:

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

Mer information finns i det fullständiga exemplet.

Se även