IHttpClientFactory con .NET
In questo articolo si apprenderà come usare l'interfaccia IHttpClientFactory
per creare tipi HttpClient
con diversi concetti fondamentali di .NET, ad esempio inserimento delle dipendenze, registrazione e configurazione. Il tipo HttpClient è stato introdotto in .NET Framework 4.5, rilasciato nel 2012. In altre parole, è stato attivo per un po'. HttpClient
viene usato per effettuare richieste HTTP e gestire le risposte HTTP dalle risorse Web identificate da un oggetto Uri. Il protocollo HTTP gestisce la maggior parte del traffico Internet.
Con i moderni principi di sviluppo delle applicazioni che guidano le procedure consigliate, IHttpClientFactory funge da astrazione factory che può creare istanze HttpClient
con configurazioni personalizzate. IHttpClientFactory è stato introdotto in .NET Core 2.1. I carichi di lavoro .NET basati su HTTP comuni possono sfruttare facilmente il middleware di terze parti resiliente e in grado di gestire gli errori temporanei.
Nota
Se l'app richiede cookie, potrebbe essere preferibile evitare di usare IHttpClientFactory nell'app. Per modi alternativi di gestione dei client, vedi Linee guida per l'uso dei client HTTP.
Importante
La gestione della durata delle istanze HttpClient
create da IHttpClientFactory
è completamente diversa dalle istanze create manualmente. Le strategie sono l'uso di client di breve durata creati da IHttpClientFactory
o client di lunga durata con configurazione PooledConnectionLifetime
. Per altre informazioni, vedi la sezione Gestione della durata httpClient e Linee guida per l'uso dei client HTTP.
Il tipo IHttpClientFactory
Il codice sorgente di esempio fornito in questo articolo richiede l'installazione del pacchetto NuGet Microsoft.Extensions.Http
. Inoltre, gli esempi di codice illustrano l'utilizzo delle richieste HTTP GET
per recuperare gli oggetti utente Todo
dall'API segnaposto {JSON} gratuita.
Quando chiami uno dei metodi di estensione AddHttpClient, aggiungi IHttpClientFactory
e i servizi correlati a IServiceCollection. Il tipo IHttpClientFactory
offre i vantaggi seguenti:
- Espone la classe
HttpClient
come tipo pronto per l'inserimento delle dipendenze. - Offre una posizione centrale per la denominazione e la configurazione di istanze di
HttpClient
logiche. - Codifica il concetto di middleware in uscita tramite delega dei gestori in
HttpClient
. - Fornisce metodi di estensione per il middleware basato su Polly per sfruttare i vantaggi della delega dei gestori in
HttpClient
. - Gestisce la memorizzazione nella cache e la durata delle istanze HttpClientHandler sottostanti. La gestione automatica evita problemi comuni del sistema DNS (Domain Name System) che si verificano durante la gestione manuale delle durate
HttpClient
. - Aggiunge un'esperienza di registrazione configurabile, tramite ILogger, per tutte le richieste inviate attraverso i client creati dalla factory.
Modelli di consumo
IHttpClientFactory
può essere usato in un'app in diversi modi:
L'approccio migliore dipende dai requisiti dell'app.
Utilizzo di base
Per registrare IHttpClientFactory
, chiama 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();
L'utilizzo di servizi può richiedere IHttpClientFactory
come parametro del costruttore con inserimento di dipendenze. Il codice seguente usa IHttpClientFactory
per creare un'istanza di HttpClient
:
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 [];
}
}
L'uso di IHttpClientFactory
come nell'esempio precedente è un buon modo per effettuare il refactoring di un'app esistente. Non ha alcun impatto sulla modalità di utilizzo di HttpClient
. Nelle posizioni in cui le istanze HttpClient
vengono create in un'app esistente, sostituisci tali occorrenze con chiamate a CreateClient.
Client denominati
I client nominati sono una scelta ottimale quando:
- L'app richiede molti usi distinti di
HttpClient
. - Molte istanze
HttpClient
hanno configurazioni diverse.
La configurazione di un HttpClient
denominato può essere specificata durante la registrazione su 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");
});
Nel codice precedente il client è configurato con:
- Nome estratto dalla configurazione in
"TodoHttpClientName"
. - Indirizzo di base
https://jsonplaceholder.typicode.com/
. - Intestazione
"User-Agent"
.
Puoi usare la configurazione per specificare i nomi dei client HTTP, utile per evitare la denominazione errata dei client durante l'aggiunta e la creazione. In questo esempio viene usato il file appsettings.json per configurare il nome del client HTTP:
{
"TodoHttpClientName": "JsonPlaceholderApi"
}
È facile estendere questa configurazione e archiviare altri dettagli sulla modalità di funzionamento del client HTTP. Per altre informazioni, vedi Configurazione in .NET.
Creare il client
Ogni volta che viene chiamato CreateClient:
- Viene creata una nuova istanza di
HttpClient
. - Viene chiamata l'azione di configurazione.
Per creare un client denominato, passarne il nome in 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 [];
}
}
Nel codice precedente non è necessario che la richiesta specifichi un nome host. Poiché viene usato l'indirizzo di base configurato per il client, il codice può passare solo il percorso.
Client tipizzati
Client tipizzati:
- Offrono le stesse funzionalità dei client denominati senza la necessità di usare le stringhe come chiavi.
- Offrire l'aiuto di IntelliSense e del compilatore quando si usano i client.
- La configurazione e l'interazione con un particolare
HttpClient
può avvenire in un'unica posizione. Ad esempio, è possibile usare un singolo client tipizzato:- Per un singolo endpoint back-end.
- Per incapsulare tutta la logica che riguarda l'endpoint.
- Usano l'inserimento di dipendenze e possono essere inseriti dove necessario nell'app.
Un client tipizzato accetta il parametro HttpClient
nel proprio costruttore:
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();
}
Nel codice precedente:
- La configurazione viene impostata quando il client tipizzato viene aggiunto alla raccolta di servizi.
HttpClient
viene assegnato come variabile in ambito di classe (campo) e usato con le API esposte.
È possibile creare metodi specifici dell'API che espongono funzionalità HttpClient
. Ad esempio, il metodo GetUserTodosAsync
incapsula il codice per recuperare oggetti Todo
specifici dell'utente.
Il codice seguente chiama AddHttpClient per registrare una classe client tipizzata:
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");
});
Il client tipizzato viene registrato come temporaneo nell'inserimento di dipendenze. Nel codice precedente AddHttpClient
registra TodoService
come servizio temporaneo. Questa registrazione usa un metodo factory per:
- Creare un'istanza di
HttpClient
. - Creare un'istanza di
TodoService
, passando l'istanza diHttpClient
al relativo costruttore.
Importante
L'uso di client tipizzati nei servizi singleton può essere pericoloso. Per altre informazioni, vedi la sezione Evitare client tipizzati nei servizi singleton.
Nota
Quando si registra un client tipizzato con il metodo AddHttpClient<TClient>
, il tipo TClient
deve avere un costruttore che accetta un HttpClient
come parametro. Inoltre, il tipo TClient
non deve essere registrato separatamente con il contenitore di inserimento delle dipendenze, perché in questo modo l'ultima registrazione sovrascriverà la prima.
Client generati
È possibile usare IHttpClientFactory
in combinazione con librerie di terze parti, ad esempio Refit. Refit è una libreria REST per .NET. Consente le definizioni dichiarative dell'API REST, il mapping dei metodi di interfaccia agli endpoint. Un'implementazione dell'interfaccia viene generata dinamicamente da RestService
, usando HttpClient
per effettuare le chiamate HTTP esterne.
Considera il tipo record
seguente:
namespace Shared;
public record class Todo(
int UserId,
int Id,
string Title,
bool Completed);
L'esempio seguente si basa sul pacchetto NuGet Refit.HttpClientFactory
ed è un'interfaccia semplice:
using Refit;
using Shared;
namespace GeneratedHttp.Example;
public interface ITodoService
{
[Get("/todos?userId={userId}")]
Task<Todo[]> GetUserTodosAsync(int userId);
}
L’interfaccia C# precedente:
- Definisce un metodo denominato
GetUserTodosAsync
che restituisce un'istanza diTask<Todo[]>
. - Dichiara un attributo
Refit.GetAttribute
con il percorso e la stringa di query nell'API esterna.
È possibile aggiungere un client tipizzato usando Refit per generare l'implementazione:
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");
});
L'interfaccia definita può essere usata dove necessario, con l'implementazione offerta dall'inserimento di dipendenze e da Refit.
Effettuare richieste POST, PUT e DELETE
Negli esempi precedenti tutte le richieste HTTP usano il verbo HTTP GET
. HttpClient
supporta anche altri verbi HTTP, tra cui:
POST
PUT
DELETE
PATCH
Per un elenco completo dei verbi HTTP supportati, vedi HttpMethod. Per altre informazioni sull'esecuzione di richieste HTTP, vedi Inviare una richiesta tramite HttpClient.
L'esempio seguente illustra come effettuare una richiesta 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();
}
Nel codice precedente il metodo CreateItemAsync
:
- Serializza il parametro
Item
in JSON usandoSystem.Text.Json
. Usa un'istanza di JsonSerializerOptions per configurare il processo di serializzazione. - Crea un'istanza di StringContent per creare un pacchetto del codice JSON serializzato per l'invio nel corpo della richiesta HTTP.
- Chiama PostAsync per inviare il contenuto JSON all'URL specificato. Si tratta di un URL relativo che viene aggiunto a HttpClient.BaseAddress.
- Chiama EnsureSuccessStatusCode per generare un'eccezione se il codice di stato della risposta non indica l’esito positivo.
HttpClient
supporta anche altri tipi di contenuto. Ad esempio, MultipartContent e StreamContent. Per un elenco completo del contenuto supportato, vedi HttpContent.
L'esempio seguente mostra una richiesta 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();
}
Il codice precedente è molto simile all'esempio POST
. Il metodo UpdateItemAsync
chiama PutAsync anziché PostAsync
.
L'esempio seguente mostra una richiesta HTTP DELETE
:
public async Task DeleteItemAsync(Guid id)
{
using HttpResponseMessage httpResponse =
await httpClient.DeleteAsync($"/api/items/{id}");
httpResponse.EnsureSuccessStatusCode();
}
Nel codice precedente il metodo DeleteItemAsync
chiama DeleteAsync. Poiché le richieste HTTP DELETE in genere non contengono alcun corpo, il metodo DeleteAsync
non fornisce un overload che accetta un'istanza di HttpContent
.
Per saperne di più sull'uso di diversi verbi HTTP con HttpClient
, vedi HttpClient.
Gestione della durata di HttpClient
Viene restituita una nuova istanza di HttpClient
per ogni chiamata di CreateClient
per IHttpClientFactory
. Viene creata un'istanza HttpClientHandler per nome client. La factory gestisce le durate delle istanze di HttpClientHandler
.
IHttpClientFactory
memorizza nella cache le istanze di HttpClientHandler
create dalla factory per ridurre il consumo di risorse. Un'istanza di HttpClientHandler
può essere riusata dalla cache quando si crea una nuova istanza di HttpClient
se la relativa durata non è scaduta.
La memorizzazione nella cache dei gestori è consigliabile in quanto ogni gestore gestisce in genere il proprio pool di connessioni HTTP sottostanti. La creazione di più gestori del necessario può comportare l'esaurimento del socket e ritardi di connessione. Alcuni gestori mantengono inoltre le connessioni aperte a tempo indefinito. Ciò può impedire al gestore di reagire alle modifiche DNS.
La durata del gestore predefinito è di due minuti. Per eseguire l'override del valore predefinito, chiama SetHandlerLifetime per ogni client in IServiceCollection
:
services.AddHttpClient("Named.Client")
.SetHandlerLifetime(TimeSpan.FromMinutes(5));
Importante
Le istanze HttpClient
create da IHttpClientFactory
sono destinate a breve durata.
Il riciclo e la ricreazione di
HttpMessageHandler
quando la loro durata scade è essenziale perIHttpClientFactory
, per garantire che i gestori reagiscano alle modifiche DNS.HttpClient
è associato a un'istanza specifica del gestore al momento della creazione, pertanto le nuove istanzeHttpClient
devono essere richieste in modo tempestivo per garantire che il client ottenga il gestore aggiornato.L'eliminazione di tali istanze
HttpClient
create dalla factory non comporterà l'esaurimento del socket, perché lo smaltimento non attiverà l'eliminazione diHttpMessageHandler
.IHttpClientFactory
tiene traccia ed elimina le risorse usate per creare istanzeHttpClient
, in particolare le istanzeHttpMessageHandler
, non appena scade la durata eHttpClient
non le usa più.
Mantenere attiva una singola istanza HttpClient
per una durata prolungata è un modello comune che può essere usato come alternativa a IHttpClientFactory
, tuttavia, questo modello richiede un'installazione aggiuntiva, ad esempio PooledConnectionLifetime
. Puoi usare client di lunga durata con PooledConnectionLifetime
, o client di breve durata creati da IHttpClientFactory
. Per informazioni sulla strategia da usare nell'app, vedi Linee guida per l'uso dei client HTTP.
Configurare il HttpMessageHandler
Può essere necessario controllare la configurazione dell'elemento HttpMessageHandler interno usato da un client.
Quando si aggiungono client denominati o tipizzati viene restituito un elemento IHttpClientBuilder. È possibile usare il metodo di estensione ConfigurePrimaryHttpMessageHandler per definire un delegato sul IServiceCollection
. Il delegato viene usato per creare e configurare l'elemento HttpMessageHandler
primario usato dal client:
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler
{
AllowAutoRedirect = false,
UseDefaultCredentials = true
};
});
La configurazione di HttClientHandler
consente di specificare un proxy per l'istanza HttpClient
tra le altre varie proprietà del gestore. Per maggiori informazioni, vedi Proxy per client.
Configurazione aggiuntiva
Sono disponibili diverse opzioni di configurazione aggiuntive per il controllo di IHttpClientHandler
:
metodo | Descrizione |
---|---|
AddHttpMessageHandler | Aggiunge un gestore di messaggi aggiuntivo per un oggetto denominato HttpClient . |
AddTypedClient | Configura un'associazione tra TClient e HttpClient denominato associato a IHttpClientBuilder . |
ConfigureHttpClient | Aggiunge un delegato che verrà usato per configurare un oggetto HttpClient denominato. |
ConfigurePrimaryHttpMessageHandler | Configura l'oggetto HttpMessageHandler primario dal contenitore di inserimento delle dipendenze per un oggetto HttpClient denominato. |
RedactLoggedHeaders | Imposta la raccolta di nomi di intestazioni HTTP per cui è necessario che i valori vengano oscurati prima della registrazione. |
SetHandlerLifetime | Imposta l'intervallo di tempo per cui un'istanza di HttpMessageHandler può essere riutilizzata. Per ogni client denominato può essere configurato un valore di durata del gestore. |
UseSocketsHttpHandler | Configura un'istanza SocketsHttpHandler nuova o già aggiunta dal contenitore di inserimento delle dipendenze da usare come gestore primario per un HttpClient denominato. (Solo .NET 5+) |
Uso di IHttpClientFactory insieme a SocketsHttpHandler
L'implementazione SocketsHttpHandler
di HttpMessageHandler
è stata aggiunta in .NET Core 2.1, il che consente di configurare PooledConnectionLifetime
. Questa impostazione viene usata per garantire che il gestore reagisca alle modifiche DNS, pertanto l'uso di SocketsHttpHandler
viene considerato un'alternativa all'uso di IHttpClientFactory
. Per altre informazioni, vedi Linee guida per l'uso dei client HTTP.
Tuttavia, SocketsHttpHandler
e IHttpClientFactory
possono essere usati insieme per migliorare la configurabilità. Usando entrambe queste API, è possibile sfruttare la configurabilità sia a un livello basso (ad esempio, usando LocalCertificateSelectionCallback
per la selezione dinamica dei certificati) sia a un livello elevato (ad esempio, sfruttando l'integrazione di inserimento delle dipendenze e diverse configurazioni client).
Per usare entrambe le API:
- Specificare
SocketsHttpHandler
comePrimaryHandler
tramite ConfigurePrimaryHttpMessageHandler o UseSocketsHttpHandler (solo .NET 5+). - Configurare SocketsHttpHandler.PooledConnectionLifetime in base all'intervallo previsto per l'aggiornamento del DNS; ad esempio a un valore precedentemente incluso in
HandlerLifetime
. - (Opzionale) Dato che
SocketsHttpHandler
gestirà il pool di connessioni e il riciclo, il riciclo del gestore a livello diIHttpClientFactory
non è più necessario. Puoi disabilitarlo impostandoHandlerLifetime
suTimeout.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
Nell'esempio precedente sono stati scelti arbitrariamente 2 minuti a scopo illustrativo, allineandosi a un valore di HandlerLifetime
predefinito. È preferibile scegliere il valore in base alla frequenza prevista delle modifiche al DNS o di altra rete. Per altre informazioni, vedere la sezione Comportamento del DNS nelle linee guida di HttpClient
e la sezione Osservazioni nella documentazione sull'API PooledConnectionLifetime.
Evitare client tipizzati nei servizi singleton
Quando si usa l'approccio client denominato, IHttpClientFactory
viene inserito nei servizi e le istanze HttpClient
vengono create chiamando CreateClient ogni volta che HttpClient
è necessario.
Tuttavia, con l'approccio client tipizzato, i client tipizzati sono oggetti temporanei in genere inseriti nei servizi. Ciò può causare un problema perché un client tipizzato può essere inserito in un servizio singleton.
Importante
I client tipizzati devono essere di breve durata nello stesso senso delle istanze HttpClient
create da IHttpClientFactory
(per altre informazioni, vedere Gestione della durata HttpClient
). Non appena viene creata un'istanza client tipizzata, IHttpClientFactory
non ha alcun controllo su di essa. Se un'istanza client tipizzata viene acquisita in un singleton, può impedire che reagisca alle modifiche DNS, vanificando uno degli scopi di IHttpClientFactory
.
Se devi usare istanze HttpClient
in un servizio singleton, prendi in considerazione le opzioni seguenti:
- Usa invece l'approccio client denominato, inserendo
IHttpClientFactory
nel servizio singleton e ricreando le istanzeHttpClient
quando necessario. - Se è necessario l'approccio client tipizzato, usa
SocketsHttpHandler
conPooledConnectionLifetime
configurato come gestore primario. Per altre informazioni sull'uso diSocketsHttpHandler
conIHttpClientFactory
, vedi la sezione Uso di IHttpClientFactory insieme a SocketsHttpHandler.
Ambiti del gestore di messaggi in IHttpClientFactory
IHttpClientFactory
crea un ambito di inserimento delle dipendenze separato per ogni istanza HttpMessageHandler
. Questi ambiti di inserimento delle dipendenze sono separati dagli ambiti di inserimento delle dipendenze dell'applicazione, ad esempio l’ambito delle richieste in ingresso ASP.NET, l’ambito di inserimento delle dipendenze manuale creato dall'utente, perciò non condivideranno le istanze di servizio di ambito. Gli ambiti del gestore di messaggi sono associati alla durata del gestore e possono avere un livello di durata maggiore degli ambiti dell'applicazione, che possono causare, ad esempio, il riutilizzo della stessa istanza HttpMessageHandler
con le stesse dipendenze con ambito inserito tra diverse richieste in ingresso.
È consigliabile non memorizzare nella cache le informazioni correlate all'ambito (ad esempio i dati di HttpContext
) all'interno delle istanze HttpMessageHandler
e usare le dipendenze di ambito con cautela per evitare la perdita di informazioni riservate.
Se hai bisogno di accedere a un ambito di inserimento delle dipendenze dell'app dal gestore dei messaggi, ad esempio per l'autenticazione incapsulerai la logica con riconoscimento dell'ambito in un DelegatingHandler
temporaneo separato ed eseguirai il wrapping in un'istanza HttpMessageHandler
dalla cache IHttpClientFactory
. Per accedere alla chiamata IHttpMessageHandlerFactory.CreateHandler del gestore per qualsiasi client denominato registrato. In tal caso, creerai un'istanza HttpClient
usando il gestore costruito.
Nell'esempio seguente viene illustrata la creazione di HttpClient
con DelegatingHandler
con riconoscimento dell'ambito:
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);
Un'ulteriore soluzione alternativa può essere seguita da un metodo di estensione per la registrazione di un DelegatingHandler
con riconoscimento dell'ambito e l'override di una registrazione predefinita IHttpClientFactory
da parte di un servizio temporaneo con accesso all'ambito dell'app corrente:
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;
}
Per altre informazioni, vedi l'esempio completo.