IHttpClientFactory med .NET
I den här artikeln får du lära dig hur du använder IHttpClientFactory
gränssnittet 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 som anges i den här artikeln kräver installation av Microsoft.Extensions.Http
NuGet-paketet. Dessutom visar kodexemplen användningen av HTTP-begäranden för att hämta användarobjekt GET
från det kostnadsfria {JSON}-Todo
platshållar-API:et.
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 IHttpClientFactory
anropar 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:
- Skapa en instans av
HttpClient
. - Skapa en instans av
TodoService
och skicka in instansen avHttpClient
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 typad klient med AddHttpClient<TClient>
metoden TClient
måste typen ha en konstruktor som accepterar en HttpClient
som en parameter. Dessutom TClient
bör typen inte registreras med DI-containern separat, eftersom detta leder till att den senare registreringen skriver över den förra.
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 RestService
av HttpClient
fö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 enTask<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 HttpClient
finns 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
HttpMessageHandler
av "s när deras livslängd upphör att gälla är viktigt förIHttpClientFactory
att säkerställa att hanterare reagerar på DNS-ändringar.HttpClient
är knuten till en specifik hanterarinstans när den skapas, så nyaHttpClient
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 avHttpMessageHandler
.IHttpClientFactory
spårar och tar bort resurser som används för att skapaHttpClient
instanser, särskiltHttpMessageHandler
instanserna, så snart deras livslängd går ut och de inteHttpClient
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 IHttpClientFactory
exempel PooledConnectionLifetime
. Du kan använda antingen långlivade klienter med PooledConnectionLifetime
eller 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 . |
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. |
UseSocketsHttpHandler | Konfigurerar en ny eller en tidigare tillagd SocketsHttpHandler instans från containern för beroendeinmatning som ska användas som primär hanterare för en namngiven HttpClient . (endast.NET 5+ |
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:
- Ange
SocketsHttpHandler
somPrimaryHandler
via ConfigurePrimaryHttpMessageHandler, eller UseSocketsHttpHandler (endast.NET 5+). - Konfigurera SocketsHttpHandler.PooledConnectionLifetime baserat på det intervall som du förväntar dig att DNS ska uppdateras, till exempel till ett värde som tidigare fanns i
HandlerLifetime
. - (Valfritt) Eftersom
SocketsHttpHandler
hanterar anslutningspooler och återvinning behövs inte längre hanteringsåtervinning på nivånIHttpClientFactory
. Du kan inaktivera det genom att angeHandlerLifetime
tillTimeout.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
I exemplet ovan valdes 2 minuter godtyckligt för illustrationsändamål och justerades till ett standardvärde HandlerLifetime
. Du bör välja värdet baserat på den förväntade frekvensen för DNS eller andra nätverksändringar. Mer information finns i avsnittet DNS-beteende i HttpClient
riktlinjerna och avsnittet Kommentarer i API-dokumentationen PooledConnectionLifetime .
Undvik inskrivna klienter i singleton-tjänster
När du använder den 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 skapats av HttpClient
(mer information HttpClient
). 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 IHttpClientFactory
syftena 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 återskapaHttpClient
instanser vid behov. - Om du behöver den typerade klientmetoden använder du
SocketsHttpHandler
med konfigureradPooledConnectionLifetime
som primär hanterare. Mer information om hur du använderSocketsHttpHandler
med finns i avsnittetIHttpClientFactory
.
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.
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 DelegatingHandler
och 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.
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.
Undvik att förlita dig på "fabriksstandard" primärhanterare
I det här avsnittet refererar termen "fabriksstandard" primärhanterare till den primära hanteraren som standardimplementeringen IHttpClientFactory
(eller mer exakt, standardimplementeringen HttpMessageHandlerBuilder
) tilldelar om inte har konfigurerats på något sätt alls.
Kommentar
Primärhanteraren "fabriksstandard" är en implementeringsinformation och kan komma att ändras.
❌ AVOID beroende på en specifik implementering som används som "fabriksstandard" (till exempel HttpClientHandler
).
Det finns fall där du behöver känna till den specifika typen av primärhanterare, särskilt om du arbetar med ett klassbibliotek. När du bevarar slutanvändarens konfiguration kanske du vill uppdatera till exempel HttpClientHandler
-specifika egenskaper som ClientCertificates
, UseCookies
och UseProxy
. Det kan vara frestande att skicka den primära hanteraren till HttpClientHandler
, vilket råkade arbete medan HttpClientHandler
användes som "fabriksstandard" primärhanterare. Men som all kod beroende på implementeringsinformation är en sådan lösning bräcklig och kommer att brytas.
I stället för att förlita dig på den primära hanteraren "fabriksstandard" kan du använda ConfigureHttpClientDefaults
för att konfigurera en standardinstans för primär hanterarinstans på "appnivå":
// 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) { ... }
});
Du kan också överväga att kontrollera den primära hanterartypen och konfigurera detaljer som klientcertifikat endast i de välkända stödtyperna (troligtvis HttpClientHandler
och 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) { ... }
});