Effettuare richieste HTTP usando IHttpClientFactory in ASP.NET Core

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 in HttpClient.
  • 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 delle HttpClient 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 dell'argomento usa System.Text.Json per deserializzare JSil contenuto ON 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 HttpClient di :

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 IHttpClientFactory di come nell'esempio precedente è un buon modo per effettuare il refactoring di un'app esistente. Non ha alcun impatto sulla modalità HttpClient di utilizzo. Nelle posizioni in cui HttpClient le istanze vengono create in un'app esistente, sostituire tali occorrenze con chiamate a CreateClient.

Client denominati

I client denominati sono una scelta ottimale quando:

  • L'app richiede molti usi distinti di HttpClient.
  • Molti HttpClienthanno 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 https://api.github.com/base .
  • Due intestazioni necessarie per lavorare con l'API GitHub.

CreateClient

Ogni volta CreateClient che viene chiamato:

  • 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. Il codice può passare solo il percorso, poiché viene usato l'indirizzo di base configurato per il client.

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.
  • Usare l'inserimento di dipendenze e, se necessario, nell'app.

Un client tipizzato accetta un HttpClient parametro nel relativo 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 HttpClient funzionalità. 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 registra AddHttpClientGitHubService come servizio temporaneo. Questa registrazione usa un metodo factory per:

  1. Creare un'istanza di HttpClient.
  2. Creare un'istanza di GitHubService, passando l'istanza di HttpClient 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

IHttpClientFactory può essere usato 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, vedere 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 CreateItemAsync metodo :

  • Serializza il TodoItem parametro su JSON usando System.Text.Json.
  • Crea un'istanza di StringContent per creare un pacchetto di ON serializzato JSper l'invio nel corpo della richiesta HTTP.
  • Chiama PostAsync per inviare il JScontenuto ON 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, vedere 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 SaveItemAsync metodo 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 DeleteItemAsync metodo chiama DeleteAsync. Poiché le richieste HTTP DELETE in genere non contengono alcun corpo, il DeleteAsync metodo non fornisce un overload che accetta un'istanza di HttpContent.

Per altre informazioni sull'uso di verbi HTTP diversi con HttpClient, vedere 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 HttpClientMicrosoft.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:

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:

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 e Long, 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 base new HttpClient(handler, disposeHandler: false) alle esigenze.

Gli approcci precedenti risolveranno i problemi di gestione delle risorse risolti IHttpClientFactory in modo analogo.

  • Le SocketsHttpHandler condivisioni tra HttpClient istanze. Questa condivisione impedisce l'esaurimento del socket.
  • Le SocketsHttpHandler connessioni cicliche in base a PooledConnectionLifetime 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
        });

Cookies

Le istanze in HttpMessageHandler pool generano la condivisione di CookieContainer oggetti. La condivisione di oggetti imprevisti CookieContainer comporta spesso codice non corretto. Per le app che richiedono cookie, prendere in considerazione:

  • 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 di IHttpClientFactory.
  • GitHubService usa IHttpClientFactory per creare un'istanza di HttpClient, 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 in Program.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

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 in HttpClient.
  • 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 delle HttpClient 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 dell'argomento usa System.Text.Json per deserializzare JSil contenuto ON 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 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)
        {
            using var responseStream = await response.Content.ReadAsStreamAsync();
            Branches = await JsonSerializer.DeserializeAsync
                <IEnumerable<GitHubBranch>>(responseStream);
        }
        else
        {
            GetBranchesError = true;
            Branches = Array.Empty<GitHubBranch>();
        }
    }
}

L'uso IHttpClientFactory di come nell'esempio precedente è un buon modo per effettuare il refactoring di un'app esistente. Non ha alcun impatto sulla modalità HttpClient di utilizzo. Nelle posizioni in cui HttpClient le istanze vengono create in un'app esistente, sostituire tali occorrenze con chiamate a CreateClient.

Client denominati

I client denominati sono una scelta ottimale quando:

  • L'app richiede molti usi distinti di HttpClient.
  • Molti HttpClienthanno 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 https://api.github.com/base .
  • Due intestazioni necessarie per lavorare con l'API GitHub.

CreateClient

Ogni volta CreateClient che viene chiamato:

  • 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. Il codice può passare solo il percorso, poiché viene usato l'indirizzo di base configurato per il client.

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.
  • Usare l'inserimento di dipendenze e, se necessario, nell'app.

Un client tipizzato accetta un HttpClient parametro nel relativo 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 HttpClient funzionalità. 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 registra AddHttpClientGitHubService come servizio temporaneo. Questa registrazione usa un metodo factory per:

  1. Creare un'istanza di HttpClient.
  2. Creare un'istanza di GitHubService, passando l'istanza di HttpClient 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

IHttpClientFactory può essere usato 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, vedere 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 CreateItemAsync metodo :

  • Serializza il TodoItem parametro su JSON usando System.Text.Json. Viene utilizzata un'istanza di per configurare il processo di JsonSerializerOptions serializzazione.
  • Crea un'istanza di StringContent per creare un pacchetto di ON serializzato JSper l'invio nel corpo della richiesta HTTP.
  • Chiama PostAsync per inviare il JScontenuto ON 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, vedere 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 SaveItemAsync metodo 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 DeleteItemAsync metodo chiama DeleteAsync. Poiché le richieste HTTP DELETE in genere non contengono alcun corpo, il DeleteAsync metodo non fornisce un overload che accetta un'istanza di HttpContent.

Per altre informazioni sull'uso di verbi HTTP diversi con HttpClient, vedere 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 HttpClientMicrosoft.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:

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:

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 base new HttpClient(handler, disposeHandler: false) alle esigenze.

Gli approcci precedenti risolveranno i problemi di gestione delle risorse risolti IHttpClientFactory in modo analogo.

  • Le SocketsHttpHandler condivisioni tra HttpClient istanze. Questa condivisione impedisce l'esaurimento del socket.
  • Le SocketsHttpHandler connessioni cicliche in base a PooledConnectionLifetime per evitare problemi DNS non aggiornati.

Cookies

Le istanze in HttpMessageHandler pool generano la condivisione di CookieContainer oggetti. La condivisione di oggetti imprevisti CookieContainer comporta spesso codice non corretto. Per le app che richiedono cookie, prendere in considerazione:

  • 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 classe HttpClient. HttpClient viene usato per recuperare una pagina Web.
  • Main crea un ambito per eseguire il metodo GetPage 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 in Startup:

    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 in HttpClient.
  • 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 delle HttpClient 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 dell'argomento usa System.Text.Json per deserializzare JSil contenuto ON 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 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)
        {
            using var responseStream = await response.Content.ReadAsStreamAsync();
            Branches = await JsonSerializer.DeserializeAsync
                <IEnumerable<GitHubBranch>>(responseStream);
        }
        else
        {
            GetBranchesError = true;
            Branches = Array.Empty<GitHubBranch>();
        }
    }
}

L'uso IHttpClientFactory di come nell'esempio precedente è un buon modo per effettuare il refactoring di un'app esistente. Non ha alcun impatto sulla modalità HttpClient di utilizzo. Nelle posizioni in cui HttpClient le istanze vengono create in un'app esistente, sostituire tali occorrenze con chiamate a CreateClient.

Client denominati

I client denominati sono una scelta ottimale quando:

  • L'app richiede molti usi distinti di HttpClient.
  • Molti HttpClienthanno 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 https://api.github.com/base .
  • Due intestazioni necessarie per lavorare con l'API GitHub.

CreateClient

Ogni volta CreateClient che viene chiamato:

  • 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. Il codice può passare solo il percorso, poiché viene usato l'indirizzo di base configurato per il client.

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.
  • Usare l'inserimento di dipendenze e, se necessario, nell'app.

Un client tipizzato accetta un HttpClient parametro nel relativo 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 HttpClient funzionalità. 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 registra AddHttpClientGitHubService come servizio temporaneo. Questa registrazione usa un metodo factory per:

  1. Creare un'istanza di HttpClient.
  2. Creare un'istanza di GitHubService, passando l'istanza di HttpClient 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

IHttpClientFactory può essere usato 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, vedere 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 CreateItemAsync metodo :

  • Serializza il TodoItem parametro su JSON usando System.Text.Json. Viene utilizzata un'istanza di per configurare il processo di JsonSerializerOptions serializzazione.
  • Crea un'istanza di StringContent per creare un pacchetto di ON serializzato JSper l'invio nel corpo della richiesta HTTP.
  • Chiama PostAsync per inviare il JScontenuto ON 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, vedere 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 SaveItemAsync metodo 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 DeleteItemAsync metodo chiama DeleteAsync. Poiché le richieste HTTP DELETE in genere non contengono alcun corpo, il DeleteAsync metodo non fornisce un overload che accetta un'istanza di HttpContent.

Per altre informazioni sull'uso di verbi HTTP diversi con HttpClient, vedere 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 HttpClientMicrosoft.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:

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:

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 base new HttpClient(handler, disposeHandler: false) alle esigenze.

Gli approcci precedenti risolveranno i problemi di gestione delle risorse risolti IHttpClientFactory in modo analogo.

  • Le SocketsHttpHandler condivisioni tra HttpClient istanze. Questa condivisione impedisce l'esaurimento del socket.
  • Le SocketsHttpHandler connessioni cicliche in base a PooledConnectionLifetime per evitare problemi DNS non aggiornati.

Cookies

Le istanze in HttpMessageHandler pool generano la condivisione di CookieContainer oggetti. La condivisione di oggetti imprevisti CookieContainer comporta spesso codice non corretto. Per le app che richiedono cookie, prendere in considerazione:

  • 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 classe HttpClient. HttpClient viene usato per recuperare una pagina Web.
  • Main crea un ambito per eseguire il metodo GetPage 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 in Startup:

    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 di HttpClient 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 un HttpClient parametro nel relativo 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 base new HttpClient(handler, disposeHandler: false) alle esigenze.

Gli approcci precedenti risolveranno i problemi di gestione delle risorse risolti IHttpClientFactory in modo analogo.

  • Le SocketsHttpHandler condivisioni tra HttpClient istanze. Questa condivisione impedisce l'esaurimento del socket.
  • Le SocketsHttpHandler connessioni cicliche in base a PooledConnectionLifetime per evitare problemi DNS non aggiornati.

Cookies

Le istanze in HttpMessageHandler pool generano la condivisione di CookieContainer oggetti. La condivisione di oggetti imprevisti CookieContainer comporta spesso codice non corretto. Per le app che richiedono cookie, prendere in considerazione:

  • 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 classe HttpClient. 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 da Program.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 in Startup:

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

Risorse aggiuntive