Effettuare richieste HTTP usando IHttpClientFactory in ASP.NET Core
Nota
Questa non è la versione più recente di questo articolo. Per la versione corrente, vedere la versione .NET 9 di questo articolo.
Avviso
Questa versione di ASP.NET Core non è più supportata. Per altre informazioni, vedere i criteri di supporto di .NET e .NET Core. Per la versione corrente, vedere la versione .NET 9 di questo articolo.
Importante
Queste informazioni si riferiscono a un prodotto non definitive che può essere modificato in modo sostanziale prima che venga rilasciato commercialmente. Microsoft non riconosce alcuna garanzia, espressa o implicita, in merito alle informazioni qui fornite.
Per la versione corrente, vedere la versione .NET 9 di questo articolo.
Di Kirk Larkin, Steve Gordon, Glenn Condron e Ryan Nowak.
È possibile registrare e usare un'interfaccia IHttpClientFactory per configurare e creare istanze di HttpClient in un'app. IHttpClientFactory
offre i vantaggi seguenti:
- Offre una posizione centrale per la denominazione e la configurazione di istanze di
HttpClient
logiche. Ad esempio, un client denominato github può essere registrato e configurato per accedere a GitHub. Un client predefinito può essere registrato per l'accesso generale. - Codifica il concetto di middleware in uscita tramite delega dei gestori in
HttpClient
. Fornisce estensioni per il middleware basato su Polly per sfruttare i vantaggi della delega dei gestori inHttpClient
. - Gestisce il pooling e la durata delle istanze sottostanti
HttpClientMessageHandler
. La gestione automatica evita problemi DNS comuni (Domain Name System) che si verificano durante la gestione manuale delleHttpClient
durate. - Aggiunge un'esperienza di registrazione configurabile, tramite
ILogger
, per tutte le richieste inviate attraverso i client creati dalla factory.
Il codice di esempio in questa versione di argomento usa System.Text.Json per deserializzare il contenuto JSON restituito nelle risposte HTTP. Per esempi che usano Json.NET
e ReadAsAsync<T>
, usare il selettore di versione per selezionare una versione 2.x di questo argomento.
Modelli di consumo
IHttpClientFactory
può essere usato in un'app in diversi modi:
L'approccio migliore dipende dai requisiti dell'app.
Utilizzo di base
Eseguire la registrazione IHttpClientFactory
chiamando AddHttpClient
in Program.cs
:
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddHttpClient();
È possibile richiedere un oggetto IHttpClientFactory
usando l'inserimento delle dipendenze . Il codice seguente usa IHttpClientFactory
per creare un'istanza di HttpClient
:
public class BasicModel : PageModel
{
private readonly IHttpClientFactory _httpClientFactory;
public BasicModel(IHttpClientFactory httpClientFactory) =>
_httpClientFactory = httpClientFactory;
public IEnumerable<GitHubBranch>? GitHubBranches { get; set; }
public async Task OnGet()
{
var httpRequestMessage = new HttpRequestMessage(
HttpMethod.Get,
"https://api.github.com/repos/dotnet/AspNetCore.Docs/branches")
{
Headers =
{
{ HeaderNames.Accept, "application/vnd.github.v3+json" },
{ HeaderNames.UserAgent, "HttpRequestsSample" }
}
};
var httpClient = _httpClientFactory.CreateClient();
var httpResponseMessage = await httpClient.SendAsync(httpRequestMessage);
if (httpResponseMessage.IsSuccessStatusCode)
{
using var contentStream =
await httpResponseMessage.Content.ReadAsStreamAsync();
GitHubBranches = await JsonSerializer.DeserializeAsync
<IEnumerable<GitHubBranch>>(contentStream);
}
}
}
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
. - Molti
HttpClient
hanno una configurazione diversa.
Specificare la configurazione per un oggetto denominato HttpClient
durante la registrazione in Program.cs
:
builder.Services.AddHttpClient("GitHub", httpClient =>
{
httpClient.BaseAddress = new Uri("https://api.github.com/");
// using Microsoft.Net.Http.Headers;
// The GitHub API requires two headers.
httpClient.DefaultRequestHeaders.Add(
HeaderNames.Accept, "application/vnd.github.v3+json");
httpClient.DefaultRequestHeaders.Add(
HeaderNames.UserAgent, "HttpRequestsSample");
});
Nel codice precedente il client è configurato con:
- Indirizzo di base
https://api.github.com/
. - Due intestazioni necessarie per lavorare con l'API GitHub.
CreateClient
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
:
public class NamedClientModel : PageModel
{
private readonly IHttpClientFactory _httpClientFactory;
public NamedClientModel(IHttpClientFactory httpClientFactory) =>
_httpClientFactory = httpClientFactory;
public IEnumerable<GitHubBranch>? GitHubBranches { get; set; }
public async Task OnGet()
{
var httpClient = _httpClientFactory.CreateClient("GitHub");
var httpResponseMessage = await httpClient.GetAsync(
"repos/dotnet/AspNetCore.Docs/branches");
if (httpResponseMessage.IsSuccessStatusCode)
{
using var contentStream =
await httpResponseMessage.Content.ReadAsStreamAsync();
GitHubBranches = await JsonSerializer.DeserializeAsync
<IEnumerable<GitHubBranch>>(contentStream);
}
}
}
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.
- Offrono 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:
public class GitHubService
{
private readonly HttpClient _httpClient;
public GitHubService(HttpClient httpClient)
{
_httpClient = httpClient;
_httpClient.BaseAddress = new Uri("https://api.github.com/");
// using Microsoft.Net.Http.Headers;
// The GitHub API requires two headers.
_httpClient.DefaultRequestHeaders.Add(
HeaderNames.Accept, "application/vnd.github.v3+json");
_httpClient.DefaultRequestHeaders.Add(
HeaderNames.UserAgent, "HttpRequestsSample");
}
public async Task<IEnumerable<GitHubBranch>?> GetAspNetCoreDocsBranchesAsync() =>
await _httpClient.GetFromJsonAsync<IEnumerable<GitHubBranch>>(
"repos/dotnet/AspNetCore.Docs/branches");
}
Nel codice precedente:
- La configurazione viene spostata nel client tipizzato.
- L'istanza fornita
HttpClient
viene archiviata come campo privato.
È possibile creare metodi specifici dell'API che espongono funzionalità HttpClient
. Ad esempio, il metodo incapsula il GetAspNetCoreDocsBranches
codice per recuperare i rami GitHub della documentazione.
Il codice seguente chiama AddHttpClient in Program.cs
per registrare la GitHubService
classe client tipizzata:
builder.Services.AddHttpClient<GitHubService>();
Il client tipizzato viene registrato come temporaneo nell'inserimento di dipendenze. Nel codice precedente AddHttpClient
registra GitHubService
come servizio temporaneo. Questa registrazione usa un metodo factory per:
- Creare un'istanza di
HttpClient
. - Creare un'istanza di
GitHubService
, passando l'istanza diHttpClient
al relativo costruttore.
Il client tipizzato può essere inserito e usato direttamente:
public class TypedClientModel : PageModel
{
private readonly GitHubService _gitHubService;
public TypedClientModel(GitHubService gitHubService) =>
_gitHubService = gitHubService;
public IEnumerable<GitHubBranch>? GitHubBranches { get; set; }
public async Task OnGet()
{
try
{
GitHubBranches = await _gitHubService.GetAspNetCoreDocsBranchesAsync();
}
catch (HttpRequestException)
{
// ...
}
}
}
La configurazione per un client tipizzato può essere specificata anche durante la registrazione in Program.cs
, anziché nel costruttore del client tipizzato:
builder.Services.AddHttpClient<GitHubService>(httpClient =>
{
httpClient.BaseAddress = new Uri("https://api.github.com/");
// ...
});
Client generati
È possibile usare IHttpClientFactory
in combinazione con librerie di terze parti, ad esempio Refit. Il refit è una REST libreria per .NET. Converte le REST API in interfacce in tempo reale. Chiamare AddRefitClient
per generare un'implementazione dinamica di un'interfaccia, che usa HttpClient
per effettuare le chiamate HTTP esterne.
Un'interfaccia personalizzata rappresenta l'API esterna:
public interface IGitHubClient
{
[Get("/repos/dotnet/AspNetCore.Docs/branches")]
Task<IEnumerable<GitHubBranch>> GetAspNetCoreDocsBranchesAsync();
}
Chiamare AddRefitClient
per generare l'implementazione dinamica e quindi chiamare ConfigureHttpClient
per configurare l'oggetto sottostante HttpClient
:
builder.Services.AddRefitClient<IGitHubClient>()
.ConfigureHttpClient(httpClient =>
{
httpClient.BaseAddress = new Uri("https://api.github.com/");
// using Microsoft.Net.Http.Headers;
// The GitHub API requires two headers.
httpClient.DefaultRequestHeaders.Add(
HeaderNames.Accept, "application/vnd.github.v3+json");
httpClient.DefaultRequestHeaders.Add(
HeaderNames.UserAgent, "HttpRequestsSample");
});
Usare l'inserimento delle dipendenze per accedere all'implementazione dinamica di IGitHubClient
:
public class RefitModel : PageModel
{
private readonly IGitHubClient _gitHubClient;
public RefitModel(IGitHubClient gitHubClient) =>
_gitHubClient = gitHubClient;
public IEnumerable<GitHubBranch>? GitHubBranches { get; set; }
public async Task OnGet()
{
try
{
GitHubBranches = await _gitHubClient.GetAspNetCoreDocsBranchesAsync();
}
catch (ApiException)
{
// ...
}
}
}
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.
L'esempio seguente illustra come effettuare una richiesta HTTP POST:
public async Task CreateItemAsync(TodoItem todoItem)
{
var todoItemJson = new StringContent(
JsonSerializer.Serialize(todoItem),
Encoding.UTF8,
Application.Json); // using static System.Net.Mime.MediaTypeNames;
using var httpResponseMessage =
await _httpClient.PostAsync("/api/TodoItems", todoItemJson);
httpResponseMessage.EnsureSuccessStatusCode();
}
Nel codice precedente il metodo CreateItemAsync
:
- Serializza il parametro
TodoItem
in JSON usandoSystem.Text.Json
. - 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.
- Chiamate 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 SaveItemAsync(TodoItem todoItem)
{
var todoItemJson = new StringContent(
JsonSerializer.Serialize(todoItem),
Encoding.UTF8,
Application.Json);
using var httpResponseMessage =
await _httpClient.PutAsync($"/api/TodoItems/{todoItem.Id}", todoItemJson);
httpResponseMessage.EnsureSuccessStatusCode();
}
Il codice precedente è simile all'esempio POST. Il metodo SaveItemAsync
chiama PutAsync anziché PostAsync
.
L'esempio seguente mostra una richiesta HTTP DELETE:
public async Task DeleteItemAsync(long itemId)
{
using var httpResponseMessage =
await _httpClient.DeleteAsync($"/api/TodoItems/{itemId}");
httpResponseMessage.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.
Middleware per richieste in uscita
HttpClient
ha il concetto di delega dei gestori che possono essere collegati tra loro per le richieste HTTP in uscita. IHttpClientFactory
:
- Semplifica la definizione dei gestori da applicare per ogni client denominato.
- Supporta la registrazione e il concatenamento di più gestori per compilare una pipeline middleware delle richieste in uscita. Ognuno di questi gestori è in grado di eseguire operazioni prima e dopo la richiesta in uscita. Questo modello:
- È simile alla pipeline middleware in ingresso in ASP.NET Core.
- Fornisce un meccanismo per gestire le problematiche trasversali relative alle richieste HTTP, ad esempio:
- caching
- gestione degli errori
- serializzazione
- registrazione
Per creare un gestore di delega:
- Derivare da DelegatingHandler.
- Eseguire l'override di SendAsync. Eseguire il codice prima di passare la richiesta al gestore successivo nella pipeline:
public class ValidateHeaderHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
if (!request.Headers.Contains("X-API-KEY"))
{
return new HttpResponseMessage(HttpStatusCode.BadRequest)
{
Content = new StringContent(
"The API key header X-API-KEY is required.")
};
}
return await base.SendAsync(request, cancellationToken);
}
}
Il codice precedente controlla se l'intestazione X-API-KEY
si trova nella richiesta. Se X-API-KEY
manca, BadRequest viene restituito .
È possibile aggiungere più gestori alla configurazione di con HttpClient
Microsoft.Extensions.DependencyInjection.HttpClientBuilderExtensions.AddHttpMessageHandler:
builder.Services.AddTransient<ValidateHeaderHandler>();
builder.Services.AddHttpClient("HttpMessageHandler")
.AddHttpMessageHandler<ValidateHeaderHandler>();
Nel codice precedente ValidateHeaderHandler
viene registrato nell'inserimento di dipendenze. Dopo la registrazione è possibile chiamare AddHttpMessageHandler, passando il tipo di gestore.
È possibile registrare più gestori nell'ordine di esecuzione. Ogni gestore esegue il wrapping del gestore successivo fino a quando l'elemento HttpClientHandler
finale non esegue la richiesta:
builder.Services.AddTransient<SampleHandler1>();
builder.Services.AddTransient<SampleHandler2>();
builder.Services.AddHttpClient("MultipleHttpMessageHandlers")
.AddHttpMessageHandler<SampleHandler1>()
.AddHttpMessageHandler<SampleHandler2>();
Nel codice precedente viene SampleHandler1
eseguito prima di SampleHandler2
.
Usare l'inserimento delle dipendenze nel middleware delle richieste in uscita
Quando IHttpClientFactory
crea un nuovo gestore di delega, usa l'inserimento delle dipendenze per soddisfare i parametri del costruttore del gestore. IHttpClientFactory
crea un ambito di inserimento delle dipendenze separato per ogni gestore, che può causare un comportamento sorprendente quando un gestore utilizza un servizio con ambito.
Si consideri ad esempio l'interfaccia seguente e la relativa implementazione, che rappresenta un'attività come operazione con un identificatore , OperationId
:
public interface IOperationScoped
{
string OperationId { get; }
}
public class OperationScoped : IOperationScoped
{
public string OperationId { get; } = Guid.NewGuid().ToString()[^4..];
}
Come suggerisce il nome, IOperationScoped
viene registrato con l'inserimento delle dipendenze usando una durata con ambito:
builder.Services.AddScoped<IOperationScoped, OperationScoped>();
Il gestore di delega seguente utilizza e usa IOperationScoped
per impostare l'intestazione X-OPERATION-ID
per la richiesta in uscita:
public class OperationHandler : DelegatingHandler
{
private readonly IOperationScoped _operationScoped;
public OperationHandler(IOperationScoped operationScoped) =>
_operationScoped = operationScoped;
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Headers.Add("X-OPERATION-ID", _operationScoped.OperationId);
return await base.SendAsync(request, cancellationToken);
}
}
HttpRequestsSample
Nel download passare a /Operation
e aggiornare la pagina. Il valore dell'ambito della richiesta cambia per ogni richiesta, ma il valore dell'ambito del gestore cambia solo ogni 5 secondi.
I gestori possono dipendere dai servizi di qualsiasi ambito. I servizi da cui dipendono i gestori vengono eliminati al momento dell'eliminazione del gestore.
Usare uno degli approcci seguenti per condividere lo stato in base alla richiesta con i gestori di messaggi:
- Passare i dati al gestore usando HttpRequestMessage.Options.
- Usare IHttpContextAccessor per accedere alla richiesta corrente.
- Creare un oggetto di archiviazione AsyncLocal<T> personalizzato per passare i dati.
Usare gestori basati su Polly
IHttpClientFactory
si integra con la libreria di terze parti Polly. Polly è una libreria di gestione degli errori temporanei e di resilienza completa per .NET. Consente agli sviluppatori di esprimere criteri quali Retry, Circuit Breaker, Timeout, Bulkhead Isolation e Fallback in modo fluido e thread-safe.
Per consentire l'uso dei criteri Polly con le istanze configurate di HttpClient
sono disponibili metodi di estensione. Le estensioni Polly supportano l'aggiunta di gestori basati su Polly ai client. Polly richiede il pacchetto NuGet Microsoft.Extensions.Http.Polly .
Gestire gli errori temporanei
Gli errori si verificano in genere quando le chiamate HTTP esterne sono temporanee. AddTransientHttpErrorPolicy consente di definire un criterio per gestire gli errori temporanei. Criteri configurati con AddTransientHttpErrorPolicy
gestire le risposte seguenti:
- HttpRequestException
- HTTP 5xx
- HTTP 408
AddTransientHttpErrorPolicy
fornisce l'accesso a un PolicyBuilder
oggetto configurato per gestire gli errori che rappresentano un possibile errore temporaneo:
builder.Services.AddHttpClient("PollyWaitAndRetry")
.AddTransientHttpErrorPolicy(policyBuilder =>
policyBuilder.WaitAndRetryAsync(
3, retryNumber => TimeSpan.FromMilliseconds(600)));
Nel codice precedente viene definito un criterio WaitAndRetryAsync
. Le richieste non riuscite vengono ritentate fino a tre volte con un ritardo di 600 millisecondi tra i tentativi.
Selezionare i criteri in modo dinamico
I metodi di estensione vengono forniti per aggiungere gestori basati su Polly, AddPolicyHandlerad esempio . L'overload seguente AddPolicyHandler
controlla la richiesta per decidere quali criteri applicare:
var timeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(10));
var longTimeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(30));
builder.Services.AddHttpClient("PollyDynamic")
.AddPolicyHandler(httpRequestMessage =>
httpRequestMessage.Method == HttpMethod.Get ? timeoutPolicy : longTimeoutPolicy);
Nel codice precedente, se la richiesta in uscita è una richiesta HTTP GET viene applicato un timeout di 10 secondi. Per qualsiasi altro metodo HTTP viene usato un timeout di 30 secondi.
Aggiungere più gestori Polly
È comune annidare i criteri polly:
builder.Services.AddHttpClient("PollyMultiple")
.AddTransientHttpErrorPolicy(policyBuilder =>
policyBuilder.RetryAsync(3))
.AddTransientHttpErrorPolicy(policyBuilder =>
policyBuilder.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));
Nell'esempio precedente:
- Vengono aggiunti due gestori.
- Il primo gestore usa per aggiungere un criterio di ripetizione dei tentativi AddTransientHttpErrorPolicy . Le richieste non riuscite vengono ritentate fino a tre volte.
- La seconda
AddTransientHttpErrorPolicy
chiamata aggiunge un criterio di interruttore. Ulteriori richieste esterne vengono bloccate per 30 secondi se si verificano in sequenza 5 tentativi non riusciti. I criteri dell'interruttore di circuito sono con stato. Tutte le chiamate tramite questo client condividono lo stesso stato di circuito.
Aggiungere criteri dal registro Polly
Un approccio alla gestione dei criteri usati di frequente consiste nel definirli una volta e registrarli in un elemento PolicyRegistry
. Ad esempio:
var timeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(10));
var longTimeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(30));
var policyRegistry = builder.Services.AddPolicyRegistry();
policyRegistry.Add("Regular", timeoutPolicy);
policyRegistry.Add("Long", longTimeoutPolicy);
builder.Services.AddHttpClient("PollyRegistryRegular")
.AddPolicyHandlerFromRegistry("Regular");
builder.Services.AddHttpClient("PollyRegistryLong")
.AddPolicyHandlerFromRegistry("Long");
Nel codice precedente:
- Due criteri,
Regular
eLong
, vengono aggiunti al Registro di sistema Polly. - AddPolicyHandlerFromRegistry configura i singoli client denominati per l'uso di questi criteri dal Registro di sistema Polly.
Per altre informazioni sulle IHttpClientFactory
integrazioni di Polly, vedere il wiki di Polly.
Gestione di HttpClient e durata
Viene restituita una nuova istanza di HttpClient
per ogni chiamata di CreateClient
per IHttpClientFactory
. Viene HttpMessageHandler creato un oggetto per ogni client denominato. La factory gestisce le durate delle istanze di HttpMessageHandler
.
IHttpClientFactory
esegue il pooling delle istanze di HttpMessageHandler
create dalla factory per ridurre il consumo di risorse. Un'istanza di HttpMessageHandler
può essere riusata dal pool quando si crea una nuova istanza di HttpClient
se la relativa durata non è scaduta.
Il pooling dei gestori è consigliabile in quanto ogni gestore gestisce in genere le proprie connessioni HTTP sottostanti. La creazione di più gestori del necessario può causare ritardi di connessione. Alcuni gestori mantengono anche le connessioni aperte a tempo indefinito, che possono impedire al gestore di reagire alle modifiche DNS (Domain Name System).
La durata del gestore predefinito è di due minuti. Il valore predefinito può essere sottoposto a override per ogni client denominato:
builder.Services.AddHttpClient("HandlerLifetime")
.SetHandlerLifetime(TimeSpan.FromMinutes(5));
HttpClient
Le istanze possono in genere essere considerate come oggetti .NET che non richiedono l'eliminazione. L'eliminazione annulla le richieste in uscita e garantisce che l'istanza di HttpClient
specificata non possa essere usata dopo la chiamata a Dispose. IHttpClientFactory
tiene traccia ed elimina le risorse usate dalle istanze di HttpClient
.
Mantenere una sola istanza di HttpClient
attiva per un lungo periodo di tempo è un modello comune usato prima dell'avvento di IHttpClientFactory
. Questo modello non è più necessario dopo la migrazione a IHttpClientFactory
.
Alternative a IHttpClientFactory
L'uso IHttpClientFactory
in un'app abilitata per l'inserimento delle dipendenze evita:
- Problemi di esaurimento delle risorse eseguendo il pool di
HttpMessageHandler
istanze. - Problemi DNS non aggiornati eseguendo il ciclo delle
HttpMessageHandler
istanze a intervalli regolari.
Esistono modi alternativi per risolvere i problemi precedenti usando un'istanza di lunga durata SocketsHttpHandler .
- Creare un'istanza di
SocketsHttpHandler
all'avvio dell'app e usarla per la vita dell'app. - Configurare PooledConnectionLifetime un valore appropriato in base ai tempi di aggiornamento DNS.
- Creare
HttpClient
istanze usando in basenew HttpClient(handler, disposeHandler: false)
alle esigenze.
Gli approcci precedenti risolveranno i problemi di gestione delle risorse risolti IHttpClientFactory
in modo analogo.
- Le
SocketsHttpHandler
condivisioni traHttpClient
istanze. Questa condivisione impedisce l'esaurimento del socket. - Le
SocketsHttpHandler
connessioni cicliche in base aPooledConnectionLifetime
per evitare problemi DNS non aggiornati.
Registrazione
I client creati tramite IHttpClientFactory
registrano i messaggi di log per tutte le richieste. Abilitare il livello di informazioni appropriato nella configurazione di registrazione per visualizzare i messaggi di log predefiniti. La registrazione aggiuntiva, ad esempio quella delle intestazioni delle richieste, è inclusa solo a livello di traccia.
La categoria di log usata per ogni client include il nome del client. Un client denominato MyNamedClient, ad esempio, registra i messaggi con una categoria "System.Net.Http.HttpClient.MyNamedClient. LogicalHandler". I messaggi con suffisso LogicalHandler sono esterni alla pipeline del gestore delle richieste. Nella richiesta i messaggi vengono registrati prima che qualsiasi altro gestore nella pipeline l'abbia elaborata. Nella risposta i messaggi vengono registrati dopo che qualsiasi altro gestore nella pipeline ha ricevuto la risposta.
La registrazione avviene anche all'interno della pipeline del gestore delle richieste. Nell'esempio MyNamedClient tali messaggi vengono registrati con la categoria di log "System.Net.Http.HttpClient.MyNamedClient. ClientHandler". Per la richiesta, questo si verifica dopo l'esecuzione di tutti gli altri gestori e immediatamente prima dell'invio della richiesta. Nella risposta la registrazione include lo stato della risposta prima di restituirla attraverso la pipeline del gestore.
L'abilitazione della registrazione all'esterno e all'interno della pipeline consente l'ispezione delle modifiche apportate da altri gestori nella pipeline. Ciò può includere modifiche alle intestazioni della richiesta o al codice di stato della risposta.
L'inclusione del nome del client nella categoria di log consente il filtro dei log per client denominati specifici.
Configurare 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. Il delegato viene usato per creare e configurare l'elemento HttpMessageHandler
primario usato dal client:
builder.Services.AddHttpClient("ConfiguredHttpMessageHandler")
.ConfigurePrimaryHttpMessageHandler(() =>
new HttpClientHandler
{
AllowAutoRedirect = true,
UseDefaultCredentials = true
});
Cookie
Le istanze in HttpMessageHandler
pool generano la condivisione di CookieContainer
oggetti. La condivisione di oggetti CookieContainer
imprevisti comporta spesso codice non corretto. Per le app che richiedono cookie, tenere presente quanto segue:
- Disabilitazione della gestione automatica cookie
- Evitando
IHttpClientFactory
Chiamata ConfigurePrimaryHttpMessageHandler per disabilitare la gestione automatica cookie :
builder.Services.AddHttpClient("NoAutomaticCookies")
.ConfigurePrimaryHttpMessageHandler(() =>
new HttpClientHandler
{
UseCookies = false
});
Usare IHttpClientFactory in un'app console
In un'app console aggiungere al progetto i riferimenti ai pacchetti seguenti:
Nell'esempio seguente :
- IHttpClientFactory e
GitHubService
vengono registrati nel contenitore del servizio dell'host generico. GitHubService
viene richiesto dall'inserimento delle dipendenze, che richiede a sua volta un'istanza diIHttpClientFactory
.GitHubService
usaIHttpClientFactory
per creare un'istanza diHttpClient
, che usa per recuperare i rami GitHub della documentazione.
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
var host = new HostBuilder()
.ConfigureServices(services =>
{
services.AddHttpClient();
services.AddTransient<GitHubService>();
})
.Build();
try
{
var gitHubService = host.Services.GetRequiredService<GitHubService>();
var gitHubBranches = await gitHubService.GetAspNetCoreDocsBranchesAsync();
Console.WriteLine($"{gitHubBranches?.Count() ?? 0} GitHub Branches");
if (gitHubBranches is not null)
{
foreach (var gitHubBranch in gitHubBranches)
{
Console.WriteLine($"- {gitHubBranch.Name}");
}
}
}
catch (Exception ex)
{
host.Services.GetRequiredService<ILogger<Program>>()
.LogError(ex, "Unable to load branches from GitHub.");
}
public class GitHubService
{
private readonly IHttpClientFactory _httpClientFactory;
public GitHubService(IHttpClientFactory httpClientFactory) =>
_httpClientFactory = httpClientFactory;
public async Task<IEnumerable<GitHubBranch>?> GetAspNetCoreDocsBranchesAsync()
{
var httpRequestMessage = new HttpRequestMessage(
HttpMethod.Get,
"https://api.github.com/repos/dotnet/AspNetCore.Docs/branches")
{
Headers =
{
{ "Accept", "application/vnd.github.v3+json" },
{ "User-Agent", "HttpRequestsConsoleSample" }
}
};
var httpClient = _httpClientFactory.CreateClient();
var httpResponseMessage = await httpClient.SendAsync(httpRequestMessage);
httpResponseMessage.EnsureSuccessStatusCode();
using var contentStream =
await httpResponseMessage.Content.ReadAsStreamAsync();
return await JsonSerializer.DeserializeAsync
<IEnumerable<GitHubBranch>>(contentStream);
}
}
public record GitHubBranch(
[property: JsonPropertyName("name")] string Name);
Middleware di propagazione delle intestazioni
La propagazione dell'intestazione è un middleware ASP.NET Core per propagare le intestazioni HTTP dalla richiesta in ingresso alle richieste in uscita HttpClient
. Per usare la propagazione dell'intestazione:
Installare il pacchetto Microsoft.AspNetCore.HeaderPropagation .
Configurare la
HttpClient
pipeline e middleware inProgram.cs
:// Add services to the container. builder.Services.AddControllers(); builder.Services.AddHttpClient("PropagateHeaders") .AddHeaderPropagation(); builder.Services.AddHeaderPropagation(options => { options.Headers.Add("X-TraceId"); }); var app = builder.Build(); // Configure the HTTP request pipeline. app.UseHttpsRedirection(); app.UseHeaderPropagation(); app.MapControllers();
Effettuare richieste in uscita usando l'istanza configurata
HttpClient
, che include le intestazioni aggiunte.
Risorse aggiuntive
- Visualizzare o scaricare il codice di esempio (procedura per il download)
- Usare HttpClientFactory per l'implementazione di richieste HTTP resilienti
- Implementazione dei tentativi di chiamate HTTP con backoff esponenziale con i criteri di Polly e HttpClientFactory
- Implementazione dello schema Circuit Breaker
- Come serializzare e deserializzare dati JSON in .NET
Di Kirk Larkin, Steve Gordon, Glenn Condron e Ryan Nowak.
È possibile registrare e usare un'interfaccia IHttpClientFactory per configurare e creare istanze di HttpClient in un'app. IHttpClientFactory
offre i vantaggi seguenti:
- Offre una posizione centrale per la denominazione e la configurazione di istanze di
HttpClient
logiche. Ad esempio, un client denominato github può essere registrato e configurato per accedere a GitHub. Un client predefinito può essere registrato per l'accesso generale. - Codifica il concetto di middleware in uscita tramite delega dei gestori in
HttpClient
. Fornisce estensioni per il middleware basato su Polly per sfruttare i vantaggi della delega dei gestori inHttpClient
. - Gestisce il pooling e la durata delle istanze sottostanti
HttpClientMessageHandler
. La gestione automatica evita problemi DNS comuni (Domain Name System) che si verificano durante la gestione manuale delleHttpClient
durate. - Aggiunge un'esperienza di registrazione configurabile, tramite
ILogger
, per tutte le richieste inviate attraverso i client creati dalla factory.
Visualizzare o scaricare il codice di esempio (procedura per il download).
Il codice di esempio in questa versione di argomento usa System.Text.Json per deserializzare il contenuto JSON restituito nelle risposte HTTP. Per esempi che usano Json.NET
e ReadAsAsync<T>
, usare il selettore di versione per selezionare una versione 2.x di questo argomento.
Modelli di consumo
IHttpClientFactory
può essere usato in un'app in diversi modi:
L'approccio migliore dipende dai requisiti dell'app.
Utilizzo di base
IHttpClientFactory
può essere registrato chiamando AddHttpClient
:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient();
// Remaining code deleted for brevity.
È possibile richiedere un oggetto IHttpClientFactory
usando l'inserimento delle dipendenze . Il codice seguente usa IHttpClientFactory
per creare un'istanza di HttpClient
:
public class BasicUsageModel : PageModel
{
private readonly IHttpClientFactory _clientFactory;
public IEnumerable<GitHubBranch> Branches { get; private set; }
public bool GetBranchesError { get; private set; }
public BasicUsageModel(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task OnGet()
{
var request = new HttpRequestMessage(HttpMethod.Get,
"https://api.github.com/repos/dotnet/AspNetCore.Docs/branches");
request.Headers.Add("Accept", "application/vnd.github.v3+json");
request.Headers.Add("User-Agent", "HttpClientFactory-Sample");
var client = _clientFactory.CreateClient();
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
using var responseStream = await response.Content.ReadAsStreamAsync();
Branches = await JsonSerializer.DeserializeAsync
<IEnumerable<GitHubBranch>>(responseStream);
}
else
{
GetBranchesError = true;
Branches = Array.Empty<GitHubBranch>();
}
}
}
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
. - Molti
HttpClient
hanno una configurazione diversa.
La configurazione per un oggetto denominato HttpClient
può essere specificata durante la registrazione in Startup.ConfigureServices
:
services.AddHttpClient("github", c =>
{
c.BaseAddress = new Uri("https://api.github.com/");
// Github API versioning
c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
// Github requires a user-agent
c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});
Nel codice precedente il client è configurato con:
- Indirizzo di base
https://api.github.com/
. - Due intestazioni necessarie per lavorare con l'API GitHub.
CreateClient
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
:
public class NamedClientModel : PageModel
{
private readonly IHttpClientFactory _clientFactory;
public IEnumerable<GitHubPullRequest> PullRequests { get; private set; }
public bool GetPullRequestsError { get; private set; }
public bool HasPullRequests => PullRequests.Any();
public NamedClientModel(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task OnGet()
{
var request = new HttpRequestMessage(HttpMethod.Get,
"repos/dotnet/AspNetCore.Docs/pulls");
var client = _clientFactory.CreateClient("github");
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
using var responseStream = await response.Content.ReadAsStreamAsync();
PullRequests = await JsonSerializer.DeserializeAsync
<IEnumerable<GitHubPullRequest>>(responseStream);
}
else
{
GetPullRequestsError = true;
PullRequests = Array.Empty<GitHubPullRequest>();
}
}
}
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.
- Offrono 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:
public class GitHubService
{
public HttpClient Client { get; }
public GitHubService(HttpClient client)
{
client.BaseAddress = new Uri("https://api.github.com/");
// GitHub API versioning
client.DefaultRequestHeaders.Add("Accept",
"application/vnd.github.v3+json");
// GitHub requires a user-agent
client.DefaultRequestHeaders.Add("User-Agent",
"HttpClientFactory-Sample");
Client = client;
}
public async Task<IEnumerable<GitHubIssue>> GetAspNetDocsIssues()
{
return await Client.GetFromJsonAsync<IEnumerable<GitHubIssue>>(
"/repos/aspnet/AspNetCore.Docs/issues?state=open&sort=created&direction=desc");
}
}
Nel codice precedente:
- La configurazione viene spostata nel client tipizzato.
- L'oggetto
HttpClient
viene esposto come una proprietà pubblica.
È possibile creare metodi specifici dell'API che espongono funzionalità HttpClient
. Ad esempio, il metodo incapsula il GetAspNetDocsIssues
codice per recuperare i problemi aperti.
Il codice seguente chiama AddHttpClient in Startup.ConfigureServices
per registrare una classe client tipizzata:
services.AddHttpClient<GitHubService>();
Il client tipizzato viene registrato come temporaneo nell'inserimento di dipendenze. Nel codice precedente AddHttpClient
registra GitHubService
come servizio temporaneo. Questa registrazione usa un metodo factory per:
- Creare un'istanza di
HttpClient
. - Creare un'istanza di
GitHubService
, passando l'istanza diHttpClient
al relativo costruttore.
Il client tipizzato può essere inserito e usato direttamente:
public class TypedClientModel : PageModel
{
private readonly GitHubService _gitHubService;
public IEnumerable<GitHubIssue> LatestIssues { get; private set; }
public bool HasIssue => LatestIssues.Any();
public bool GetIssuesError { get; private set; }
public TypedClientModel(GitHubService gitHubService)
{
_gitHubService = gitHubService;
}
public async Task OnGet()
{
try
{
LatestIssues = await _gitHubService.GetAspNetDocsIssues();
}
catch(HttpRequestException)
{
GetIssuesError = true;
LatestIssues = Array.Empty<GitHubIssue>();
}
}
}
La configurazione per un client tipizzato può essere specificata durante la registrazione in Startup.ConfigureServices
, anziché nel costruttore del client tipizzato:
services.AddHttpClient<RepoService>(c =>
{
c.BaseAddress = new Uri("https://api.github.com/");
c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});
HttpClient
Può essere incapsulato all'interno di un client tipizzato. Anziché esporlo come proprietà, definire un metodo che chiama l'istanza HttpClient
internamente:
public class RepoService
{
// _httpClient isn't exposed publicly
private readonly HttpClient _httpClient;
public RepoService(HttpClient client)
{
_httpClient = client;
}
public async Task<IEnumerable<string>> GetRepos()
{
var response = await _httpClient.GetAsync("aspnet/repos");
response.EnsureSuccessStatusCode();
using var responseStream = await response.Content.ReadAsStreamAsync();
return await JsonSerializer.DeserializeAsync
<IEnumerable<string>>(responseStream);
}
}
Nel codice precedente, l'oggetto HttpClient
viene archiviato in un campo privato. L'accesso HttpClient
a è tramite il metodo pubblico GetRepos
.
Client generati
È possibile usare IHttpClientFactory
in combinazione con librerie di terze parti, ad esempio Refit. Il refit è una REST libreria per .NET. Converte le REST API in interfacce in tempo reale. Un'implementazione dell'interfaccia viene generata dinamicamente da RestService
, usando HttpClient
per effettuare le chiamate HTTP esterne.
Per rappresentare l'API esterna e la relativa risposta vengono definite un'interfaccia e una risposta:
public interface IHelloClient
{
[Get("/helloworld")]
Task<Reply> GetMessageAsync();
}
public class Reply
{
public string Message { get; set; }
}
È possibile aggiungere un client tipizzato usando Refit per generare l'implementazione:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient("hello", c =>
{
c.BaseAddress = new Uri("http://localhost:5000");
})
.AddTypedClient(c => Refit.RestService.For<IHelloClient>(c));
services.AddControllers();
}
L'interfaccia definita può essere usata dove necessario, con l'implementazione offerta dall'inserimento di dipendenze e da Refit:
[ApiController]
public class ValuesController : ControllerBase
{
private readonly IHelloClient _client;
public ValuesController(IHelloClient client)
{
_client = client;
}
[HttpGet("/")]
public async Task<ActionResult<Reply>> Index()
{
return await _client.GetMessageAsync();
}
}
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.
L'esempio seguente illustra come effettuare una richiesta HTTP POST:
public async Task CreateItemAsync(TodoItem todoItem)
{
var todoItemJson = new StringContent(
JsonSerializer.Serialize(todoItem, _jsonSerializerOptions),
Encoding.UTF8,
"application/json");
using var httpResponse =
await _httpClient.PostAsync("/api/TodoItems", todoItemJson);
httpResponse.EnsureSuccessStatusCode();
}
Nel codice precedente il metodo CreateItemAsync
:
- Serializza il parametro
TodoItem
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 SaveItemAsync(TodoItem todoItem)
{
var todoItemJson = new StringContent(
JsonSerializer.Serialize(todoItem),
Encoding.UTF8,
"application/json");
using var httpResponse =
await _httpClient.PutAsync($"/api/TodoItems/{todoItem.Id}", todoItemJson);
httpResponse.EnsureSuccessStatusCode();
}
Il codice precedente è molto simile all'esempio POST. Il metodo SaveItemAsync
chiama PutAsync anziché PostAsync
.
L'esempio seguente mostra una richiesta HTTP DELETE:
public async Task DeleteItemAsync(long itemId)
{
using var httpResponse =
await _httpClient.DeleteAsync($"/api/TodoItems/{itemId}");
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.
Middleware per richieste in uscita
HttpClient
ha il concetto di delega dei gestori che possono essere collegati tra loro per le richieste HTTP in uscita. IHttpClientFactory
:
- Semplifica la definizione dei gestori da applicare per ogni client denominato.
- Supporta la registrazione e il concatenamento di più gestori per compilare una pipeline middleware delle richieste in uscita. Ognuno di questi gestori è in grado di eseguire operazioni prima e dopo la richiesta in uscita. Questo modello:
- È simile alla pipeline middleware in ingresso in ASP.NET Core.
- Fornisce un meccanismo per gestire le problematiche trasversali relative alle richieste HTTP, ad esempio:
- caching
- gestione degli errori
- serializzazione
- registrazione
Per creare un gestore di delega:
- Derivare da DelegatingHandler.
- Eseguire l'override di SendAsync. Eseguire il codice prima di passare la richiesta al gestore successivo nella pipeline:
public class ValidateHeaderHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
if (!request.Headers.Contains("X-API-KEY"))
{
return new HttpResponseMessage(HttpStatusCode.BadRequest)
{
Content = new StringContent(
"You must supply an API key header called X-API-KEY")
};
}
return await base.SendAsync(request, cancellationToken);
}
}
Il codice precedente controlla se l'intestazione X-API-KEY
si trova nella richiesta. Se X-API-KEY
manca, BadRequest viene restituito .
È possibile aggiungere più gestori alla configurazione di con HttpClient
Microsoft.Extensions.DependencyInjection.HttpClientBuilderExtensions.AddHttpMessageHandler:
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<ValidateHeaderHandler>();
services.AddHttpClient("externalservice", c =>
{
// Assume this is an "external" service which requires an API KEY
c.BaseAddress = new Uri("https://localhost:5001/");
})
.AddHttpMessageHandler<ValidateHeaderHandler>();
// Remaining code deleted for brevity.
Nel codice precedente ValidateHeaderHandler
viene registrato nell'inserimento di dipendenze. Dopo la registrazione è possibile chiamare AddHttpMessageHandler, passando il tipo di gestore.
È possibile registrare più gestori nell'ordine di esecuzione. Ogni gestore esegue il wrapping del gestore successivo fino a quando l'elemento HttpClientHandler
finale non esegue la richiesta:
services.AddTransient<SecureRequestHandler>();
services.AddTransient<RequestDataHandler>();
services.AddHttpClient("clientwithhandlers")
// This handler is on the outside and called first during the
// request, last during the response.
.AddHttpMessageHandler<SecureRequestHandler>()
// This handler is on the inside, closest to the request being
// sent.
.AddHttpMessageHandler<RequestDataHandler>();
Usare l'inserimento delle dipendenze nel middleware delle richieste in uscita
Quando IHttpClientFactory
crea un nuovo gestore di delega, usa l'inserimento delle dipendenze per soddisfare i parametri del costruttore del gestore. IHttpClientFactory
crea un ambito di inserimento delle dipendenze separato per ogni gestore, che può causare un comportamento sorprendente quando un gestore utilizza un servizio con ambito.
Si consideri ad esempio l'interfaccia seguente e la relativa implementazione, che rappresenta un'attività come operazione con un identificatore , OperationId
:
public interface IOperationScoped
{
string OperationId { get; }
}
public class OperationScoped : IOperationScoped
{
public string OperationId { get; } = Guid.NewGuid().ToString()[^4..];
}
Come suggerisce il nome, IOperationScoped
viene registrato con l'inserimento delle dipendenze usando una durata con ambito:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<TodoContext>(options =>
options.UseInMemoryDatabase("TodoItems"));
services.AddHttpContextAccessor();
services.AddHttpClient<TodoClient>((sp, httpClient) =>
{
var httpRequest = sp.GetRequiredService<IHttpContextAccessor>().HttpContext.Request;
// For sample purposes, assume TodoClient is used in the context of an incoming request.
httpClient.BaseAddress = new Uri(UriHelper.BuildAbsolute(httpRequest.Scheme,
httpRequest.Host, httpRequest.PathBase));
httpClient.Timeout = TimeSpan.FromSeconds(5);
});
services.AddScoped<IOperationScoped, OperationScoped>();
services.AddTransient<OperationHandler>();
services.AddTransient<OperationResponseHandler>();
services.AddHttpClient("Operation")
.AddHttpMessageHandler<OperationHandler>()
.AddHttpMessageHandler<OperationResponseHandler>()
.SetHandlerLifetime(TimeSpan.FromSeconds(5));
services.AddControllers();
services.AddRazorPages();
}
Il gestore di delega seguente utilizza e usa IOperationScoped
per impostare l'intestazione X-OPERATION-ID
per la richiesta in uscita:
public class OperationHandler : DelegatingHandler
{
private readonly IOperationScoped _operationService;
public OperationHandler(IOperationScoped operationScoped)
{
_operationService = operationScoped;
}
protected async override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Headers.Add("X-OPERATION-ID", _operationService.OperationId);
return await base.SendAsync(request, cancellationToken);
}
}
HttpRequestsSample
Nel download passare a /Operation
e aggiornare la pagina. Il valore dell'ambito della richiesta cambia per ogni richiesta, ma il valore dell'ambito del gestore cambia solo ogni 5 secondi.
I gestori possono dipendere dai servizi di qualsiasi ambito. I servizi da cui dipendono i gestori vengono eliminati al momento dell'eliminazione del gestore.
Usare uno degli approcci seguenti per condividere lo stato in base alla richiesta con i gestori di messaggi:
- Passare i dati al gestore usando HttpRequestMessage.Options.
- Usare IHttpContextAccessor per accedere alla richiesta corrente.
- Creare un oggetto di archiviazione AsyncLocal<T> personalizzato per passare i dati.
Usare gestori basati su Polly
IHttpClientFactory
si integra con la libreria di terze parti Polly. Polly è una libreria di gestione degli errori temporanei e di resilienza completa per .NET. Consente agli sviluppatori di esprimere criteri quali Retry, Circuit Breaker, Timeout, Bulkhead Isolation e Fallback in modo fluido e thread-safe.
Per consentire l'uso dei criteri Polly con le istanze configurate di HttpClient
sono disponibili metodi di estensione. Le estensioni Polly supportano l'aggiunta di gestori basati su Polly ai client. Polly richiede il pacchetto NuGet Microsoft.Extensions.Http.Polly .
Gestire gli errori temporanei
Gli errori si verificano in genere quando le chiamate HTTP esterne sono temporanee. AddTransientHttpErrorPolicy consente di definire un criterio per gestire gli errori temporanei. Criteri configurati con AddTransientHttpErrorPolicy
gestire le risposte seguenti:
- HttpRequestException
- HTTP 5xx
- HTTP 408
AddTransientHttpErrorPolicy
fornisce l'accesso a un PolicyBuilder
oggetto configurato per gestire gli errori che rappresentano un possibile errore temporaneo:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient<UnreliableEndpointCallerService>()
.AddTransientHttpErrorPolicy(p =>
p.WaitAndRetryAsync(3, _ => TimeSpan.FromMilliseconds(600)));
// Remaining code deleted for brevity.
Nel codice precedente viene definito un criterio WaitAndRetryAsync
. Le richieste non riuscite vengono ritentate fino a tre volte con un ritardo di 600 millisecondi tra i tentativi.
Selezionare i criteri in modo dinamico
I metodi di estensione vengono forniti per aggiungere gestori basati su Polly, AddPolicyHandlerad esempio . L'overload seguente AddPolicyHandler
controlla la richiesta per decidere quali criteri applicare:
var timeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(10));
var longTimeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(30));
services.AddHttpClient("conditionalpolicy")
// Run some code to select a policy based on the request
.AddPolicyHandler(request =>
request.Method == HttpMethod.Get ? timeout : longTimeout);
Nel codice precedente, se la richiesta in uscita è una richiesta HTTP GET viene applicato un timeout di 10 secondi. Per qualsiasi altro metodo HTTP viene usato un timeout di 30 secondi.
Aggiungere più gestori Polly
È comune annidare i criteri polly:
services.AddHttpClient("multiplepolicies")
.AddTransientHttpErrorPolicy(p => p.RetryAsync(3))
.AddTransientHttpErrorPolicy(
p => p.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));
Nell'esempio precedente:
- Vengono aggiunti due gestori.
- Il primo gestore usa per aggiungere un criterio di ripetizione dei tentativi AddTransientHttpErrorPolicy . Le richieste non riuscite vengono ritentate fino a tre volte.
- La seconda
AddTransientHttpErrorPolicy
chiamata aggiunge un criterio di interruttore. Ulteriori richieste esterne vengono bloccate per 30 secondi se si verificano in sequenza 5 tentativi non riusciti. I criteri dell'interruttore di circuito sono con stato. Tutte le chiamate tramite questo client condividono lo stesso stato di circuito.
Aggiungere criteri dal registro Polly
Un approccio alla gestione dei criteri usati di frequente consiste nel definirli una volta e registrarli in un elemento PolicyRegistry
.
Nel codice seguente:
- Vengono aggiunti i criteri "regolari" e "long".
- AddPolicyHandlerFromRegistry aggiunge i criteri "regolari" e "long" dal Registro di sistema.
public void ConfigureServices(IServiceCollection services)
{
var timeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(10));
var longTimeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(30));
var registry = services.AddPolicyRegistry();
registry.Add("regular", timeout);
registry.Add("long", longTimeout);
services.AddHttpClient("regularTimeoutHandler")
.AddPolicyHandlerFromRegistry("regular");
services.AddHttpClient("longTimeoutHandler")
.AddPolicyHandlerFromRegistry("long");
// Remaining code deleted for brevity.
Per altre informazioni sulle IHttpClientFactory
integrazioni di Polly, vedere il wiki di Polly.
Gestione di HttpClient e durata
Viene restituita una nuova istanza di HttpClient
per ogni chiamata di CreateClient
per IHttpClientFactory
. Viene HttpMessageHandler creato un oggetto per ogni client denominato. La factory gestisce le durate delle istanze di HttpMessageHandler
.
IHttpClientFactory
esegue il pooling delle istanze di HttpMessageHandler
create dalla factory per ridurre il consumo di risorse. Un'istanza di HttpMessageHandler
può essere riusata dal pool quando si crea una nuova istanza di HttpClient
se la relativa durata non è scaduta.
Il pooling dei gestori è consigliabile in quanto ogni gestore gestisce in genere le proprie connessioni HTTP sottostanti. La creazione di più gestori del necessario può causare ritardi di connessione. Alcuni gestori mantengono anche le connessioni aperte a tempo indefinito, che possono impedire al gestore di reagire alle modifiche DNS (Domain Name System).
La durata del gestore predefinito è di due minuti. Il valore predefinito può essere sottoposto a override per ogni client denominato:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient("extendedhandlerlifetime")
.SetHandlerLifetime(TimeSpan.FromMinutes(5));
// Remaining code deleted for brevity.
HttpClient
Le istanze possono in genere essere considerate come oggetti .NET che non richiedono l'eliminazione. L'eliminazione annulla le richieste in uscita e garantisce che l'istanza di HttpClient
specificata non possa essere usata dopo la chiamata a Dispose. IHttpClientFactory
tiene traccia ed elimina le risorse usate dalle istanze di HttpClient
.
Mantenere una sola istanza di HttpClient
attiva per un lungo periodo di tempo è un modello comune usato prima dell'avvento di IHttpClientFactory
. Questo modello non è più necessario dopo la migrazione a IHttpClientFactory
.
Alternative a IHttpClientFactory
L'uso IHttpClientFactory
in un'app abilitata per l'inserimento delle dipendenze evita:
- Problemi di esaurimento delle risorse eseguendo il pool di
HttpMessageHandler
istanze. - Problemi DNS non aggiornati eseguendo il ciclo delle
HttpMessageHandler
istanze a intervalli regolari.
Esistono modi alternativi per risolvere i problemi precedenti usando un'istanza di lunga durata SocketsHttpHandler .
- Creare un'istanza di
SocketsHttpHandler
all'avvio dell'app e usarla per la vita dell'app. - Configurare PooledConnectionLifetime un valore appropriato in base ai tempi di aggiornamento DNS.
- Creare
HttpClient
istanze usando in basenew HttpClient(handler, disposeHandler: false)
alle esigenze.
Gli approcci precedenti risolveranno i problemi di gestione delle risorse risolti IHttpClientFactory
in modo analogo.
- Le
SocketsHttpHandler
condivisioni traHttpClient
istanze. Questa condivisione impedisce l'esaurimento del socket. - Le
SocketsHttpHandler
connessioni cicliche in base aPooledConnectionLifetime
per evitare problemi DNS non aggiornati.
Cookie
Le istanze in HttpMessageHandler
pool generano la condivisione di CookieContainer
oggetti. La condivisione di oggetti CookieContainer
imprevisti comporta spesso codice non corretto. Per le app che richiedono cookie, tenere presente quanto segue:
- Disabilitazione della gestione automatica cookie
- Evitando
IHttpClientFactory
Chiamata ConfigurePrimaryHttpMessageHandler per disabilitare la gestione automatica cookie :
services.AddHttpClient("configured-disable-automatic-cookies")
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler()
{
UseCookies = false,
};
});
Registrazione
I client creati tramite IHttpClientFactory
registrano i messaggi di log per tutte le richieste. Abilitare il livello di informazioni appropriato nella configurazione di registrazione per visualizzare i messaggi di log predefiniti. La registrazione aggiuntiva, ad esempio quella delle intestazioni delle richieste, è inclusa solo a livello di traccia.
La categoria di log usata per ogni client include il nome del client. Un client denominato MyNamedClient, ad esempio, registra i messaggi con una categoria "System.Net.Http.HttpClient.MyNamedClient. LogicalHandler". I messaggi con suffisso LogicalHandler sono esterni alla pipeline del gestore delle richieste. Nella richiesta i messaggi vengono registrati prima che qualsiasi altro gestore nella pipeline l'abbia elaborata. Nella risposta i messaggi vengono registrati dopo che qualsiasi altro gestore nella pipeline ha ricevuto la risposta.
La registrazione avviene anche all'interno della pipeline del gestore delle richieste. Nell'esempio MyNamedClient tali messaggi vengono registrati con la categoria di log "System.Net.Http.HttpClient.MyNamedClient. ClientHandler". Per la richiesta, questo si verifica dopo l'esecuzione di tutti gli altri gestori e immediatamente prima dell'invio della richiesta. Nella risposta la registrazione include lo stato della risposta prima di restituirla attraverso la pipeline del gestore.
L'abilitazione della registrazione all'esterno e all'interno della pipeline consente l'ispezione delle modifiche apportate da altri gestori nella pipeline. Ciò può includere modifiche alle intestazioni della richiesta o al codice di stato della risposta.
L'inclusione del nome del client nella categoria di log consente il filtro dei log per client denominati specifici.
Configurare 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. Il delegato viene usato per creare e configurare l'elemento HttpMessageHandler
primario usato dal client:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient("configured-inner-handler")
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler()
{
AllowAutoRedirect = false,
UseDefaultCredentials = true
};
});
// Remaining code deleted for brevity.
Usare IHttpClientFactory in un'app console
In un'app console aggiungere al progetto i riferimenti ai pacchetti seguenti:
Nell'esempio seguente :
- IHttpClientFactory è registrato nel contenitore di servizi dell'host generico.
MyService
crea un'istanza della factory client dal servizio, che viene usata per creare una classeHttpClient
.HttpClient
viene usato per recuperare una pagina Web.Main
crea un ambito per eseguire il metodoGetPage
del servizio e scrivere i primi 500 caratteri del contenuto della pagina Web nella console.
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
class Program
{
static async Task<int> Main(string[] args)
{
var builder = new HostBuilder()
.ConfigureServices((hostContext, services) =>
{
services.AddHttpClient();
services.AddTransient<IMyService, MyService>();
}).UseConsoleLifetime();
var host = builder.Build();
try
{
var myService = host.Services.GetRequiredService<IMyService>();
var pageContent = await myService.GetPage();
Console.WriteLine(pageContent.Substring(0, 500));
}
catch (Exception ex)
{
var logger = host.Services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred.");
}
return 0;
}
public interface IMyService
{
Task<string> GetPage();
}
public class MyService : IMyService
{
private readonly IHttpClientFactory _clientFactory;
public MyService(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task<string> GetPage()
{
// Content from BBC One: Dr. Who website (©BBC)
var request = new HttpRequestMessage(HttpMethod.Get,
"https://www.bbc.co.uk/programmes/b006q2x0");
var client = _clientFactory.CreateClient();
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
return await response.Content.ReadAsStringAsync();
}
else
{
return $"StatusCode: {response.StatusCode}";
}
}
}
}
Middleware di propagazione delle intestazioni
La propagazione dell'intestazione è un middleware ASP.NET Core per propagare le intestazioni HTTP dalla richiesta in ingresso alle richieste client HTTP in uscita. Per usare la propagazione dell'intestazione:
Fare riferimento al pacchetto Microsoft.AspNetCore.HeaderPropagation .
Configurare il middleware e
HttpClient
inStartup
:public void ConfigureServices(IServiceCollection services) { services.AddControllers(); services.AddHttpClient("MyForwardingClient").AddHeaderPropagation(); services.AddHeaderPropagation(options => { options.Headers.Add("X-TraceId"); }); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseHttpsRedirection(); app.UseHeaderPropagation(); app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); }
Il client include le intestazioni configurate nelle richieste in uscita:
var client = clientFactory.CreateClient("MyForwardingClient"); var response = client.GetAsync(...);
Risorse aggiuntive
Di Kirk Larkin, Steve Gordon, Glenn Condron e Ryan Nowak.
È possibile registrare e usare un'interfaccia IHttpClientFactory per configurare e creare istanze di HttpClient in un'app. IHttpClientFactory
offre i vantaggi seguenti:
- Offre una posizione centrale per la denominazione e la configurazione di istanze di
HttpClient
logiche. Ad esempio, un client denominato github può essere registrato e configurato per accedere a GitHub. Un client predefinito può essere registrato per l'accesso generale. - Codifica il concetto di middleware in uscita tramite delega dei gestori in
HttpClient
. Fornisce estensioni per il middleware basato su Polly per sfruttare i vantaggi della delega dei gestori inHttpClient
. - Gestisce il pooling e la durata delle istanze sottostanti
HttpClientMessageHandler
. La gestione automatica evita problemi DNS comuni (Domain Name System) che si verificano durante la gestione manuale delleHttpClient
durate. - Aggiunge un'esperienza di registrazione configurabile, tramite
ILogger
, per tutte le richieste inviate attraverso i client creati dalla factory.
Visualizzare o scaricare il codice di esempio (procedura per il download).
Il codice di esempio in questa versione di argomento usa System.Text.Json per deserializzare il contenuto JSON restituito nelle risposte HTTP. Per esempi che usano Json.NET
e ReadAsAsync<T>
, usare il selettore di versione per selezionare una versione 2.x di questo argomento.
Modelli di consumo
IHttpClientFactory
può essere usato in un'app in diversi modi:
L'approccio migliore dipende dai requisiti dell'app.
Utilizzo di base
IHttpClientFactory
può essere registrato chiamando AddHttpClient
:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient();
// Remaining code deleted for brevity.
È possibile richiedere un oggetto IHttpClientFactory
usando l'inserimento delle dipendenze . Il codice seguente usa IHttpClientFactory
per creare un'istanza di HttpClient
:
public class BasicUsageModel : PageModel
{
private readonly IHttpClientFactory _clientFactory;
public IEnumerable<GitHubBranch> Branches { get; private set; }
public bool GetBranchesError { get; private set; }
public BasicUsageModel(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task OnGet()
{
var request = new HttpRequestMessage(HttpMethod.Get,
"https://api.github.com/repos/dotnet/AspNetCore.Docs/branches");
request.Headers.Add("Accept", "application/vnd.github.v3+json");
request.Headers.Add("User-Agent", "HttpClientFactory-Sample");
var client = _clientFactory.CreateClient();
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
using var responseStream = await response.Content.ReadAsStreamAsync();
Branches = await JsonSerializer.DeserializeAsync
<IEnumerable<GitHubBranch>>(responseStream);
}
else
{
GetBranchesError = true;
Branches = Array.Empty<GitHubBranch>();
}
}
}
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
. - Molti
HttpClient
hanno una configurazione diversa.
La configurazione per un oggetto denominato HttpClient
può essere specificata durante la registrazione in Startup.ConfigureServices
:
services.AddHttpClient("github", c =>
{
c.BaseAddress = new Uri("https://api.github.com/");
// Github API versioning
c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
// Github requires a user-agent
c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});
Nel codice precedente il client è configurato con:
- Indirizzo di base
https://api.github.com/
. - Due intestazioni necessarie per lavorare con l'API GitHub.
CreateClient
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
:
public class NamedClientModel : PageModel
{
private readonly IHttpClientFactory _clientFactory;
public IEnumerable<GitHubPullRequest> PullRequests { get; private set; }
public bool GetPullRequestsError { get; private set; }
public bool HasPullRequests => PullRequests.Any();
public NamedClientModel(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task OnGet()
{
var request = new HttpRequestMessage(HttpMethod.Get,
"repos/dotnet/AspNetCore.Docs/pulls");
var client = _clientFactory.CreateClient("github");
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
using var responseStream = await response.Content.ReadAsStreamAsync();
PullRequests = await JsonSerializer.DeserializeAsync
<IEnumerable<GitHubPullRequest>>(responseStream);
}
else
{
GetPullRequestsError = true;
PullRequests = Array.Empty<GitHubPullRequest>();
}
}
}
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.
- Offrono 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:
public class GitHubService
{
public HttpClient Client { get; }
public GitHubService(HttpClient client)
{
client.BaseAddress = new Uri("https://api.github.com/");
// GitHub API versioning
client.DefaultRequestHeaders.Add("Accept",
"application/vnd.github.v3+json");
// GitHub requires a user-agent
client.DefaultRequestHeaders.Add("User-Agent",
"HttpClientFactory-Sample");
Client = client;
}
public async Task<IEnumerable<GitHubIssue>> GetAspNetDocsIssues()
{
var response = await Client.GetAsync(
"/repos/dotnet/AspNetCore.Docs/issues?state=open&sort=created&direction=desc");
response.EnsureSuccessStatusCode();
using var responseStream = await response.Content.ReadAsStreamAsync();
return await JsonSerializer.DeserializeAsync
<IEnumerable<GitHubIssue>>(responseStream);
}
}
Per visualizzare i commenti del codice tradotti in lingue diverse dall'inglese, segnalarlo in questo problema di discussione su GitHub.
Nel codice precedente:
- La configurazione viene spostata nel client tipizzato.
- L'oggetto
HttpClient
viene esposto come una proprietà pubblica.
È possibile creare metodi specifici dell'API che espongono funzionalità HttpClient
. Ad esempio, il metodo incapsula il GetAspNetDocsIssues
codice per recuperare i problemi aperti.
Il codice seguente chiama AddHttpClient in Startup.ConfigureServices
per registrare una classe client tipizzata:
services.AddHttpClient<GitHubService>();
Il client tipizzato viene registrato come temporaneo nell'inserimento di dipendenze. Nel codice precedente AddHttpClient
registra GitHubService
come servizio temporaneo. Questa registrazione usa un metodo factory per:
- Creare un'istanza di
HttpClient
. - Creare un'istanza di
GitHubService
, passando l'istanza diHttpClient
al relativo costruttore.
Il client tipizzato può essere inserito e usato direttamente:
public class TypedClientModel : PageModel
{
private readonly GitHubService _gitHubService;
public IEnumerable<GitHubIssue> LatestIssues { get; private set; }
public bool HasIssue => LatestIssues.Any();
public bool GetIssuesError { get; private set; }
public TypedClientModel(GitHubService gitHubService)
{
_gitHubService = gitHubService;
}
public async Task OnGet()
{
try
{
LatestIssues = await _gitHubService.GetAspNetDocsIssues();
}
catch(HttpRequestException)
{
GetIssuesError = true;
LatestIssues = Array.Empty<GitHubIssue>();
}
}
}
La configurazione per un client tipizzato può essere specificata durante la registrazione in Startup.ConfigureServices
, anziché nel costruttore del client tipizzato:
services.AddHttpClient<RepoService>(c =>
{
c.BaseAddress = new Uri("https://api.github.com/");
c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});
HttpClient
Può essere incapsulato all'interno di un client tipizzato. Anziché esporlo come proprietà, definire un metodo che chiama l'istanza HttpClient
internamente:
public class RepoService
{
// _httpClient isn't exposed publicly
private readonly HttpClient _httpClient;
public RepoService(HttpClient client)
{
_httpClient = client;
}
public async Task<IEnumerable<string>> GetRepos()
{
var response = await _httpClient.GetAsync("aspnet/repos");
response.EnsureSuccessStatusCode();
using var responseStream = await response.Content.ReadAsStreamAsync();
return await JsonSerializer.DeserializeAsync
<IEnumerable<string>>(responseStream);
}
}
Nel codice precedente, l'oggetto HttpClient
viene archiviato in un campo privato. L'accesso HttpClient
a è tramite il metodo pubblico GetRepos
.
Client generati
È possibile usare IHttpClientFactory
in combinazione con librerie di terze parti, ad esempio Refit. Il refit è una REST libreria per .NET. Converte le REST API in interfacce in tempo reale. Un'implementazione dell'interfaccia viene generata dinamicamente da RestService
, usando HttpClient
per effettuare le chiamate HTTP esterne.
Per rappresentare l'API esterna e la relativa risposta vengono definite un'interfaccia e una risposta:
public interface IHelloClient
{
[Get("/helloworld")]
Task<Reply> GetMessageAsync();
}
public class Reply
{
public string Message { get; set; }
}
È possibile aggiungere un client tipizzato usando Refit per generare l'implementazione:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient("hello", c =>
{
c.BaseAddress = new Uri("http://localhost:5000");
})
.AddTypedClient(c => Refit.RestService.For<IHelloClient>(c));
services.AddControllers();
}
L'interfaccia definita può essere usata dove necessario, con l'implementazione offerta dall'inserimento di dipendenze e da Refit:
[ApiController]
public class ValuesController : ControllerBase
{
private readonly IHelloClient _client;
public ValuesController(IHelloClient client)
{
_client = client;
}
[HttpGet("/")]
public async Task<ActionResult<Reply>> Index()
{
return await _client.GetMessageAsync();
}
}
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.
L'esempio seguente illustra come effettuare una richiesta HTTP POST:
public async Task CreateItemAsync(TodoItem todoItem)
{
var todoItemJson = new StringContent(
JsonSerializer.Serialize(todoItem, _jsonSerializerOptions),
Encoding.UTF8,
"application/json");
using var httpResponse =
await _httpClient.PostAsync("/api/TodoItems", todoItemJson);
httpResponse.EnsureSuccessStatusCode();
}
Nel codice precedente il metodo CreateItemAsync
:
- Serializza il parametro
TodoItem
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 SaveItemAsync(TodoItem todoItem)
{
var todoItemJson = new StringContent(
JsonSerializer.Serialize(todoItem),
Encoding.UTF8,
"application/json");
using var httpResponse =
await _httpClient.PutAsync($"/api/TodoItems/{todoItem.Id}", todoItemJson);
httpResponse.EnsureSuccessStatusCode();
}
Il codice precedente è molto simile all'esempio POST. Il metodo SaveItemAsync
chiama PutAsync anziché PostAsync
.
L'esempio seguente mostra una richiesta HTTP DELETE:
public async Task DeleteItemAsync(long itemId)
{
using var httpResponse =
await _httpClient.DeleteAsync($"/api/TodoItems/{itemId}");
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.
Middleware per richieste in uscita
HttpClient
ha il concetto di delega dei gestori che possono essere collegati tra loro per le richieste HTTP in uscita. IHttpClientFactory
:
- Semplifica la definizione dei gestori da applicare per ogni client denominato.
- Supporta la registrazione e il concatenamento di più gestori per compilare una pipeline middleware delle richieste in uscita. Ognuno di questi gestori è in grado di eseguire operazioni prima e dopo la richiesta in uscita. Questo modello:
- È simile alla pipeline middleware in ingresso in ASP.NET Core.
- Fornisce un meccanismo per gestire le problematiche trasversali relative alle richieste HTTP, ad esempio:
- caching
- gestione degli errori
- serializzazione
- registrazione
Per creare un gestore di delega:
- Derivare da DelegatingHandler.
- Eseguire l'override di SendAsync. Eseguire il codice prima di passare la richiesta al gestore successivo nella pipeline:
public class ValidateHeaderHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
if (!request.Headers.Contains("X-API-KEY"))
{
return new HttpResponseMessage(HttpStatusCode.BadRequest)
{
Content = new StringContent(
"You must supply an API key header called X-API-KEY")
};
}
return await base.SendAsync(request, cancellationToken);
}
}
Il codice precedente controlla se l'intestazione X-API-KEY
si trova nella richiesta. Se X-API-KEY
manca, BadRequest viene restituito .
È possibile aggiungere più gestori alla configurazione di con HttpClient
Microsoft.Extensions.DependencyInjection.HttpClientBuilderExtensions.AddHttpMessageHandler:
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<ValidateHeaderHandler>();
services.AddHttpClient("externalservice", c =>
{
// Assume this is an "external" service which requires an API KEY
c.BaseAddress = new Uri("https://localhost:5001/");
})
.AddHttpMessageHandler<ValidateHeaderHandler>();
// Remaining code deleted for brevity.
Nel codice precedente ValidateHeaderHandler
viene registrato nell'inserimento di dipendenze. Dopo la registrazione è possibile chiamare AddHttpMessageHandler, passando il tipo di gestore.
È possibile registrare più gestori nell'ordine di esecuzione. Ogni gestore esegue il wrapping del gestore successivo fino a quando l'elemento HttpClientHandler
finale non esegue la richiesta:
services.AddTransient<SecureRequestHandler>();
services.AddTransient<RequestDataHandler>();
services.AddHttpClient("clientwithhandlers")
// This handler is on the outside and called first during the
// request, last during the response.
.AddHttpMessageHandler<SecureRequestHandler>()
// This handler is on the inside, closest to the request being
// sent.
.AddHttpMessageHandler<RequestDataHandler>();
Usare l'inserimento delle dipendenze nel middleware delle richieste in uscita
Quando IHttpClientFactory
crea un nuovo gestore di delega, usa l'inserimento delle dipendenze per soddisfare i parametri del costruttore del gestore. IHttpClientFactory
crea un ambito di inserimento delle dipendenze separato per ogni gestore, che può causare un comportamento sorprendente quando un gestore utilizza un servizio con ambito.
Si consideri ad esempio l'interfaccia seguente e la relativa implementazione, che rappresenta un'attività come operazione con un identificatore , OperationId
:
public interface IOperationScoped
{
string OperationId { get; }
}
public class OperationScoped : IOperationScoped
{
public string OperationId { get; } = Guid.NewGuid().ToString()[^4..];
}
Come suggerisce il nome, IOperationScoped
viene registrato con l'inserimento delle dipendenze usando una durata con ambito:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<TodoContext>(options =>
options.UseInMemoryDatabase("TodoItems"));
services.AddHttpContextAccessor();
services.AddHttpClient<TodoClient>((sp, httpClient) =>
{
var httpRequest = sp.GetRequiredService<IHttpContextAccessor>().HttpContext.Request;
// For sample purposes, assume TodoClient is used in the context of an incoming request.
httpClient.BaseAddress = new Uri(UriHelper.BuildAbsolute(httpRequest.Scheme,
httpRequest.Host, httpRequest.PathBase));
httpClient.Timeout = TimeSpan.FromSeconds(5);
});
services.AddScoped<IOperationScoped, OperationScoped>();
services.AddTransient<OperationHandler>();
services.AddTransient<OperationResponseHandler>();
services.AddHttpClient("Operation")
.AddHttpMessageHandler<OperationHandler>()
.AddHttpMessageHandler<OperationResponseHandler>()
.SetHandlerLifetime(TimeSpan.FromSeconds(5));
services.AddControllers();
services.AddRazorPages();
}
Il gestore di delega seguente utilizza e usa IOperationScoped
per impostare l'intestazione X-OPERATION-ID
per la richiesta in uscita:
public class OperationHandler : DelegatingHandler
{
private readonly IOperationScoped _operationService;
public OperationHandler(IOperationScoped operationScoped)
{
_operationService = operationScoped;
}
protected async override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Headers.Add("X-OPERATION-ID", _operationService.OperationId);
return await base.SendAsync(request, cancellationToken);
}
}
HttpRequestsSample
Nel download passare a /Operation
e aggiornare la pagina. Il valore dell'ambito della richiesta cambia per ogni richiesta, ma il valore dell'ambito del gestore cambia solo ogni 5 secondi.
I gestori possono dipendere dai servizi di qualsiasi ambito. I servizi da cui dipendono i gestori vengono eliminati al momento dell'eliminazione del gestore.
Usare uno degli approcci seguenti per condividere lo stato in base alla richiesta con i gestori di messaggi:
- Passare i dati al gestore usando HttpRequestMessage.Properties.
- Usare IHttpContextAccessor per accedere alla richiesta corrente.
- Creare un oggetto di archiviazione AsyncLocal<T> personalizzato per passare i dati.
Usare gestori basati su Polly
IHttpClientFactory
si integra con la libreria di terze parti Polly. Polly è una libreria di gestione degli errori temporanei e di resilienza completa per .NET. Consente agli sviluppatori di esprimere criteri quali Retry, Circuit Breaker, Timeout, Bulkhead Isolation e Fallback in modo fluido e thread-safe.
Per consentire l'uso dei criteri Polly con le istanze configurate di HttpClient
sono disponibili metodi di estensione. Le estensioni Polly supportano l'aggiunta di gestori basati su Polly ai client. Polly richiede il pacchetto NuGet Microsoft.Extensions.Http.Polly .
Gestire gli errori temporanei
Gli errori si verificano in genere quando le chiamate HTTP esterne sono temporanee. AddTransientHttpErrorPolicy consente di definire un criterio per gestire gli errori temporanei. Criteri configurati con AddTransientHttpErrorPolicy
gestire le risposte seguenti:
- HttpRequestException
- HTTP 5xx
- HTTP 408
AddTransientHttpErrorPolicy
fornisce l'accesso a un PolicyBuilder
oggetto configurato per gestire gli errori che rappresentano un possibile errore temporaneo:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient<UnreliableEndpointCallerService>()
.AddTransientHttpErrorPolicy(p =>
p.WaitAndRetryAsync(3, _ => TimeSpan.FromMilliseconds(600)));
// Remaining code deleted for brevity.
Nel codice precedente viene definito un criterio WaitAndRetryAsync
. Le richieste non riuscite vengono ritentate fino a tre volte con un ritardo di 600 millisecondi tra i tentativi.
Selezionare i criteri in modo dinamico
I metodi di estensione vengono forniti per aggiungere gestori basati su Polly, AddPolicyHandlerad esempio . L'overload seguente AddPolicyHandler
controlla la richiesta per decidere quali criteri applicare:
var timeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(10));
var longTimeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(30));
services.AddHttpClient("conditionalpolicy")
// Run some code to select a policy based on the request
.AddPolicyHandler(request =>
request.Method == HttpMethod.Get ? timeout : longTimeout);
Nel codice precedente, se la richiesta in uscita è una richiesta HTTP GET viene applicato un timeout di 10 secondi. Per qualsiasi altro metodo HTTP viene usato un timeout di 30 secondi.
Aggiungere più gestori Polly
È comune annidare i criteri polly:
services.AddHttpClient("multiplepolicies")
.AddTransientHttpErrorPolicy(p => p.RetryAsync(3))
.AddTransientHttpErrorPolicy(
p => p.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));
Nell'esempio precedente:
- Vengono aggiunti due gestori.
- Il primo gestore usa per aggiungere un criterio di ripetizione dei tentativi AddTransientHttpErrorPolicy . Le richieste non riuscite vengono ritentate fino a tre volte.
- La seconda
AddTransientHttpErrorPolicy
chiamata aggiunge un criterio di interruttore. Ulteriori richieste esterne vengono bloccate per 30 secondi se si verificano in sequenza 5 tentativi non riusciti. I criteri dell'interruttore di circuito sono con stato. Tutte le chiamate tramite questo client condividono lo stesso stato di circuito.
Aggiungere criteri dal registro Polly
Un approccio alla gestione dei criteri usati di frequente consiste nel definirli una volta e registrarli in un elemento PolicyRegistry
.
Nel codice seguente:
- Vengono aggiunti i criteri "regolari" e "long".
- AddPolicyHandlerFromRegistry aggiunge i criteri "regolari" e "long" dal Registro di sistema.
public void ConfigureServices(IServiceCollection services)
{
var timeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(10));
var longTimeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(30));
var registry = services.AddPolicyRegistry();
registry.Add("regular", timeout);
registry.Add("long", longTimeout);
services.AddHttpClient("regularTimeoutHandler")
.AddPolicyHandlerFromRegistry("regular");
services.AddHttpClient("longTimeoutHandler")
.AddPolicyHandlerFromRegistry("long");
// Remaining code deleted for brevity.
Per altre informazioni sulle IHttpClientFactory
integrazioni di Polly, vedere il wiki di Polly.
Gestione di HttpClient e durata
Viene restituita una nuova istanza di HttpClient
per ogni chiamata di CreateClient
per IHttpClientFactory
. Viene HttpMessageHandler creato un oggetto per ogni client denominato. La factory gestisce le durate delle istanze di HttpMessageHandler
.
IHttpClientFactory
esegue il pooling delle istanze di HttpMessageHandler
create dalla factory per ridurre il consumo di risorse. Un'istanza di HttpMessageHandler
può essere riusata dal pool quando si crea una nuova istanza di HttpClient
se la relativa durata non è scaduta.
Il pooling dei gestori è consigliabile in quanto ogni gestore gestisce in genere le proprie connessioni HTTP sottostanti. La creazione di più gestori del necessario può causare ritardi di connessione. Alcuni gestori mantengono anche le connessioni aperte a tempo indefinito, che possono impedire al gestore di reagire alle modifiche DNS (Domain Name System).
La durata del gestore predefinito è di due minuti. Il valore predefinito può essere sottoposto a override per ogni client denominato:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient("extendedhandlerlifetime")
.SetHandlerLifetime(TimeSpan.FromMinutes(5));
// Remaining code deleted for brevity.
HttpClient
Le istanze possono in genere essere considerate come oggetti .NET che non richiedono l'eliminazione. L'eliminazione annulla le richieste in uscita e garantisce che l'istanza di HttpClient
specificata non possa essere usata dopo la chiamata a Dispose. IHttpClientFactory
tiene traccia ed elimina le risorse usate dalle istanze di HttpClient
.
Mantenere una sola istanza di HttpClient
attiva per un lungo periodo di tempo è un modello comune usato prima dell'avvento di IHttpClientFactory
. Questo modello non è più necessario dopo la migrazione a IHttpClientFactory
.
Alternative a IHttpClientFactory
L'uso IHttpClientFactory
in un'app abilitata per l'inserimento delle dipendenze evita:
- Problemi di esaurimento delle risorse eseguendo il pool di
HttpMessageHandler
istanze. - Problemi DNS non aggiornati eseguendo il ciclo delle
HttpMessageHandler
istanze a intervalli regolari.
Esistono modi alternativi per risolvere i problemi precedenti usando un'istanza di lunga durata SocketsHttpHandler .
- Creare un'istanza di
SocketsHttpHandler
all'avvio dell'app e usarla per la vita dell'app. - Configurare PooledConnectionLifetime un valore appropriato in base ai tempi di aggiornamento DNS.
- Creare
HttpClient
istanze usando in basenew HttpClient(handler, disposeHandler: false)
alle esigenze.
Gli approcci precedenti risolveranno i problemi di gestione delle risorse risolti IHttpClientFactory
in modo analogo.
- Le
SocketsHttpHandler
condivisioni traHttpClient
istanze. Questa condivisione impedisce l'esaurimento del socket. - Le
SocketsHttpHandler
connessioni cicliche in base aPooledConnectionLifetime
per evitare problemi DNS non aggiornati.
Cookie
Le istanze in HttpMessageHandler
pool generano la condivisione di CookieContainer
oggetti. La condivisione di oggetti CookieContainer
imprevisti comporta spesso codice non corretto. Per le app che richiedono cookie, tenere presente quanto segue:
- Disabilitazione della gestione automatica cookie
- Evitando
IHttpClientFactory
Chiamata ConfigurePrimaryHttpMessageHandler per disabilitare la gestione automatica cookie :
services.AddHttpClient("configured-disable-automatic-cookies")
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler()
{
UseCookies = false,
};
});
Registrazione
I client creati tramite IHttpClientFactory
registrano i messaggi di log per tutte le richieste. Abilitare il livello di informazioni appropriato nella configurazione di registrazione per visualizzare i messaggi di log predefiniti. La registrazione aggiuntiva, ad esempio quella delle intestazioni delle richieste, è inclusa solo a livello di traccia.
La categoria di log usata per ogni client include il nome del client. Un client denominato MyNamedClient, ad esempio, registra i messaggi con una categoria "System.Net.Http.HttpClient.MyNamedClient. LogicalHandler". I messaggi con suffisso LogicalHandler sono esterni alla pipeline del gestore delle richieste. Nella richiesta i messaggi vengono registrati prima che qualsiasi altro gestore nella pipeline l'abbia elaborata. Nella risposta i messaggi vengono registrati dopo che qualsiasi altro gestore nella pipeline ha ricevuto la risposta.
La registrazione avviene anche all'interno della pipeline del gestore delle richieste. Nell'esempio MyNamedClient tali messaggi vengono registrati con la categoria di log "System.Net.Http.HttpClient.MyNamedClient. ClientHandler". Per la richiesta, questo si verifica dopo l'esecuzione di tutti gli altri gestori e immediatamente prima dell'invio della richiesta. Nella risposta la registrazione include lo stato della risposta prima di restituirla attraverso la pipeline del gestore.
L'abilitazione della registrazione all'esterno e all'interno della pipeline consente l'ispezione delle modifiche apportate da altri gestori nella pipeline. Ciò può includere modifiche alle intestazioni della richiesta o al codice di stato della risposta.
L'inclusione del nome del client nella categoria di log consente il filtro dei log per client denominati specifici.
Configurare 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. Il delegato viene usato per creare e configurare l'elemento HttpMessageHandler
primario usato dal client:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient("configured-inner-handler")
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler()
{
AllowAutoRedirect = false,
UseDefaultCredentials = true
};
});
// Remaining code deleted for brevity.
Usare IHttpClientFactory in un'app console
In un'app console aggiungere al progetto i riferimenti ai pacchetti seguenti:
Nell'esempio seguente :
- IHttpClientFactory è registrato nel contenitore di servizi dell'host generico.
MyService
crea un'istanza della factory client dal servizio, che viene usata per creare una classeHttpClient
.HttpClient
viene usato per recuperare una pagina Web.Main
crea un ambito per eseguire il metodoGetPage
del servizio e scrivere i primi 500 caratteri del contenuto della pagina Web nella console.
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
class Program
{
static async Task<int> Main(string[] args)
{
var builder = new HostBuilder()
.ConfigureServices((hostContext, services) =>
{
services.AddHttpClient();
services.AddTransient<IMyService, MyService>();
}).UseConsoleLifetime();
var host = builder.Build();
try
{
var myService = host.Services.GetRequiredService<IMyService>();
var pageContent = await myService.GetPage();
Console.WriteLine(pageContent.Substring(0, 500));
}
catch (Exception ex)
{
var logger = host.Services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred.");
}
return 0;
}
public interface IMyService
{
Task<string> GetPage();
}
public class MyService : IMyService
{
private readonly IHttpClientFactory _clientFactory;
public MyService(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task<string> GetPage()
{
// Content from BBC One: Dr. Who website (©BBC)
var request = new HttpRequestMessage(HttpMethod.Get,
"https://www.bbc.co.uk/programmes/b006q2x0");
var client = _clientFactory.CreateClient();
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
return await response.Content.ReadAsStringAsync();
}
else
{
return $"StatusCode: {response.StatusCode}";
}
}
}
}
Middleware di propagazione delle intestazioni
La propagazione dell'intestazione è un middleware ASP.NET Core per propagare le intestazioni HTTP dalla richiesta in ingresso alle richieste client HTTP in uscita. Per usare la propagazione dell'intestazione:
Fare riferimento al pacchetto Microsoft.AspNetCore.HeaderPropagation .
Configurare il middleware e
HttpClient
inStartup
:public void ConfigureServices(IServiceCollection services) { services.AddControllers(); services.AddHttpClient("MyForwardingClient").AddHeaderPropagation(); services.AddHeaderPropagation(options => { options.Headers.Add("X-TraceId"); }); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseHttpsRedirection(); app.UseHeaderPropagation(); app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); }
Il client include le intestazioni configurate nelle richieste in uscita:
var client = clientFactory.CreateClient("MyForwardingClient"); var response = client.GetAsync(...);
Risorse aggiuntive
Di Glenn Condron, Ryan Nowak e Steve Gordon
È possibile registrare e usare un'interfaccia IHttpClientFactory per configurare e creare istanze di HttpClient in un'app. I vantaggi offerti sono i seguenti:
- Offre una posizione centrale per la denominazione e la configurazione di istanze di
HttpClient
logiche. Ad esempio, è possibile registrare e configurare un client github per accedere a GitHub. È possibile registrare un client predefinito per altri scopi. - Codifica il concetto di middleware in uscita tramite la delega di gestori in
HttpClient
e offre estensioni per il middleware basato su Polly per sfruttarne i vantaggi. - Gestisce il pooling e la durata delle istanze di
HttpClientMessageHandler
sottostanti per evitare problemi DNS comuni che si verificano quando le durate diHttpClient
vengono gestite manualmente. - Aggiunge un'esperienza di registrazione configurabile, tramite
ILogger
, per tutte le richieste inviate attraverso i client creati dalla factory.
Visualizzare o scaricare il codice di esempio (procedura per il download)
Prerequisiti
I progetti destinati a .NET Framework richiedono l'installazione del pacchetto NuGet Microsoft.Extensions.Http. I progetti destinati a .NET Core che fanno riferimento al metapacchetto Microsoft.AspNetCore.All sono già inclusi nel pacchetto Microsoft.Extensions.Http
.
Modelli di consumo
IHttpClientFactory
può essere usato in un'app in diversi modi:
Nessuno di questi modi può essere considerato superiore a un altro. L'approccio migliore dipende dai vincoli dell'app.
Utilizzo di base
È possibile registrare IHttpClientFactory
chiamando il metodo di estensione AddHttpClient
in IServiceCollection
, all'interno del metodo Startup.ConfigureServices
.
services.AddHttpClient();
Dopo la registrazione, il codice può accettare un'interfaccia IHttpClientFactory
ovunque sia possibile inserire servizi con l'inserimento di dipendenze. Può IHttpClientFactory
essere usato per creare un'istanza HttpClient
di :
public class BasicUsageModel : PageModel
{
private readonly IHttpClientFactory _clientFactory;
public IEnumerable<GitHubBranch> Branches { get; private set; }
public bool GetBranchesError { get; private set; }
public BasicUsageModel(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task OnGet()
{
var request = new HttpRequestMessage(HttpMethod.Get,
"https://api.github.com/repos/dotnet/AspNetCore.Docs/branches");
request.Headers.Add("Accept", "application/vnd.github.v3+json");
request.Headers.Add("User-Agent", "HttpClientFactory-Sample");
var client = _clientFactory.CreateClient();
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
Branches = await response.Content
.ReadAsAsync<IEnumerable<GitHubBranch>>();
}
else
{
GetBranchesError = true;
Branches = Array.Empty<GitHubBranch>();
}
}
}
Questo uso di IHttpClientFactory
è un modo efficace per effettuare il refactoring di un'app esistente. Non influisce in alcun modo sulle modalità d'uso di HttpClient
. Nelle posizioni in cui vengono attualmente create istanze di HttpClient
, sostituire le occorrenze con una chiamata a CreateClient.
Client denominati
Se un'app richiede molti usi distinti di HttpClient
, ognuno con una configurazione diversa, un'opzione è l'uso di client denominati. La configurazione di un HttpClient
denominato può essere specificata durante la registrazione in Startup.ConfigureServices
.
services.AddHttpClient("github", c =>
{
c.BaseAddress = new Uri("https://api.github.com/");
// Github API versioning
c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
// Github requires a user-agent
c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});
Nel codice precedente viene chiamato AddHttpClient
, in cui viene specificato il nome github. Questo client ha una configurazione predefinita applicata, ovvero l'indirizzo di base e due intestazioni necessarie per lavorare con l'API GitHub.
Ogni volta che CreateClient
viene chiamato, verrà creata una nuova istanza di HttpClient
e verrà chiamata l'azione di configurazione.
Per usare un client denominato, è possibile passare un parametro di stringa a CreateClient
. Specificare il nome del client da creare:
public class NamedClientModel : PageModel
{
private readonly IHttpClientFactory _clientFactory;
public IEnumerable<GitHubPullRequest> PullRequests { get; private set; }
public bool GetPullRequestsError { get; private set; }
public bool HasPullRequests => PullRequests.Any();
public NamedClientModel(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task OnGet()
{
var request = new HttpRequestMessage(HttpMethod.Get,
"repos/dotnet/AspNetCore.Docs/pulls");
var client = _clientFactory.CreateClient("github");
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
PullRequests = await response.Content
.ReadAsAsync<IEnumerable<GitHubPullRequest>>();
}
else
{
GetPullRequestsError = true;
PullRequests = Array.Empty<GitHubPullRequest>();
}
}
}
Nel codice precedente non è necessario che la richiesta specifichi un nome host. Poiché viene usato l'indirizzo di base configurato per il client, è possibile passare solo il percorso.
Client tipizzati
Client tipizzati:
- Offrono le stesse funzionalità dei client denominati senza la necessità di usare le stringhe come chiavi.
- Offrono 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. Per un singolo endpoint back-end è ad esempio possibile usare un unico client tipizzato che incapsuli tutta la logica relativa all'endpoint. - Usano l'inserimento di dipendenze e possono essere inseriti dove necessario nell'app.
Un client tipizzato accetta il parametro HttpClient
nel proprio costruttore:
public class GitHubService
{
public HttpClient Client { get; }
public GitHubService(HttpClient client)
{
client.BaseAddress = new Uri("https://api.github.com/");
// GitHub API versioning
client.DefaultRequestHeaders.Add("Accept",
"application/vnd.github.v3+json");
// GitHub requires a user-agent
client.DefaultRequestHeaders.Add("User-Agent",
"HttpClientFactory-Sample");
Client = client;
}
public async Task<IEnumerable<GitHubIssue>> GetAspNetDocsIssues()
{
var response = await Client.GetAsync(
"/repos/dotnet/AspNetCore.Docs/issues?state=open&sort=created&direction=desc");
response.EnsureSuccessStatusCode();
var result = await response.Content
.ReadAsAsync<IEnumerable<GitHubIssue>>();
return result;
}
}
Nel codice precedente la configurazione viene spostata nel client tipizzato. L'oggetto HttpClient
viene esposto come una proprietà pubblica. È possibile definire metodi di API specifiche che espongono la funzionalità HttpClient
. Il metodo GetAspNetDocsIssues
incapsula il codice necessario per eseguire una query e analizzare gli ultimi problemi aperti in un repository GitHub.
Per registrare un client tipizzato è possibile usare il metodo di estensione AddHttpClient generico all'interno di Startup.ConfigureServices
, specificando la classe del client tipizzato:
services.AddHttpClient<GitHubService>();
Il client tipizzato viene registrato come temporaneo nell'inserimento di dipendenze. Il client tipizzato può essere inserito e usato direttamente:
public class TypedClientModel : PageModel
{
private readonly GitHubService _gitHubService;
public IEnumerable<GitHubIssue> LatestIssues { get; private set; }
public bool HasIssue => LatestIssues.Any();
public bool GetIssuesError { get; private set; }
public TypedClientModel(GitHubService gitHubService)
{
_gitHubService = gitHubService;
}
public async Task OnGet()
{
try
{
LatestIssues = await _gitHubService.GetAspNetDocsIssues();
}
catch(HttpRequestException)
{
GetIssuesError = true;
LatestIssues = Array.Empty<GitHubIssue>();
}
}
}
Se si preferisce, è possibile specificare la configurazione di un client tipizzato durante la registrazione in Startup.ConfigureServices
, anziché nel relativo costruttore:
services.AddHttpClient<RepoService>(c =>
{
c.BaseAddress = new Uri("https://api.github.com/");
c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});
È possibile incapsulare interamente HttpClient
all'interno di un client tipizzato. Anziché esporlo come una proprietà, è possibile specificare metodi pubblici che chiamano l'istanza di HttpClient
internamente.
public class RepoService
{
// _httpClient isn't exposed publicly
private readonly HttpClient _httpClient;
public RepoService(HttpClient client)
{
_httpClient = client;
}
public async Task<IEnumerable<string>> GetRepos()
{
var response = await _httpClient.GetAsync("aspnet/repos");
response.EnsureSuccessStatusCode();
var result = await response.Content
.ReadAsAsync<IEnumerable<string>>();
return result;
}
}
Nel codice precedente HttpClient
viene archiviato come un campo privato. L'accesso per effettuare chiamate esterne passa attraverso il metodo GetRepos
.
Client generati
È possibile usare IHttpClientFactory
in combinazione con altre librerie di terze parti, ad esempio Refit. Il refit è una REST libreria per .NET. Converte le REST API in interfacce in tempo reale. Un'implementazione dell'interfaccia viene generata dinamicamente da RestService
, usando HttpClient
per effettuare le chiamate HTTP esterne.
Per rappresentare l'API esterna e la relativa risposta vengono definite un'interfaccia e una risposta:
public interface IHelloClient
{
[Get("/helloworld")]
Task<Reply> GetMessageAsync();
}
public class Reply
{
public string Message { get; set; }
}
È possibile aggiungere un client tipizzato usando Refit per generare l'implementazione:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient("hello", c =>
{
c.BaseAddress = new Uri("http://localhost:5000");
})
.AddTypedClient(c => Refit.RestService.For<IHelloClient>(c));
services.AddMvc();
}
L'interfaccia definita può essere usata dove necessario, con l'implementazione offerta dall'inserimento di dipendenze e da Refit:
[ApiController]
public class ValuesController : ControllerBase
{
private readonly IHelloClient _client;
public ValuesController(IHelloClient client)
{
_client = client;
}
[HttpGet("/")]
public async Task<ActionResult<Reply>> Index()
{
return await _client.GetMessageAsync();
}
}
Middleware per richieste in uscita
HttpClient
include già il concetto di delega di gestori concatenati per le richieste HTTP in uscita. IHttpClientFactory
semplifica la definizione dei gestori da applicare per ogni client denominato. Supporta la registrazione e il concatenamento di più gestori per creare una pipeline di middleware per le richieste in uscita. Ognuno di questi gestori è in grado di eseguire operazioni prima e dopo la richiesta in uscita. Questo modello è simile alla pipeline di middleware in ingresso in ASP.NET Core. Il modello offre un meccanismo per gestire le problematiche trasversali relative alle richieste HTTP, tra cui memorizzazione nella cache, gestione degli errori, serializzazione e registrazione.
Per creare un gestore, definire una classe che deriva da DelegatingHandler. Eseguire l'override del metodo SendAsync
per eseguire il codice prima di passare la richiesta al gestore successivo nella pipeline:
public class ValidateHeaderHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
if (!request.Headers.Contains("X-API-KEY"))
{
return new HttpResponseMessage(HttpStatusCode.BadRequest)
{
Content = new StringContent(
"You must supply an API key header called X-API-KEY")
};
}
return await base.SendAsync(request, cancellationToken);
}
}
Il codice precedente definisce un gestore di base. Verifica se un'intestazione X-API-KEY
è stata inclusa nella richiesta. Se l'intestazione non è presente, può evitare la chiamata HTTP e restituire una risposta appropriata.
Durante la registrazione, è possibile aggiungere uno o più gestori alla configurazione di un oggetto HttpClient
. Questa attività viene eseguita tramite metodi di estensione in IHttpClientBuilder.
services.AddTransient<ValidateHeaderHandler>();
services.AddHttpClient("externalservice", c =>
{
// Assume this is an "external" service which requires an API KEY
c.BaseAddress = new Uri("https://localhost:5000/");
})
.AddHttpMessageHandler<ValidateHeaderHandler>();
Nel codice precedente ValidateHeaderHandler
viene registrato nell'inserimento di dipendenze. Il gestore deve essere registrato nell'inserimento di dipendenze come servizio temporaneo, senza definizione di ambito. Se il gestore viene registrato come un servizio con ambito e i servizi da cui dipende il gestore sono eliminabili:
- I servizi del gestore potrebbero essere eliminati prima che il gestore esca dall'ambito.
- I servizi del gestore eliminati causano un errore del gestore.
Dopo la registrazione è possibile chiamare AddHttpMessageHandler, passando il tipo di gestore.
È possibile registrare più gestori nell'ordine di esecuzione. Ogni gestore esegue il wrapping del gestore successivo fino a quando l'elemento HttpClientHandler
finale non esegue la richiesta:
services.AddTransient<SecureRequestHandler>();
services.AddTransient<RequestDataHandler>();
services.AddHttpClient("clientwithhandlers")
// This handler is on the outside and called first during the
// request, last during the response.
.AddHttpMessageHandler<SecureRequestHandler>()
// This handler is on the inside, closest to the request being
// sent.
.AddHttpMessageHandler<RequestDataHandler>();
Usare uno degli approcci seguenti per condividere lo stato in base alla richiesta con i gestori di messaggi:
- Passare i dati al gestore usando
HttpRequestMessage.Properties
. - Usare
IHttpContextAccessor
per accedere alla richiesta corrente. - Creare un oggetto di archiviazione
AsyncLocal
personalizzato per passare i dati.
Usare gestori basati su Polly
IHttpClientFactory
si integra con una libreria di terze parti piuttosto diffusa denominata Polly. Polly è una libreria di gestione degli errori temporanei e di resilienza completa per .NET. Consente agli sviluppatori di esprimere criteri quali Retry, Circuit Breaker, Timeout, Bulkhead Isolation e Fallback in modo fluido e thread-safe.
Per consentire l'uso dei criteri Polly con le istanze configurate di HttpClient
sono disponibili metodi di estensione. Le estensioni Polly:
- Supportano l'aggiunta di gestori basati su Polly ai client.
- Possono essere usate dopo l'installazione del pacchetto NuGet Microsoft.Extensions.Http.Polly. Il pacchetto non è incluso nel framework condiviso ASP.NET Core.
Gestire gli errori temporanei
La maggior parte degli errori comuni si verifica quando le chiamate HTTP esterne sono temporanee. Per definire un criterio in grado di gestire gli errori temporanei è disponibile un pratico metodo di estensione denominato AddTransientHttpErrorPolicy
. I criteri configurati con questo metodo di estensione gestiscono HttpRequestException
, risposte HTTP 5xx e risposte HTTP 408.
L'estensione AddTransientHttpErrorPolicy
può essere usata all'interno di Startup.ConfigureServices
. L'estensione consente l'accesso a un oggetto PolicyBuilder
configurato per gestire gli errori che rappresentano un possibile errore temporaneo:
services.AddHttpClient<UnreliableEndpointCallerService>()
.AddTransientHttpErrorPolicy(p =>
p.WaitAndRetryAsync(3, _ => TimeSpan.FromMilliseconds(600)));
Nel codice precedente viene definito un criterio WaitAndRetryAsync
. Le richieste non riuscite vengono ritentate fino a tre volte con un ritardo di 600 millisecondi tra i tentativi.
Selezionare i criteri in modo dinamico
Per aggiungere gestori basati su Polly è possibile usare altri metodi di estensione. Una di queste estensioni è AddPolicyHandler
, che include più overload. Un overload consente l'ispezione della richiesta al momento della definizione del criterio da applicare:
var timeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(10));
var longTimeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(30));
services.AddHttpClient("conditionalpolicy")
// Run some code to select a policy based on the request
.AddPolicyHandler(request =>
request.Method == HttpMethod.Get ? timeout : longTimeout);
Nel codice precedente, se la richiesta in uscita è una richiesta HTTP GET viene applicato un timeout di 10 secondi. Per qualsiasi altro metodo HTTP viene usato un timeout di 30 secondi.
Aggiungere più gestori Polly
In molti casi i criteri Polly vengono annidati per offrire funzionalità avanzate:
services.AddHttpClient("multiplepolicies")
.AddTransientHttpErrorPolicy(p => p.RetryAsync(3))
.AddTransientHttpErrorPolicy(
p => p.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));
Nell'esempio precedente vengono aggiunti due gestori. Il primo usa l'estensione AddTransientHttpErrorPolicy
per aggiungere criteri di ripetizione. Le richieste non riuscite vengono ritentate fino a tre volte. La seconda chiamata a AddTransientHttpErrorPolicy
aggiunge criteri dell'interruttore di circuito. Ulteriori richieste esterne vengono bloccate per 30 secondi nel caso si verifichino cinque tentativi non riusciti consecutivi. I criteri dell'interruttore di circuito sono con stato. Tutte le chiamate tramite questo client condividono lo stesso stato di circuito.
Aggiungere criteri dal registro Polly
Un approccio alla gestione dei criteri usati di frequente consiste nel definirli una volta e registrarli in un elemento PolicyRegistry
. Per aggiungere un gestore usando un criterio del registro è disponibile un metodo di estensione:
var registry = services.AddPolicyRegistry();
registry.Add("regular", timeout);
registry.Add("long", longTimeout);
services.AddHttpClient("regulartimeouthandler")
.AddPolicyHandlerFromRegistry("regular");
Nel codice precedente vengono registrati due criteri quando si aggiunge PolicyRegistry
a ServiceCollection
. Per usare un criterio dal registro viene usato il metodo AddPolicyHandlerFromRegistry
passando il nome del criterio da applicare.
Altre informazioni su IHttpClientFactory
e le integrazioni con Polly sono disponibili nel wiki di Polly.
Gestione di HttpClient e durata
Viene restituita una nuova istanza di HttpClient
per ogni chiamata di CreateClient
per IHttpClientFactory
. È presente un HttpMessageHandler per ogni client denominato. La factory gestisce le durate delle istanze di HttpMessageHandler
.
IHttpClientFactory
esegue il pooling delle istanze di HttpMessageHandler
create dalla factory per ridurre il consumo di risorse. Un'istanza di HttpMessageHandler
può essere riusata dal pool quando si crea una nuova istanza di HttpClient
se la relativa durata non è scaduta.
Il pooling dei gestori è consigliabile in quanto ogni gestore gestisce in genere le proprie connessioni HTTP sottostanti. La creazione di più gestori del necessario può causare 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. Il valore predefinito può essere sottoposto a override per ogni client denominato. Per eseguire l'override, chiamare SetHandlerLifetime nell'elemento IHttpClientBuilder
restituito al momento della creazione del client:
services.AddHttpClient("extendedhandlerlifetime")
.SetHandlerLifetime(TimeSpan.FromMinutes(5));
L'eliminazione del client non è obbligatoria. L'eliminazione annulla le richieste in uscita e garantisce che l'istanza di HttpClient
specificata non possa essere usata dopo la chiamata a Dispose. IHttpClientFactory
tiene traccia ed elimina le risorse usate dalle istanze di HttpClient
. Le istanze di HttpClient
possono essere considerate a livello generale come oggetti .NET che non richiedono l'eliminazione.
Mantenere una sola istanza di HttpClient
attiva per un lungo periodo di tempo è un modello comune usato prima dell'avvento di IHttpClientFactory
. Questo modello non è più necessario dopo la migrazione a IHttpClientFactory
.
Alternative a IHttpClientFactory
L'uso IHttpClientFactory
in un'app abilitata per l'inserimento delle dipendenze evita:
- Problemi di esaurimento delle risorse eseguendo il pool di
HttpMessageHandler
istanze. - Problemi DNS non aggiornati eseguendo il ciclo delle
HttpMessageHandler
istanze a intervalli regolari.
Esistono modi alternativi per risolvere i problemi precedenti usando un'istanza di lunga durata SocketsHttpHandler .
- Creare un'istanza di
SocketsHttpHandler
all'avvio dell'app e usarla per la vita dell'app. - Configurare PooledConnectionLifetime un valore appropriato in base ai tempi di aggiornamento DNS.
- Creare
HttpClient
istanze usando in basenew HttpClient(handler, disposeHandler: false)
alle esigenze.
Gli approcci precedenti risolveranno i problemi di gestione delle risorse risolti IHttpClientFactory
in modo analogo.
- Le
SocketsHttpHandler
condivisioni traHttpClient
istanze. Questa condivisione impedisce l'esaurimento del socket. - Le
SocketsHttpHandler
connessioni cicliche in base aPooledConnectionLifetime
per evitare problemi DNS non aggiornati.
Cookie
Le istanze in HttpMessageHandler
pool generano la condivisione di CookieContainer
oggetti. La condivisione di oggetti CookieContainer
imprevisti comporta spesso codice non corretto. Per le app che richiedono cookie, tenere presente quanto segue:
- Disabilitazione della gestione automatica cookie
- Evitando
IHttpClientFactory
Chiamata ConfigurePrimaryHttpMessageHandler per disabilitare la gestione automatica cookie :
services.AddHttpClient("configured-disable-automatic-cookies")
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler()
{
UseCookies = false,
};
});
Registrazione
I client creati tramite IHttpClientFactory
registrano i messaggi di log per tutte le richieste. Per visualizzare i messaggi di log predefiniti, abilitare il livello di informazioni appropriato nella configurazione di registrazione. La registrazione aggiuntiva, ad esempio quella delle intestazioni delle richieste, è inclusa solo a livello di traccia.
La categoria di log usata per ogni client include il nome del client. Un client denominato MyNamedClient, ad esempio, registra i messaggi con una categoria System.Net.Http.HttpClient.MyNamedClient.LogicalHandler
. I messaggi con suffisso LogicalHandler sono esterni alla pipeline del gestore delle richieste. Nella richiesta i messaggi vengono registrati prima che qualsiasi altro gestore nella pipeline l'abbia elaborata. Nella risposta i messaggi vengono registrati dopo che qualsiasi altro gestore nella pipeline ha ricevuto la risposta.
La registrazione avviene anche all'interno della pipeline del gestore delle richieste. Nell'esempio MyNamedClient, i messaggi vengono registrati nella categoria di log System.Net.Http.HttpClient.MyNamedClient.ClientHandler
. Per la richiesta, ciò avviene dopo l'esecuzione di tutti gli altri gestori e immediatamente prima che la richiesta sia inviata in rete. Nella risposta la registrazione include lo stato della risposta prima di restituirla attraverso la pipeline del gestore.
L'abilitazione della registrazione all'esterno e all'interno della pipeline consente l'ispezione delle modifiche apportate da altri gestori nella pipeline. Le modifiche possono ad esempio riguardare le intestazioni delle richieste o il codice di stato della risposta.
L'inclusione del nome del client nella categoria di log consente di filtrare i log in base a client denominati specifici, se necessario.
Configurare 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. Il delegato viene usato per creare e configurare l'elemento HttpMessageHandler
primario usato dal client:
services.AddHttpClient("configured-inner-handler")
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler()
{
AllowAutoRedirect = false,
UseDefaultCredentials = true
};
});
Usare IHttpClientFactory in un'app console
In un'app console aggiungere al progetto i riferimenti ai pacchetti seguenti:
Nell'esempio seguente :
- IHttpClientFactory è registrato nel contenitore di servizi dell'host generico.
MyService
crea un'istanza della factory client dal servizio, che viene usata per creare una classeHttpClient
.HttpClient
viene usato per recuperare una pagina Web.- Il metodo del
GetPage
servizio viene eseguito per scrivere i primi 500 caratteri del contenuto della pagina Web nella console. Per altre informazioni sulla chiamata dei servizi daProgram.Main
, vedere Inserimento delle dipendenze in ASP.NET Core.
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
class Program
{
static async Task<int> Main(string[] args)
{
var builder = new HostBuilder()
.ConfigureServices((hostContext, services) =>
{
services.AddHttpClient();
services.AddTransient<IMyService, MyService>();
}).UseConsoleLifetime();
var host = builder.Build();
try
{
var myService = host.Services.GetRequiredService<IMyService>();
var pageContent = await myService.GetPage();
Console.WriteLine(pageContent.Substring(0, 500));
}
catch (Exception ex)
{
var logger = host.Services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred.");
}
return 0;
}
public interface IMyService
{
Task<string> GetPage();
}
public class MyService : IMyService
{
private readonly IHttpClientFactory _clientFactory;
public MyService(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task<string> GetPage()
{
// Content from BBC One: Dr. Who website (©BBC)
var request = new HttpRequestMessage(HttpMethod.Get,
"https://www.bbc.co.uk/programmes/b006q2x0");
var client = _clientFactory.CreateClient();
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
return await response.Content.ReadAsStringAsync();
}
else
{
return $"StatusCode: {response.StatusCode}";
}
}
}
}
Middleware di propagazione delle intestazioni
La propagazione dell'intestazione è un middleware supportato dalla community per propagare le intestazioni HTTP dalla richiesta in ingresso alle richieste client HTTP in uscita. Per usare la propagazione dell'intestazione:
Fare riferimento alla porta supportata dalla community del pacchetto HeaderPropagation. ASP.NET Core 3.1 e versioni successive supporta Microsoft.AspNetCore.HeaderPropagation.
Configurare il middleware e
HttpClient
inStartup
:public void ConfigureServices(IServiceCollection services) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); services.AddHttpClient("MyForwardingClient").AddHeaderPropagation(); services.AddHeaderPropagation(options => { options.Headers.Add("X-TraceId"); }); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseHsts(); } app.UseHttpsRedirection(); app.UseHeaderPropagation(); app.UseMvc(); }
Il client include le intestazioni configurate nelle richieste in uscita:
var client = clientFactory.CreateClient("MyForwardingClient"); var response = client.GetAsync(...);