Effettuare richieste HTTP usando IHttpClientFactory in ASP.NET CoreMake HTTP requests using IHttpClientFactory in ASP.NET Core

Di Glenn Condron, Ryan Nowak, Steve Gordon, Rick Andersone Kirk LarkinBy Glenn Condron, Ryan Nowak, Steve Gordon, Rick Anderson, and Kirk Larkin

È possibile registrare e usare un'interfaccia IHttpClientFactory per configurare e creare istanze di HttpClient in un'app.An IHttpClientFactory can be registered and used to configure and create HttpClient instances in an app. IHttpClientFactory offre i seguenti vantaggi:IHttpClientFactory offers the following benefits:

  • Offre una posizione centrale per la denominazione e la configurazione di istanze di HttpClient logiche.Provides a central location for naming and configuring logical HttpClient instances. Ad esempio, un client denominato GitHub potrebbe essere registrato e configurato per accedere a GitHub.For example, a client named github could be registered and configured to access GitHub. Un client predefinito può essere registrato per l'accesso generale.A default client can be registered for general access.
  • Codifica il concetto di middleware in uscita tramite la delega dei gestori in HttpClient.Codifies the concept of outgoing middleware via delegating handlers in HttpClient. Fornisce le estensioni per il middleware basato su Polly per sfruttare i vantaggi delegare i gestori in HttpClient.Provides extensions for Polly-based middleware to take advantage of delegating handlers in HttpClient.
  • Gestisce il pool e la durata delle istanze di HttpClientMessageHandler sottostanti.Manages the pooling and lifetime of underlying HttpClientMessageHandler instances. La gestione automatica evita i problemi comuni di DNS (Domain Name System) che si verificano quando si gestiscono manualmente HttpClient durate.Automatic management avoids common DNS (Domain Name System) problems that occur when manually managing HttpClient lifetimes.
  • Aggiunge un'esperienza di registrazione configurabile, tramite ILogger, per tutte le richieste inviate attraverso i client creati dalla factory.Adds a configurable logging experience (via ILogger) for all requests sent through clients created by the factory.

Visualizzare o scaricare il codice di esempio (procedura per il download).View or download sample code (how to download).

Il codice di esempio in questa versione dell'argomento USA System.Text.Json per deserializzare il contenuto JSON restituito nelle risposte HTTP.The sample code in this topic version uses System.Text.Json to deserialize JSON content returned in HTTP responses. Per esempi che usano Json.NET e ReadAsAsync<T>, usare il selettore di versione per selezionare una versione 2. x di questo argomento.For samples that use Json.NET and ReadAsAsync<T>, use the version selector to select a 2.x version of this topic.

Modelli di consumoConsumption patterns

IHttpClientFactory può essere usato in un'app in diversi modi:There are several ways IHttpClientFactory can be used in an app:

L'approccio migliore dipende dai requisiti dell'app.The best approach depends upon the app's requirements.

Utilizzo di baseBasic usage

IHttpClientFactory può essere registrato chiamando AddHttpClient:IHttpClientFactory can be registered by calling 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 IHttpClientFactory usando l' inserimento di dipendenze.An IHttpClientFactory can be requested using dependency injection (DI). Il codice seguente usa IHttpClientFactory per creare un'istanza di HttpClient:The following code uses IHttpClientFactory to create an HttpClient instance:

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/aspnet/AspNetCore.Docs/branches");
        request.Headers.Add("Accept", "application/vnd.github.v3+json");
        request.Headers.Add("User-Agent", "HttpClientFactory-Sample");

        var client = _clientFactory.CreateClient();

        var response = await client.SendAsync(request);

        if (response.IsSuccessStatusCode)
        {
            using var responseStream = await response.Content.ReadAsStreamAsync();
            Branches = await JsonSerializer.DeserializeAsync
                <IEnumerable<GitHubBranch>>(responseStream);
        }
        else
        {
            GetBranchesError = true;
            Branches = Array.Empty<GitHubBranch>();
        }
    }
}

L'uso di IHttpClientFactory come nell'esempio precedente è un modo efficace per effettuare il refactoring di un'app esistente.Using IHttpClientFactory like in the preceding example is a good way to refactor an existing app. Non ha alcun effetto sul modo in cui viene usato HttpClient.It has no impact on how HttpClient is used. Nelle posizioni in cui vengono create HttpClient istanze in un'app esistente, sostituire tali occorrenze con le chiamate a CreateClient.In places where HttpClient instances are created in an existing app, replace those occurrences with calls to CreateClient.

Client denominatiNamed clients

I client denominati sono una scelta ottimale nei casi seguenti:Named clients are a good choice when:

  • L'app richiede molti usi distinti di HttpClient.The app requires many distinct uses of HttpClient.
  • Molti HttpClients hanno una configurazione diversa.Many HttpClients have different configuration.

La configurazione di un HttpClient denominato può essere specificata durante la registrazione in Startup.ConfigureServices:Configuration for a named HttpClient can be specified during registration 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 viene configurato con:In the preceding code the client is configured with:

  • Indirizzo di base https://api.github.com/.The base address https://api.github.com/.
  • Due intestazioni necessarie per lavorare con l'API GitHub.Two headers required to work with the GitHub API.

CreateClientCreateClient

Ogni volta che viene chiamato CreateClient:Each time CreateClient is called:

  • Viene creata una nuova istanza di HttpClient.A new instance of HttpClient is created.
  • Viene chiamata l'azione di configurazione.The configuration action is called.

Per creare un client denominato, passare il nome in CreateClient:To create a named client, pass its name into 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/aspnet/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.In the preceding code, the request doesn't need to specify a hostname. Il codice può passare solo il percorso, perché viene usato l'indirizzo di base configurato per il client.The code can pass just the path, since the base address configured for the client is used.

Client tipizzatiTyped clients

Client tipizzati:Typed clients:

  • Offrono le stesse funzionalità dei client denominati senza la necessità di usare le stringhe come chiavi.Provide the same capabilities as named clients without the need to use strings as keys.
  • Offrono l'aiuto di IntelliSense e del compilatore quando si usano i client.Provides IntelliSense and compiler help when consuming clients.
  • La configurazione e l'interazione con un particolare HttpClient può avvenire in un'unica posizione.Provide a single location to configure and interact with a particular HttpClient. Ad esempio, è possibile usare un singolo client tipizzato:For example, a single typed client might be used:
    • Per un singolo endpoint back-end.For a single backend endpoint.
    • Per incapsulare tutta la logica che riguarda l'endpoint.To encapsulate all logic dealing with the endpoint.
  • Usare DI e può essere inserito laddove necessario nell'app.Work with DI and can be injected where required in the app.

Un client tipizzato accetta un parametro HttpClient nel costruttore:A typed client accepts an HttpClient parameter in its constructor:

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/aspnet/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, segnalare il problema in questo argomento di discussione su GitHub.If you would like to see code comments translated to languages other than English, let us know in this GitHub discussion issue.

Nel codice precedente:In the preceding code:

  • La configurazione viene spostata nel client tipizzato.The configuration is moved into the typed client.
  • L'oggetto HttpClient viene esposto come una proprietà pubblica.The HttpClient object is exposed as a public property.

È possibile creare metodi specifici dell'API che espongono HttpClient funzionalità.API-specific methods can be created that expose HttpClient functionality. Ad esempio, il metodo GetAspNetDocsIssues incapsula il codice per recuperare i problemi aperti.For example, the GetAspNetDocsIssues method encapsulates code to retrieve open issues.

Il codice seguente chiama AddHttpClient in Startup.ConfigureServices per registrare una classe client tipizzata:The following code calls AddHttpClient in Startup.ConfigureServices to register a typed client class:

services.AddHttpClient<GitHubService>();

Il client tipizzato viene registrato come temporaneo nell'inserimento di dipendenze.The typed client is registered as transient with DI. Nel codice precedente AddHttpClient registra GitHubService come servizio temporaneo.In the preceding code, AddHttpClient registers GitHubService as a transient service. Questa registrazione usa un metodo factory per:This registration uses a factory method to:

  1. Creare un'istanza di HttpClient.Create an instance of HttpClient.
  2. Creare un'istanza di GitHubService, passando l'istanza di HttpClient al relativo costruttore.Create an instance of GitHubService, passing in the instance of HttpClient to its constructor.

Il client tipizzato può essere inserito e usato direttamente:The typed client can be injected and consumed directly:

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 di un client tipizzato può essere specificata durante la registrazione in Startup.ConfigureServices, anziché nel costruttore del client tipizzato:The configuration for a typed client can be specified during registration in Startup.ConfigureServices, rather than in the typed client's constructor:

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

Il HttpClient può essere incapsulato all'interno di un client tipizzato.The HttpClient can be encapsulated within a typed client. Anziché esporla come proprietà, definire un metodo che chiama l'istanza di HttpClient internamente:Rather than exposing it as a property, define a method which calls the HttpClient instance internally:

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, il HttpClient viene archiviato in un campo privato.In the preceding code, the HttpClient is stored in a private field. L'accesso alla HttpClient è dal metodo public GetRepos.Access to the HttpClient is by the public GetRepos method.

Client generatiGenerated clients

IHttpClientFactory può essere usato in combinazione con librerie di terze parti, ad esempio il refitting.IHttpClientFactory can be used in combination with third-party libraries such as Refit. Refit è una libreria REST per .NET.Refit is a REST library for .NET. Converte le API REST in interfacce live.It converts REST APIs into live interfaces. Un'implementazione dell'interfaccia viene generata dinamicamente da RestService, usando HttpClient per effettuare le chiamate HTTP esterne.An implementation of the interface is generated dynamically by the RestService, using HttpClient to make the external HTTP calls.

Per rappresentare l'API esterna e la relativa risposta vengono definite un'interfaccia e una risposta:An interface and a reply are defined to represent the external API and its response:

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:A typed client can be added, using Refit to generate the implementation:

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:The defined interface can be consumed where necessary, with the implementation provided by DI and 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 uscitaOutgoing request middleware

HttpClient ha il concetto di delegare i gestori che possono essere collegati insieme per le richieste HTTP in uscita.HttpClient has the concept of delegating handlers that can be linked together for outgoing HTTP requests. IHttpClientFactory:IHttpClientFactory:

  • Semplifica la definizione dei gestori da applicare per ogni client denominato.Simplifies defining the handlers to apply for each named client.

  • Supporta la registrazione e il concatenamento di più gestori per compilare una pipeline middleware per le richieste in uscita.Supports registration and chaining of multiple handlers to build an outgoing request middleware pipeline. Ognuno di questi gestori è in grado di eseguire operazioni prima e dopo la richiesta in uscita.Each of these handlers is able to perform work before and after the outgoing request. Questo modello:This pattern:

    • È simile alla pipeline del middleware in ingresso in ASP.NET Core.Is similar to the inbound middleware pipeline in ASP.NET Core.

    • Fornisce un meccanismo per gestire le problematiche trasversali relative alle richieste HTTP, ad esempio:Provides a mechanism to manage cross-cutting concerns around HTTP requests, such as:

      • memorizzazione nella cachecaching
      • gestione degli errorierror handling
      • serializzazioneserialization
      • registrazionelogging

Per creare un gestore di delega:To create a delegating handler:

  • Derivare da DelegatingHandler.Derive from DelegatingHandler.
  • Eseguire l'override di SendAsync.Override SendAsync. Eseguire il codice prima di passare la richiesta al gestore successivo nella pipeline:Execute code before passing the request to the next handler in the 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 è presente nella richiesta.The preceding code checks if the X-API-KEY header is in the request. Se X-API-KEY è mancante, viene restituito BadRequest.If X-API-KEY is missing, BadRequest is returned.

È possibile aggiungere più di un gestore alla configurazione per un HttpClient con Microsoft.Extensions.DependencyInjection.HttpClientBuilderExtensions.AddHttpMessageHandler:More than one handler can be added to the configuration for an HttpClient with Microsoft.Extensions.DependencyInjection.HttpClientBuilderExtensions.AddHttpMessageHandler:

public void ConfigureServices(IServiceCollection services)
{
    services.AddTransient<ValidateHeaderHandler>();

    services.AddHttpClient("externalservice", c =>
    {
        // Assume this is an "external" service which requires an API KEY
        c.BaseAddress = new Uri("https://localhost:5001/");
    })
    .AddHttpMessageHandler<ValidateHeaderHandler>();

    // Remaining code deleted for brevity.

Nel codice precedente ValidateHeaderHandler viene registrato nell'inserimento di dipendenze.In the preceding code, the ValidateHeaderHandler is registered with DI. L'interfaccia IHttpClientFactory crea un ambito di inserimento delle dipendenze separato per ogni gestore.The IHttpClientFactory creates a separate DI scope for each handler. I gestori possono dipendere da servizi di qualsiasi ambito.Handlers can depend upon services of any scope. I servizi da cui dipendono i gestori vengono eliminati al momento dell'eliminazione del gestore.Services that handlers depend upon are disposed when the handler is disposed.

Dopo la registrazione è possibile chiamare AddHttpMessageHandler, passando il tipo di gestore.Once registered, AddHttpMessageHandler can be called, passing in the type for the handler.

È possibile registrare più gestori nell'ordine di esecuzione.Multiple handlers can be registered in the order that they should execute. Ogni gestore esegue il wrapping del gestore successivo fino a quando l'elemento HttpClientHandler finale non esegue la richiesta:Each handler wraps the next handler until the final HttpClientHandler executes the request:

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:Use one of the following approaches to share per-request state with message handlers:

Usare gestori basati su PollyUse Polly-based handlers

IHttpClientFactory si integra con la libreria di terze parti Polly.IHttpClientFactory integrates with the third-party library Polly. Polly è una libreria di gestione degli errori temporanei e di resilienza completa per .NET.Polly is a comprehensive resilience and transient fault-handling library for .NET. Consente agli sviluppatori di esprimere criteri quali Retry, Circuit Breaker, Timeout, Bulkhead Isolation e Fallback in modo fluido e thread-safe.It allows developers to express policies such as Retry, Circuit Breaker, Timeout, Bulkhead Isolation, and Fallback in a fluent and thread-safe manner.

Per consentire l'uso dei criteri Polly con le istanze configurate di HttpClient sono disponibili metodi di estensione.Extension methods are provided to enable the use of Polly policies with configured HttpClient instances. Le estensioni di Polly supportano l'aggiunta di gestori basati su Polly ai client.The Polly extensions support adding Polly-based handlers to clients. Polly richiede il pacchetto NuGet Microsoft. Extensions. http. Polly .Polly requires the Microsoft.Extensions.Http.Polly NuGet package.

Gestire gli errori temporaneiHandle transient faults

Gli errori si verificano in genere quando le chiamate HTTP esterne sono temporanee.Faults typically occur when external HTTP calls are transient. AddTransientHttpErrorPolicy consente di definire un criterio per gestire gli errori temporanei.AddTransientHttpErrorPolicy allows a policy to be defined to handle transient errors. I criteri configurati con AddTransientHttpErrorPolicy gestiscono le risposte seguenti:Policies configured with AddTransientHttpErrorPolicy handle the following responses:

AddTransientHttpErrorPolicy fornisce l'accesso a un oggetto PolicyBuilder configurato per gestire gli errori che rappresentano un possibile errore temporaneo:AddTransientHttpErrorPolicy provides access to a PolicyBuilder object configured to handle errors representing a possible transient fault:

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.In the preceding code, a WaitAndRetryAsync policy is defined. Le richieste non riuscite vengono ritentate fino a tre volte con un ritardo di 600 millisecondi tra i tentativi.Failed requests are retried up to three times with a delay of 600 ms between attempts.

Selezionare i criteri in modo dinamicoDynamically select policies

I metodi di estensione vengono forniti per aggiungere gestori basati su Polly, ad esempio AddPolicyHandler.Extension methods are provided to add Polly-based handlers, for example, AddPolicyHandler. L'overload di AddPolicyHandler seguente controlla la richiesta per decidere quali criteri applicare:The following AddPolicyHandler overload inspects the request to decide which policy to apply:

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.In the preceding code, if the outgoing request is an HTTP GET, a 10-second timeout is applied. Per qualsiasi altro metodo HTTP viene usato un timeout di 30 secondi.For any other HTTP method, a 30-second timeout is used.

Aggiungere più gestori PollyAdd multiple Polly handlers

È comune annidare i criteri Polly:It's common to nest Polly policies:

services.AddHttpClient("multiplepolicies")
    .AddTransientHttpErrorPolicy(p => p.RetryAsync(3))
    .AddTransientHttpErrorPolicy(
        p => p.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));

Nell'esempio precedente:In the preceding example:

  • Vengono aggiunti due gestori.Two handlers are added.
  • Il primo gestore USA AddTransientHttpErrorPolicy per aggiungere un criterio di ripetizione dei tentativi.The first handler uses AddTransientHttpErrorPolicy to add a retry policy. Le richieste non riuscite vengono ritentate fino a tre volte.Failed requests are retried up to three times.
  • La seconda chiamata di AddTransientHttpErrorPolicy aggiunge un criterio di interruttore.The second AddTransientHttpErrorPolicy call adds a circuit breaker policy. Altre richieste esterne vengono bloccate per 30 secondi se 5 tentativi non riusciti si verificano in sequenza.Further external requests are blocked for 30 seconds if 5 failed attempts occur sequentially. I criteri dell'interruttore di circuito sono con stato.Circuit breaker policies are stateful. Tutte le chiamate tramite questo client condividono lo stesso stato di circuito.All calls through this client share the same circuit state.

Aggiungere criteri dal registro PollyAdd policies from the Polly registry

Un approccio alla gestione dei criteri usati di frequente consiste nel definirli una volta e registrarli in un elemento PolicyRegistry.An approach to managing regularly used policies is to define them once and register them with a PolicyRegistry.

Nel codice seguente:In the following code:

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 ulteriori informazioni sulle integrazioni IHttpClientFactory e Polly, vedere il wiki di Polly.For more information on IHttpClientFactory and Polly integrations, see the Polly wiki.

Gestione di HttpClient e durataHttpClient and lifetime management

Viene restituita una nuova istanza di HttpClient per ogni chiamata di CreateClient per IHttpClientFactory.A new HttpClient instance is returned each time CreateClient is called on the IHttpClientFactory. Viene creato un HttpMessageHandler per ogni client denominato.An HttpMessageHandler is created per named client. La factory gestisce le durate delle istanze di HttpMessageHandler.The factory manages the lifetimes of the HttpMessageHandler instances.

IHttpClientFactory esegue il pooling delle istanze di HttpMessageHandler create dalla factory per ridurre il consumo di risorse.IHttpClientFactory pools the HttpMessageHandler instances created by the factory to reduce resource consumption. Un'istanza di HttpMessageHandler può essere riusata dal pool quando si crea una nuova istanza di HttpClient se la relativa durata non è scaduta.An HttpMessageHandler instance may be reused from the pool when creating a new HttpClient instance if its lifetime hasn't expired.

Il pooling dei gestori è consigliabile in quanto ogni gestore gestisce in genere le proprie connessioni HTTP sottostanti.Pooling of handlers is desirable as each handler typically manages its own underlying HTTP connections. La creazione di più gestori del necessario può causare ritardi di connessione.Creating more handlers than necessary can result in connection delays. Alcuni gestori mantengono inoltre le connessioni aperte a tempo indefinito, che possono impedire al gestore di reagire alle modifiche DNS (Domain Name System).Some handlers also keep connections open indefinitely, which can prevent the handler from reacting to DNS (Domain Name System) changes.

La durata del gestore predefinito è di due minuti.The default handler lifetime is two minutes. È possibile eseguire l'override del valore predefinito in base al client denominato:The default value can be overridden on a per named client basis:

public void ConfigureServices(IServiceCollection services)
{           
    services.AddHttpClient("extendedhandlerlifetime")
        .SetHandlerLifetime(TimeSpan.FromMinutes(5));

    // Remaining code deleted for brevity.

le istanze di HttpClient possono in genere essere considerate come oggetti .NET che non richiedono l'eliminazione.HttpClient instances can generally be treated as .NET objects not requiring disposal. L'eliminazione annulla le richieste in uscita e garantisce che l'istanza di HttpClient specificata non possa essere usata dopo la chiamata a Dispose.Disposal cancels outgoing requests and guarantees the given HttpClient instance can't be used after calling Dispose. IHttpClientFactory tiene traccia ed elimina le risorse usate dalle istanze di HttpClient.IHttpClientFactory tracks and disposes resources used by HttpClient instances.

Mantenere una sola istanza di HttpClient attiva per un lungo periodo di tempo è un modello comune usato prima dell'avvento di IHttpClientFactory.Keeping a single HttpClient instance alive for a long duration is a common pattern used before the inception of IHttpClientFactory. Questo modello non è più necessario dopo la migrazione a IHttpClientFactory.This pattern becomes unnecessary after migrating to IHttpClientFactory.

Alternative a IHttpClientFactoryAlternatives to IHttpClientFactory

L'uso di IHttpClientFactory in un'app abilitata per la funzionalità consente DI evitare:Using IHttpClientFactory in a DI-enabled app avoids:

  • Problemi di esaurimento delle risorse raggruppando le istanze HttpMessageHandler.Resource exhaustion problems by pooling HttpMessageHandler instances.
  • Problemi relativi a DNS obsoleti ciclando HttpMessageHandler istanze a intervalli regolari.Stale DNS problems by cycling HttpMessageHandler instances at regular intervals.

Esistono modi alternativi per risolvere i problemi precedenti utilizzando un'istanza di SocketsHttpHandler di lunga durata.There are alternative ways to solve the preceding problems using a long-lived SocketsHttpHandler instance.

  • Creare un'istanza di SocketsHttpHandler quando l'app viene avviata e usata per il ciclo di vita dell'app.Create an instance of SocketsHttpHandler when the app starts and use it for the life of the app.
  • Configurare PooledConnectionLifetime su un valore appropriato in base alle ore di aggiornamento DNS.Configure PooledConnectionLifetime to an appropriate value based on DNS refresh times.
  • Creare istanze di HttpClient usando new HttpClient(handler, disposeHandler: false) in base alle esigenze.Create HttpClient instances using new HttpClient(handler, disposeHandler: false) as needed.

Gli approcci precedenti risolvono i problemi di gestione delle risorse che IHttpClientFactory risolve in modo analogo.The preceding approaches solve the resource management problems that IHttpClientFactory solves in a similar way.

  • Il SocketsHttpHandler condivide le connessioni tra le istanze di HttpClient.The SocketsHttpHandler shares connections across HttpClient instances. Questa condivisione impedisce l'esaurimento del socket.This sharing prevents socket exhaustion.
  • Il SocketsHttpHandler cicla le connessioni in base a PooledConnectionLifetime per evitare problemi DNS non aggiornati.The SocketsHttpHandler cycles connections according to PooledConnectionLifetime to avoid stale DNS problems.

CookieCookies

Le istanze di HttpMessageHandler in pool generano CookieContainer oggetti condivisi.The pooled HttpMessageHandler instances results in CookieContainer objects being shared. La condivisione di oggetti CookieContainer non prevista spesso genera codice errato.Unanticipated CookieContainer object sharing often results in incorrect code. Per le app che richiedono cookie, prendere in considerazione una delle seguenti operazioni:For apps that require cookies, consider either:

  • Disabilitazione della gestione automatica dei cookieDisabling automatic cookie handling
  • Evitare IHttpClientFactoryAvoiding IHttpClientFactory

Chiamare ConfigurePrimaryHttpMessageHandler per disabilitare la gestione automatica dei cookie:Call ConfigurePrimaryHttpMessageHandler to disable automatic cookie handling:

services.AddHttpClient("configured-disable-automatic-cookies")
    .ConfigurePrimaryHttpMessageHandler(() =>
    {
        return new SocketsHttpHandler()
        {
            UseCookies = false,
        };
    });

RegistrazioneLogging

I client creati tramite IHttpClientFactory registrano i messaggi di log per tutte le richieste.Clients created via IHttpClientFactory record log messages for all requests. Abilitare il livello di informazioni appropriato nella configurazione di registrazione per visualizzare i messaggi di log predefiniti.Enable the appropriate information level in the logging configuration to see the default log messages. La registrazione aggiuntiva, ad esempio quella delle intestazioni delle richieste, è inclusa solo a livello di traccia.Additional logging, such as the logging of request headers, is only included at trace level.

La categoria di log usata per ogni client include il nome del client.The log category used for each client includes the name of the client. Un client denominato MyNamedClient, ad esempio, registra i messaggi con una categoria "System .NET. http. HttpClient. MyNamedClient. LogicalHandler".A client named MyNamedClient, for example, logs messages with a category of "System.Net.Http.HttpClient.MyNamedClient.LogicalHandler". I messaggi con suffisso LogicalHandler sono esterni alla pipeline del gestore delle richieste.Messages suffixed with LogicalHandler occur outside the request handler pipeline. Nella richiesta i messaggi vengono registrati prima che qualsiasi altro gestore nella pipeline l'abbia elaborata.On the request, messages are logged before any other handlers in the pipeline have processed it. Nella risposta i messaggi vengono registrati dopo che qualsiasi altro gestore nella pipeline ha ricevuto la risposta.On the response, messages are logged after any other pipeline handlers have received the response.

La registrazione avviene anche all'interno della pipeline del gestore delle richieste.Logging also occurs inside the request handler pipeline. Nell'esempio MyNamedClient i messaggi vengono registrati con la categoria di log "System .NET. http. HttpClient. MyNamedClient. ClientHandler".In the MyNamedClient example, those messages are logged with the log category "System.Net.Http.HttpClient.MyNamedClient.ClientHandler". Per la richiesta, questo errore si verifica dopo l'esecuzione di tutti gli altri gestori e immediatamente prima dell'invio della richiesta.For the request, this occurs after all other handlers have run and immediately before the request is sent. Nella risposta la registrazione include lo stato della risposta prima di restituirla attraverso la pipeline del gestore.On the response, this logging includes the state of the response before it passes back through the handler pipeline.

L'abilitazione della registrazione all'esterno e all'interno della pipeline consente l'ispezione delle modifiche apportate da altri gestori nella pipeline.Enabling logging outside and inside the pipeline enables inspection of the changes made by the other pipeline handlers. Questo può includere modifiche alle intestazioni delle richieste o al codice di stato della risposta.This may include changes to request headers or to the response status code.

L'inclusione del nome del client nella categoria log consente il filtraggio dei log per specifici client denominati.Including the name of the client in the log category enables log filtering for specific named clients.

Configurare HttpMessageHandlerConfigure the HttpMessageHandler

Può essere necessario controllare la configurazione dell'elemento HttpMessageHandler interno usato da un client.It may be necessary to control the configuration of the inner HttpMessageHandler used by a client.

Quando si aggiungono client denominati o tipizzati viene restituito un elemento IHttpClientBuilder.An IHttpClientBuilder is returned when adding named or typed clients. È possibile usare il metodo di estensione ConfigurePrimaryHttpMessageHandler per definire un delegato.The ConfigurePrimaryHttpMessageHandler extension method can be used to define a delegate. Il delegato viene usato per creare e configurare l'elemento HttpMessageHandler primario usato dal client:The delegate is used to create and configure the primary HttpMessageHandler used by that 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 consoleUse IHttpClientFactory in a console app

In un'app console aggiungere al progetto i riferimenti ai pacchetti seguenti:In a console app, add the following package references to the project:

Nell'esempio seguente:In the following example:

  • IHttpClientFactory è registrato nel contenitore di servizi dell'host generico.IHttpClientFactory is registered in the Generic Host's service container.
  • MyService crea un'istanza della factory client dal servizio, che viene usata per creare una classe HttpClient.MyService creates a client factory instance from the service, which is used to create an HttpClient. HttpClient viene usato per recuperare una pagina Web.HttpClient is used to retrieve a webpage.
  • Main crea un ambito per eseguire il metodo GetPage del servizio e scrivere i primi 500 caratteri del contenuto della pagina Web nella console.Main creates a scope to execute the service's GetPage method and write the first 500 characters of the webpage content to the 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();

        using (var serviceScope = host.Services.CreateScope())
        {
            var services = serviceScope.ServiceProvider;

            try
            {
                var myService = services.GetRequiredService<IMyService>();
                var pageContent = await myService.GetPage();

                Console.WriteLine(pageContent.Substring(0, 500));
            }
            catch (Exception ex)
            {
                var logger = 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 intestazioniHeader propagation middleware

La propagazione dell'intestazione è un middleware ASP.NET Core per propagare le intestazioni HTTP dalla richiesta in ingresso alle richieste del client HTTP in uscita.Header propagation is an ASP.NET Core middleware to propagate HTTP headers from the incoming request to the outgoing HTTP Client requests. Per usare la propagazione delle intestazioni:To use header propagation:

  • Fare riferimento al pacchetto Microsoft. AspNetCore. HeaderPropagation .Reference the Microsoft.AspNetCore.HeaderPropagation package.

  • Configurare il middleware e HttpClient in Startup:Configure the middleware and 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:The client includes the configured headers on outbound requests:

    var client = clientFactory.CreateClient("MyForwardingClient");
    var response = client.GetAsync(...);
    

Risorse aggiuntiveAdditional resources

Di Glenn Condron, Ryan Nowak e Steve GordonBy Glenn Condron, Ryan Nowak, and Steve Gordon

È possibile registrare e usare un'interfaccia IHttpClientFactory per configurare e creare istanze di HttpClient in un'app.An IHttpClientFactory can be registered and used to configure and create HttpClient instances in an app. I vantaggi offerti sono i seguenti:It offers the following benefits:

  • Offre una posizione centrale per la denominazione e la configurazione di istanze di HttpClient logiche.Provides a central location for naming and configuring logical HttpClient instances. Ad esempio, è possibile registrare e configurare un client github per accedere a GitHub.For example, a github client can be registered and configured to access GitHub. È possibile registrare un client predefinito per altri scopi.A default client can be registered for other purposes.
  • 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.Codifies the concept of outgoing middleware via delegating handlers in HttpClient and provides extensions for Polly-based middleware to take advantage of that.
  • 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.Manages the pooling and lifetime of underlying HttpClientMessageHandler instances to avoid common DNS problems that occur when manually managing HttpClient lifetimes.
  • Aggiunge un'esperienza di registrazione configurabile, tramite ILogger, per tutte le richieste inviate attraverso i client creati dalla factory.Adds a configurable logging experience (via ILogger) for all requests sent through clients created by the factory.

Visualizzare o scaricare il codice di esempio (procedura per il download)View or download sample code (how to download)

Modelli di consumoConsumption patterns

IHttpClientFactory può essere usato in un'app in diversi modi:There are several ways IHttpClientFactory can be used in an app:

Nessuno di questi modi può essere considerato superiore a un altro.None of them are strictly superior to another. L'approccio migliore dipende dai vincoli dell'app.The best approach depends upon the app's constraints.

Utilizzo di baseBasic usage

È possibile registrare IHttpClientFactory chiamando il metodo di estensione AddHttpClient in IServiceCollection, all'interno del metodo Startup.ConfigureServices.The IHttpClientFactory can be registered by calling the AddHttpClient extension method on the IServiceCollection, inside the Startup.ConfigureServices method.

services.AddHttpClient();

Dopo la registrazione, il codice può accettare un'interfaccia IHttpClientFactory ovunque sia possibile inserire servizi con l'inserimento di dipendenze.Once registered, code can accept an IHttpClientFactory anywhere services can be injected with dependency injection (DI). Il IHttpClientFactory può essere utilizzato per creare un'istanza di HttpClient:The IHttpClientFactory can be used to create an HttpClient instance:

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/aspnet/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.Using IHttpClientFactory in this fashion is a good way to refactor an existing app. Non influisce in alcun modo sulle modalità d'uso di HttpClient.It has no impact on the way HttpClient is used. Nelle posizioni in cui vengono attualmente create istanze di HttpClient, sostituire le occorrenze con una chiamata a CreateClient.In places where HttpClient instances are currently created, replace those occurrences with a call to CreateClient.

Client denominatiNamed clients

Se un'app richiede molti usi distinti di HttpClient, ognuno con una configurazione diversa, un'opzione è l'uso di client denominati.If an app requires many distinct uses of HttpClient, each with a different configuration, an option is to use named clients. La configurazione di un HttpClient denominato può essere specificata durante la registrazione in Startup.ConfigureServices.Configuration for a named HttpClient can be specified during registration 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.In the preceding code, AddHttpClient is called, providing the name github. Al client viene applicata una configurazione predefinita, ovvero l'indirizzo di base e due intestazioni necessari per l'uso dell'API GitHub.This client has some default configuration applied—namely the base address and two headers required to work with the GitHub API.

Ogni volta che CreateClient viene chiamato, verrà creata una nuova istanza di HttpClient e verrà chiamata l'azione di configurazione.Each time CreateClient is called, a new instance of HttpClient is created and the configuration action is called.

Per usare un client denominato, è possibile passare un parametro di stringa a CreateClient.To consume a named client, a string parameter can be passed to CreateClient. Specificare il nome del client da creare:Specify the name of the client to be created:

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/aspnet/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.In the preceding code, the request doesn't need to specify a hostname. Poiché viene usato l'indirizzo di base configurato per il client, è possibile passare solo il percorso.It can pass just the path, since the base address configured for the client is used.

Client tipizzatiTyped clients

Client tipizzati:Typed clients:

  • Offrono le stesse funzionalità dei client denominati senza la necessità di usare le stringhe come chiavi.Provide the same capabilities as named clients without the need to use strings as keys.
  • Offrono l'aiuto di IntelliSense e del compilatore quando si usano i client.Provides IntelliSense and compiler help when consuming clients.
  • La configurazione e l'interazione con un particolare HttpClient può avvenire in un'unica posizione.Provide a single location to configure and interact with a particular HttpClient. Per un singolo endpoint back-end è ad esempio possibile usare un unico client tipizzato che incapsuli tutta la logica relativa all'endpoint.For example, a single typed client might be used for a single backend endpoint and encapsulate all logic dealing with that endpoint.
  • Usano l'inserimento di dipendenze e possono essere inseriti dove necessario nell'app.Work with DI and can be injected where required in your app.

Un client tipizzato accetta un parametro HttpClient nel costruttore:A typed client accepts an HttpClient parameter in its constructor:

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/aspnet/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.In the preceding code, the configuration is moved into the typed client. L'oggetto HttpClient viene esposto come una proprietà pubblica.The HttpClient object is exposed as a public property. È possibile definire metodi di API specifiche che espongono la funzionalità HttpClient.It's possible to define API-specific methods that expose HttpClient functionality. Il metodo GetAspNetDocsIssues incapsula il codice necessario per eseguire una query e analizzare gli ultimi problemi aperti in un repository GitHub.The GetAspNetDocsIssues method encapsulates the code needed to query for and parse out the latest open issues from a GitHub repository.

Per registrare un client tipizzato è possibile usare il metodo di estensione AddHttpClient generico all'interno di Startup.ConfigureServices, specificando la classe del client tipizzato:To register a typed client, the generic AddHttpClient extension method can be used within Startup.ConfigureServices, specifying the typed client class:

services.AddHttpClient<GitHubService>();

Il client tipizzato viene registrato come temporaneo nell'inserimento di dipendenze.The typed client is registered as transient with DI. Il client tipizzato può essere inserito e usato direttamente:The typed client can be injected and consumed directly:

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:If preferred, the configuration for a typed client can be specified during registration in Startup.ConfigureServices, rather than in the typed client's constructor:

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.It's possible to entirely encapsulate the HttpClient within a typed client. Anziché esporlo come una proprietà, è possibile specificare metodi pubblici che chiamano l'istanza di HttpClient internamente.Rather than exposing it as a property, public methods can be provided which call the HttpClient instance internally.

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.In the preceding code, the HttpClient is stored as a private field. L'accesso per effettuare chiamate esterne passa attraverso il metodo GetRepos.All access to make external calls goes through the GetRepos method.

Client generatiGenerated clients

È possibile usare IHttpClientFactory in combinazione con altre librerie di terze parti, ad esempio Refit.IHttpClientFactory can be used in combination with other third-party libraries such as Refit. Refit è una libreria REST per .NET.Refit is a REST library for .NET. Converte le API REST in interfacce live.It converts REST APIs into live interfaces. Un'implementazione dell'interfaccia viene generata dinamicamente da RestService, usando HttpClient per effettuare le chiamate HTTP esterne.An implementation of the interface is generated dynamically by the RestService, using HttpClient to make the external HTTP calls.

Per rappresentare l'API esterna e la relativa risposta vengono definite un'interfaccia e una risposta:An interface and a reply are defined to represent the external API and its response:

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:A typed client can be added, using Refit to generate the implementation:

public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpClient("hello", c =>
    {
        c.BaseAddress = new Uri("https://localhost:5001");
    })
    .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:The defined interface can be consumed where necessary, with the implementation provided by DI and 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 uscitaOutgoing request middleware

HttpClient include già il concetto di delega di gestori concatenati per le richieste HTTP in uscita.HttpClient already has the concept of delegating handlers that can be linked together for outgoing HTTP requests. IHttpClientFactory semplifica la definizione dei gestori da applicare per ogni client denominato.The IHttpClientFactory makes it easy to define the handlers to apply for each named client. Supporta la registrazione e il concatenamento di più gestori per creare una pipeline di middleware per le richieste in uscita.It supports registration and chaining of multiple handlers to build an outgoing request middleware pipeline. Ognuno di questi gestori è in grado di eseguire operazioni prima e dopo la richiesta in uscita.Each of these handlers is able to perform work before and after the outgoing request. Questo modello è simile alla pipeline di middleware in ingresso in ASP.NET Core.This pattern is similar to the inbound middleware pipeline 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.The pattern provides a mechanism to manage cross-cutting concerns around HTTP requests, including caching, error handling, serialization, and logging.

Per creare un gestore, definire una classe che deriva da DelegatingHandler.To create a handler, define a class deriving from DelegatingHandler. Eseguire l'override del metodo SendAsync per eseguire il codice prima di passare la richiesta al gestore successivo nella pipeline:Override the SendAsync method to execute code before passing the request to the next handler in the 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.The preceding code defines a basic handler. Verifica se un'intestazione X-API-KEY è stata inclusa nella richiesta.It checks to see if an X-API-KEY header has been included on the request. Se l'intestazione non è presente, può evitare la chiamata HTTP e restituire una risposta appropriata.If the header is missing, it can avoid the HTTP call and return a suitable response.

Durante la registrazione, è possibile aggiungere uno o più gestori alla configurazione per un HttpClient.During registration, one or more handlers can be added to the configuration for an HttpClient. Questa attività viene eseguita tramite metodi di estensione in IHttpClientBuilder.This task is accomplished via extension methods on the 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.In the preceding code, the ValidateHeaderHandler is registered with DI. L'interfaccia IHttpClientFactory crea un ambito di inserimento delle dipendenze separato per ogni gestore.The IHttpClientFactory creates a separate DI scope for each handler. I gestori possono dipendere da servizi di qualsiasi ambito.Handlers are free to depend upon services of any scope. I servizi da cui dipendono i gestori vengono eliminati al momento dell'eliminazione del gestore.Services that handlers depend upon are disposed when the handler is disposed.

Dopo la registrazione è possibile chiamare AddHttpMessageHandler, passando il tipo di gestore.Once registered, AddHttpMessageHandler can be called, passing in the type for the handler.

È possibile registrare più gestori nell'ordine di esecuzione.Multiple handlers can be registered in the order that they should execute. Ogni gestore esegue il wrapping del gestore successivo fino a quando l'elemento HttpClientHandler finale non esegue la richiesta:Each handler wraps the next handler until the final HttpClientHandler executes the request:

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:Use one of the following approaches to share per-request state with message handlers:

  • Passare i dati al gestore usando HttpRequestMessage.Properties.Pass data into the handler using HttpRequestMessage.Properties.
  • Usare IHttpContextAccessor per accedere alla richiesta corrente.Use IHttpContextAccessor to access the current request.
  • Creare un oggetto di archiviazione AsyncLocal personalizzato per passare i dati.Create a custom AsyncLocal storage object to pass the data.

Usare gestori basati su PollyUse Polly-based handlers

IHttpClientFactory si integra con una libreria di terze parti piuttosto diffusa denominata Polly.IHttpClientFactory integrates with a popular third-party library called Polly. Polly è una libreria di gestione degli errori temporanei e di resilienza completa per .NET.Polly is a comprehensive resilience and transient fault-handling library for .NET. Consente agli sviluppatori di esprimere criteri quali Retry, Circuit Breaker, Timeout, Bulkhead Isolation e Fallback in modo fluido e thread-safe.It allows developers to express policies such as Retry, Circuit Breaker, Timeout, Bulkhead Isolation, and Fallback in a fluent and thread-safe manner.

Per consentire l'uso dei criteri Polly con le istanze configurate di HttpClient sono disponibili metodi di estensione.Extension methods are provided to enable the use of Polly policies with configured HttpClient instances. Le estensioni Polly:The Polly extensions:

  • Supportano l'aggiunta di gestori basati su Polly ai client.Support adding Polly-based handlers to clients.
  • Possono essere usate dopo l'installazione del pacchetto NuGet Microsoft.Extensions.Http.Polly.Can be used after installing the Microsoft.Extensions.Http.Polly NuGet package. Il pacchetto non è incluso nel framework condiviso ASP.NET Core.The package isn't included in the ASP.NET Core shared framework.

Gestire gli errori temporaneiHandle transient faults

La maggior parte degli errori comuni si verifica quando le chiamate HTTP esterne sono temporanee.Most common faults occur when external HTTP calls are transient. Per definire un criterio in grado di gestire gli errori temporanei è disponibile un pratico metodo di estensione denominato AddTransientHttpErrorPolicy.A convenient extension method called AddTransientHttpErrorPolicy is included which allows a policy to be defined to handle transient errors. I criteri configurati con questo metodo di estensione gestiscono HttpRequestException, risposte HTTP 5xx e risposte HTTP 408.Policies configured with this extension method handle HttpRequestException, HTTP 5xx responses, and HTTP 408 responses.

L'estensione AddTransientHttpErrorPolicy può essere usata all'interno di Startup.ConfigureServices.The AddTransientHttpErrorPolicy extension can be used within Startup.ConfigureServices. L'estensione consente l'accesso a un oggetto PolicyBuilder configurato per gestire gli errori che rappresentano un possibile errore temporaneo:The extension provides access to a PolicyBuilder object configured to handle errors representing a possible transient fault:

services.AddHttpClient<UnreliableEndpointCallerService>()
    .AddTransientHttpErrorPolicy(p => 
        p.WaitAndRetryAsync(3, _ => TimeSpan.FromMilliseconds(600)));

Nel codice precedente viene definito un criterio WaitAndRetryAsync.In the preceding code, a WaitAndRetryAsync policy is defined. Le richieste non riuscite vengono ritentate fino a tre volte con un ritardo di 600 millisecondi tra i tentativi.Failed requests are retried up to three times with a delay of 600 ms between attempts.

Selezionare i criteri in modo dinamicoDynamically select policies

Per aggiungere gestori basati su Polly è possibile usare altri metodi di estensione.Additional extension methods exist which can be used to add Polly-based handlers. Una di queste estensioni è AddPolicyHandler, che include più overload.One such extension is AddPolicyHandler, which has multiple overloads. Un overload consente l'ispezione della richiesta al momento della definizione del criterio da applicare:One overload allows the request to be inspected when defining which policy to apply:

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.In the preceding code, if the outgoing request is an HTTP GET, a 10-second timeout is applied. Per qualsiasi altro metodo HTTP viene usato un timeout di 30 secondi.For any other HTTP method, a 30-second timeout is used.

Aggiungere più gestori PollyAdd multiple Polly handlers

In molti casi i criteri Polly vengono annidati per offrire funzionalità avanzate:It's common to nest Polly policies to provide enhanced functionality:

services.AddHttpClient("multiplepolicies")
    .AddTransientHttpErrorPolicy(p => p.RetryAsync(3))
    .AddTransientHttpErrorPolicy(
        p => p.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));

Nell'esempio precedente vengono aggiunti due gestori.In the preceding example, two handlers are added. Il primo usa l'estensione AddTransientHttpErrorPolicy per aggiungere criteri di ripetizione.The first uses the AddTransientHttpErrorPolicy extension to add a retry policy. Le richieste non riuscite vengono ritentate fino a tre volte.Failed requests are retried up to three times. La seconda chiamata a AddTransientHttpErrorPolicy aggiunge criteri dell'interruttore di circuito.The second call to AddTransientHttpErrorPolicy adds a circuit breaker policy. Ulteriori richieste esterne vengono bloccate per 30 secondi nel caso si verifichino cinque tentativi non riusciti consecutivi.Further external requests are blocked for 30 seconds if five failed attempts occur sequentially. I criteri dell'interruttore di circuito sono con stato.Circuit breaker policies are stateful. Tutte le chiamate tramite questo client condividono lo stesso stato di circuito.All calls through this client share the same circuit state.

Aggiungere criteri dal registro PollyAdd policies from the Polly registry

Un approccio alla gestione dei criteri usati di frequente consiste nel definirli una volta e registrarli in un elemento PolicyRegistry.An approach to managing regularly used policies is to define them once and register them with a PolicyRegistry. Per aggiungere un gestore usando un criterio del registro è disponibile un metodo di estensione:An extension method is provided which allows a handler to be added using a policy from the registry:

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.In the preceding code, two policies are registered when the PolicyRegistry is added to the ServiceCollection. Per usare un criterio dal registro viene usato il metodo AddPolicyHandlerFromRegistry passando il nome del criterio da applicare.To use a policy from the registry, the AddPolicyHandlerFromRegistry method is used, passing the name of the policy to apply.

Altre informazioni su IHttpClientFactory e le integrazioni con Polly sono disponibili nel wiki di Polly.Further information about IHttpClientFactory and Polly integrations can be found on the Polly wiki.

Gestione di HttpClient e durataHttpClient and lifetime management

Viene restituita una nuova istanza di HttpClient per ogni chiamata di CreateClient per IHttpClientFactory.A new HttpClient instance is returned each time CreateClient is called on the IHttpClientFactory. È presente un HttpMessageHandler per ogni client denominato.There's an HttpMessageHandler per named client. La factory gestisce le durate delle istanze di HttpMessageHandler.The factory manages the lifetimes of the HttpMessageHandler instances.

IHttpClientFactory esegue il pooling delle istanze di HttpMessageHandler create dalla factory per ridurre il consumo di risorse.IHttpClientFactory pools the HttpMessageHandler instances created by the factory to reduce resource consumption. Un'istanza di HttpMessageHandler può essere riusata dal pool quando si crea una nuova istanza di HttpClient se la relativa durata non è scaduta.An HttpMessageHandler instance may be reused from the pool when creating a new HttpClient instance if its lifetime hasn't expired.

Il pooling dei gestori è consigliabile in quanto ogni gestore gestisce in genere le proprie connessioni HTTP sottostanti.Pooling of handlers is desirable as each handler typically manages its own underlying HTTP connections. La creazione di più gestori del necessario può causare ritardi di connessione.Creating more handlers than necessary can result in connection delays. Alcuni gestori mantengono inoltre le connessioni aperte a tempo indefinito. Ciò può impedire al gestore di reagire alle modifiche DNS.Some handlers also keep connections open indefinitely, which can prevent the handler from reacting to DNS changes.

La durata del gestore predefinito è di due minuti.The default handler lifetime is two minutes. Il valore predefinito può essere sottoposto a override per ogni client denominato.The default value can be overridden on a per named client basis. Per eseguire l'override, chiamare SetHandlerLifetime nell'elemento IHttpClientBuilder restituito al momento della creazione del client:To override it, call SetHandlerLifetime on the IHttpClientBuilder that is returned when creating the client:

services.AddHttpClient("extendedhandlerlifetime")
    .SetHandlerLifetime(TimeSpan.FromMinutes(5));

L'eliminazione del client non è obbligatoria.Disposal of the client isn't required. L'eliminazione annulla le richieste in uscita e garantisce che l'istanza di HttpClient specificata non possa essere usata dopo la chiamata a Dispose.Disposal cancels outgoing requests and guarantees the given HttpClient instance can't be used after calling Dispose. IHttpClientFactory tiene traccia ed elimina le risorse usate dalle istanze di HttpClient.IHttpClientFactory tracks and disposes resources used by HttpClient instances. Le istanze di HttpClient possono essere considerate a livello generale come oggetti .NET che non richiedono l'eliminazione.The HttpClient instances can generally be treated as .NET objects not requiring disposal.

Mantenere una sola istanza di HttpClient attiva per un lungo periodo di tempo è un modello comune usato prima dell'avvento di IHttpClientFactory.Keeping a single HttpClient instance alive for a long duration is a common pattern used before the inception of IHttpClientFactory. Questo modello non è più necessario dopo la migrazione a IHttpClientFactory.This pattern becomes unnecessary after migrating to IHttpClientFactory.

Alternative a IHttpClientFactoryAlternatives to IHttpClientFactory

L'uso di IHttpClientFactory in un'app abilitata per la funzionalità consente DI evitare:Using IHttpClientFactory in a DI-enabled app avoids:

  • Problemi di esaurimento delle risorse raggruppando le istanze HttpMessageHandler.Resource exhaustion problems by pooling HttpMessageHandler instances.
  • Problemi relativi a DNS obsoleti ciclando HttpMessageHandler istanze a intervalli regolari.Stale DNS problems by cycling HttpMessageHandler instances at regular intervals.

Esistono modi alternativi per risolvere i problemi precedenti utilizzando un'istanza di SocketsHttpHandler di lunga durata.There are alternative ways to solve the preceding problems using a long-lived SocketsHttpHandler instance.

  • Creare un'istanza di SocketsHttpHandler quando l'app viene avviata e usata per il ciclo di vita dell'app.Create an instance of SocketsHttpHandler when the app starts and use it for the life of the app.
  • Configurare PooledConnectionLifetime su un valore appropriato in base alle ore di aggiornamento DNS.Configure PooledConnectionLifetime to an appropriate value based on DNS refresh times.
  • Creare istanze di HttpClient usando new HttpClient(handler, disposeHandler: false) in base alle esigenze.Create HttpClient instances using new HttpClient(handler, disposeHandler: false) as needed.

Gli approcci precedenti risolvono i problemi di gestione delle risorse che IHttpClientFactory risolve in modo analogo.The preceding approaches solve the resource management problems that IHttpClientFactory solves in a similar way.

  • Il SocketsHttpHandler condivide le connessioni tra le istanze di HttpClient.The SocketsHttpHandler shares connections across HttpClient instances. Questa condivisione impedisce l'esaurimento del socket.This sharing prevents socket exhaustion.
  • Il SocketsHttpHandler cicla le connessioni in base a PooledConnectionLifetime per evitare problemi DNS non aggiornati.The SocketsHttpHandler cycles connections according to PooledConnectionLifetime to avoid stale DNS problems.

CookieCookies

Le istanze di HttpMessageHandler in pool generano CookieContainer oggetti condivisi.The pooled HttpMessageHandler instances results in CookieContainer objects being shared. La condivisione di oggetti CookieContainer non prevista spesso genera codice errato.Unanticipated CookieContainer object sharing often results in incorrect code. Per le app che richiedono cookie, prendere in considerazione una delle seguenti operazioni:For apps that require cookies, consider either:

  • Disabilitazione della gestione automatica dei cookieDisabling automatic cookie handling
  • Evitare IHttpClientFactoryAvoiding IHttpClientFactory

Chiamare ConfigurePrimaryHttpMessageHandler per disabilitare la gestione automatica dei cookie:Call ConfigurePrimaryHttpMessageHandler to disable automatic cookie handling:

services.AddHttpClient("configured-disable-automatic-cookies")
    .ConfigurePrimaryHttpMessageHandler(() =>
    {
        return new SocketsHttpHandler()
        {
            UseCookies = false,
        };
    });

RegistrazioneLogging

I client creati tramite IHttpClientFactory registrano i messaggi di log per tutte le richieste.Clients created via IHttpClientFactory record log messages for all requests. Per visualizzare i messaggi di log predefiniti, abilitare il livello di informazioni appropriato nella configurazione di registrazione.Enable the appropriate information level in your logging configuration to see the default log messages. La registrazione aggiuntiva, ad esempio quella delle intestazioni delle richieste, è inclusa solo a livello di traccia.Additional logging, such as the logging of request headers, is only included at trace level.

La categoria di log usata per ogni client include il nome del client.The log category used for each client includes the name of the client. Un client denominato MyNamedClient, ad esempio, registra i messaggi con una categoria System.Net.Http.HttpClient.MyNamedClient.LogicalHandler.A client named MyNamedClient, for example, logs messages with a category of System.Net.Http.HttpClient.MyNamedClient.LogicalHandler. I messaggi con suffisso LogicalHandler sono esterni alla pipeline del gestore delle richieste.Messages suffixed with LogicalHandler occur outside the request handler pipeline. Nella richiesta i messaggi vengono registrati prima che qualsiasi altro gestore nella pipeline l'abbia elaborata.On the request, messages are logged before any other handlers in the pipeline have processed it. Nella risposta i messaggi vengono registrati dopo che qualsiasi altro gestore nella pipeline ha ricevuto la risposta.On the response, messages are logged after any other pipeline handlers have received the response.

La registrazione avviene anche all'interno della pipeline del gestore delle richieste.Logging also occurs inside the request handler pipeline. Nell'esempio MyNamedClient, i messaggi vengono registrati nella categoria di log System.Net.Http.HttpClient.MyNamedClient.ClientHandler.In the MyNamedClient example, those messages are logged against the log category 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.For the request, this occurs after all other handlers have run and immediately before the request is sent out on the network. Nella risposta la registrazione include lo stato della risposta prima di restituirla attraverso la pipeline del gestore.On the response, this logging includes the state of the response before it passes back through the handler pipeline.

L'abilitazione della registrazione all'esterno e all'interno della pipeline consente l'ispezione delle modifiche apportate da altri gestori nella pipeline.Enabling logging outside and inside the pipeline enables inspection of the changes made by the other pipeline handlers. Le modifiche possono ad esempio riguardare le intestazioni delle richieste o il codice di stato della risposta.This may include changes to request headers, for example, or to the response status code.

L'inclusione del nome del client nella categoria di log consente di filtrare i log in base a client denominati specifici, se necessario.Including the name of the client in the log category enables log filtering for specific named clients where necessary.

Configurare HttpMessageHandlerConfigure the HttpMessageHandler

Può essere necessario controllare la configurazione dell'elemento HttpMessageHandler interno usato da un client.It may be necessary to control the configuration of the inner HttpMessageHandler used by a client.

Quando si aggiungono client denominati o tipizzati viene restituito un elemento IHttpClientBuilder.An IHttpClientBuilder is returned when adding named or typed clients. È possibile usare il metodo di estensione ConfigurePrimaryHttpMessageHandler per definire un delegato.The ConfigurePrimaryHttpMessageHandler extension method can be used to define a delegate. Il delegato viene usato per creare e configurare l'elemento HttpMessageHandler primario usato dal client:The delegate is used to create and configure the primary HttpMessageHandler used by that client:

services.AddHttpClient("configured-inner-handler")
    .ConfigurePrimaryHttpMessageHandler(() =>
    {
        return new HttpClientHandler()
        {
            AllowAutoRedirect = false,
            UseDefaultCredentials = true
        };
    });

Usare IHttpClientFactory in un'app consoleUse IHttpClientFactory in a console app

In un'app console aggiungere al progetto i riferimenti ai pacchetti seguenti:In a console app, add the following package references to the project:

Nell'esempio seguente:In the following example:

  • IHttpClientFactory è registrato nel contenitore di servizi dell'host generico.IHttpClientFactory is registered in the Generic Host's service container.
  • MyService crea un'istanza della factory client dal servizio, che viene usata per creare una classe HttpClient.MyService creates a client factory instance from the service, which is used to create an HttpClient. HttpClient viene usato per recuperare una pagina Web.HttpClient is used to retrieve a webpage.
  • Main crea un ambito per eseguire il metodo GetPage del servizio e scrivere i primi 500 caratteri del contenuto della pagina Web nella console.Main creates a scope to execute the service's GetPage method and write the first 500 characters of the webpage content to the 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();

        using (var serviceScope = host.Services.CreateScope())
        {
            var services = serviceScope.ServiceProvider;

            try
            {
                var myService = services.GetRequiredService<IMyService>();
                var pageContent = await myService.GetPage();

                Console.WriteLine(pageContent.Substring(0, 500));
            }
            catch (Exception ex)
            {
                var logger = 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}";
            }
        }
    }
}

Risorse aggiuntiveAdditional resources

Di Glenn Condron, Ryan Nowak e Steve GordonBy Glenn Condron, Ryan Nowak, and Steve Gordon

È possibile registrare e usare un'interfaccia IHttpClientFactory per configurare e creare istanze di HttpClient in un'app.An IHttpClientFactory can be registered and used to configure and create HttpClient instances in an app. I vantaggi offerti sono i seguenti:It offers the following benefits:

  • Offre una posizione centrale per la denominazione e la configurazione di istanze di HttpClient logiche.Provides a central location for naming and configuring logical HttpClient instances. Ad esempio, è possibile registrare e configurare un client github per accedere a GitHub.For example, a github client can be registered and configured to access GitHub. È possibile registrare un client predefinito per altri scopi.A default client can be registered for other purposes.
  • 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.Codifies the concept of outgoing middleware via delegating handlers in HttpClient and provides extensions for Polly-based middleware to take advantage of that.
  • 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.Manages the pooling and lifetime of underlying HttpClientMessageHandler instances to avoid common DNS problems that occur when manually managing HttpClient lifetimes.
  • Aggiunge un'esperienza di registrazione configurabile, tramite ILogger, per tutte le richieste inviate attraverso i client creati dalla factory.Adds a configurable logging experience (via ILogger) for all requests sent through clients created by the factory.

Visualizzare o scaricare il codice di esempio (procedura per il download)View or download sample code (how to download)

PrerequisitesPrerequisites

I progetti destinati a .NET Framework richiedono l'installazione del pacchetto NuGet Microsoft.Extensions.Http.Projects targeting .NET Framework require installation of the Microsoft.Extensions.Http NuGet package. I progetti destinati a .NET Core che fanno riferimento al metapacchetto Microsoft.AspNetCore.All sono già inclusi nel pacchetto Microsoft.Extensions.Http.Projects that target .NET Core and reference the Microsoft.AspNetCore.App metapackage already include the Microsoft.Extensions.Http package.

Modelli di consumoConsumption patterns

IHttpClientFactory può essere usato in un'app in diversi modi:There are several ways IHttpClientFactory can be used in an app:

Nessuno di questi modi può essere considerato superiore a un altro.None of them are strictly superior to another. L'approccio migliore dipende dai vincoli dell'app.The best approach depends upon the app's constraints.

Utilizzo di baseBasic usage

È possibile registrare IHttpClientFactory chiamando il metodo di estensione AddHttpClient in IServiceCollection, all'interno del metodo Startup.ConfigureServices.The IHttpClientFactory can be registered by calling the AddHttpClient extension method on the IServiceCollection, inside the Startup.ConfigureServices method.

services.AddHttpClient();

Dopo la registrazione, il codice può accettare un'interfaccia IHttpClientFactory ovunque sia possibile inserire servizi con l'inserimento di dipendenze.Once registered, code can accept an IHttpClientFactory anywhere services can be injected with dependency injection (DI). Il IHttpClientFactory può essere utilizzato per creare un'istanza di HttpClient:The IHttpClientFactory can be used to create an HttpClient instance:

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/aspnet/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.Using IHttpClientFactory in this fashion is a good way to refactor an existing app. Non influisce in alcun modo sulle modalità d'uso di HttpClient.It has no impact on the way HttpClient is used. Nelle posizioni in cui vengono attualmente create istanze di HttpClient, sostituire le occorrenze con una chiamata a CreateClient.In places where HttpClient instances are currently created, replace those occurrences with a call to CreateClient.

Client denominatiNamed clients

Se un'app richiede molti usi distinti di HttpClient, ognuno con una configurazione diversa, un'opzione è l'uso di client denominati.If an app requires many distinct uses of HttpClient, each with a different configuration, an option is to use named clients. La configurazione di un HttpClient denominato può essere specificata durante la registrazione in Startup.ConfigureServices.Configuration for a named HttpClient can be specified during registration 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.In the preceding code, AddHttpClient is called, providing the name github. Al client viene applicata una configurazione predefinita, ovvero l'indirizzo di base e due intestazioni necessari per l'uso dell'API GitHub.This client has some default configuration applied—namely the base address and two headers required to work with the GitHub API.

Ogni volta che CreateClient viene chiamato, verrà creata una nuova istanza di HttpClient e verrà chiamata l'azione di configurazione.Each time CreateClient is called, a new instance of HttpClient is created and the configuration action is called.

Per usare un client denominato, è possibile passare un parametro di stringa a CreateClient.To consume a named client, a string parameter can be passed to CreateClient. Specificare il nome del client da creare:Specify the name of the client to be created:

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/aspnet/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.In the preceding code, the request doesn't need to specify a hostname. Poiché viene usato l'indirizzo di base configurato per il client, è possibile passare solo il percorso.It can pass just the path, since the base address configured for the client is used.

Client tipizzatiTyped clients

Client tipizzati:Typed clients:

  • Offrono le stesse funzionalità dei client denominati senza la necessità di usare le stringhe come chiavi.Provide the same capabilities as named clients without the need to use strings as keys.
  • Offrono l'aiuto di IntelliSense e del compilatore quando si usano i client.Provides IntelliSense and compiler help when consuming clients.
  • La configurazione e l'interazione con un particolare HttpClient può avvenire in un'unica posizione.Provide a single location to configure and interact with a particular HttpClient. Per un singolo endpoint back-end è ad esempio possibile usare un unico client tipizzato che incapsuli tutta la logica relativa all'endpoint.For example, a single typed client might be used for a single backend endpoint and encapsulate all logic dealing with that endpoint.
  • Usano l'inserimento di dipendenze e possono essere inseriti dove necessario nell'app.Work with DI and can be injected where required in your app.

Un client tipizzato accetta un parametro HttpClient nel costruttore:A typed client accepts an HttpClient parameter in its constructor:

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/aspnet/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.In the preceding code, the configuration is moved into the typed client. L'oggetto HttpClient viene esposto come una proprietà pubblica.The HttpClient object is exposed as a public property. È possibile definire metodi di API specifiche che espongono la funzionalità HttpClient.It's possible to define API-specific methods that expose HttpClient functionality. Il metodo GetAspNetDocsIssues incapsula il codice necessario per eseguire una query e analizzare gli ultimi problemi aperti in un repository GitHub.The GetAspNetDocsIssues method encapsulates the code needed to query for and parse out the latest open issues from a GitHub repository.

Per registrare un client tipizzato è possibile usare il metodo di estensione AddHttpClient generico all'interno di Startup.ConfigureServices, specificando la classe del client tipizzato:To register a typed client, the generic AddHttpClient extension method can be used within Startup.ConfigureServices, specifying the typed client class:

services.AddHttpClient<GitHubService>();

Il client tipizzato viene registrato come temporaneo nell'inserimento di dipendenze.The typed client is registered as transient with DI. Il client tipizzato può essere inserito e usato direttamente:The typed client can be injected and consumed directly:

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:If preferred, the configuration for a typed client can be specified during registration in Startup.ConfigureServices, rather than in the typed client's constructor:

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.It's possible to entirely encapsulate the HttpClient within a typed client. Anziché esporlo come una proprietà, è possibile specificare metodi pubblici che chiamano l'istanza di HttpClient internamente.Rather than exposing it as a property, public methods can be provided which call the HttpClient instance internally.

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.In the preceding code, the HttpClient is stored as a private field. L'accesso per effettuare chiamate esterne passa attraverso il metodo GetRepos.All access to make external calls goes through the GetRepos method.

Client generatiGenerated clients

È possibile usare IHttpClientFactory in combinazione con altre librerie di terze parti, ad esempio Refit.IHttpClientFactory can be used in combination with other third-party libraries such as Refit. Refit è una libreria REST per .NET.Refit is a REST library for .NET. Converte le API REST in interfacce live.It converts REST APIs into live interfaces. Un'implementazione dell'interfaccia viene generata dinamicamente da RestService, usando HttpClient per effettuare le chiamate HTTP esterne.An implementation of the interface is generated dynamically by the RestService, using HttpClient to make the external HTTP calls.

Per rappresentare l'API esterna e la relativa risposta vengono definite un'interfaccia e una risposta:An interface and a reply are defined to represent the external API and its response:

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:A typed client can be added, using Refit to generate the implementation:

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:The defined interface can be consumed where necessary, with the implementation provided by DI and 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 uscitaOutgoing request middleware

HttpClient include già il concetto di delega di gestori concatenati per le richieste HTTP in uscita.HttpClient already has the concept of delegating handlers that can be linked together for outgoing HTTP requests. IHttpClientFactory semplifica la definizione dei gestori da applicare per ogni client denominato.The IHttpClientFactory makes it easy to define the handlers to apply for each named client. Supporta la registrazione e il concatenamento di più gestori per creare una pipeline di middleware per le richieste in uscita.It supports registration and chaining of multiple handlers to build an outgoing request middleware pipeline. Ognuno di questi gestori è in grado di eseguire operazioni prima e dopo la richiesta in uscita.Each of these handlers is able to perform work before and after the outgoing request. Questo modello è simile alla pipeline di middleware in ingresso in ASP.NET Core.This pattern is similar to the inbound middleware pipeline 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.The pattern provides a mechanism to manage cross-cutting concerns around HTTP requests, including caching, error handling, serialization, and logging.

Per creare un gestore, definire una classe che deriva da DelegatingHandler.To create a handler, define a class deriving from DelegatingHandler. Eseguire l'override del metodo SendAsync per eseguire il codice prima di passare la richiesta al gestore successivo nella pipeline:Override the SendAsync method to execute code before passing the request to the next handler in the 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.The preceding code defines a basic handler. Verifica se un'intestazione X-API-KEY è stata inclusa nella richiesta.It checks to see if an X-API-KEY header has been included on the request. Se l'intestazione non è presente, può evitare la chiamata HTTP e restituire una risposta appropriata.If the header is missing, it can avoid the HTTP call and return a suitable response.

Durante la registrazione, è possibile aggiungere uno o più gestori alla configurazione per un HttpClient.During registration, one or more handlers can be added to the configuration for an HttpClient. Questa attività viene eseguita tramite metodi di estensione in IHttpClientBuilder.This task is accomplished via extension methods on the 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.In the preceding code, the ValidateHeaderHandler is registered with DI. Il gestore deve essere registrato nell'inserimento di dipendenze come servizio temporaneo, senza definizione di ambito.The handler must be registered in DI as a transient service, never scoped. Se il gestore viene registrato come un servizio con ambito e i servizi da cui dipende il gestore sono eliminabili:If the handler is registered as a scoped service and any services that the handler depends upon are disposable:

  • I servizi del gestore potrebbero essere eliminati prima che il gestore esca dall'ambito.The handler's services could be disposed before the handler goes out of scope.
  • I servizi del gestore eliminati causano un errore del gestore.The disposed handler services causes the handler to fail.

Dopo la registrazione è possibile chiamare AddHttpMessageHandler, passando il tipo di gestore.Once registered, AddHttpMessageHandler can be called, passing in the handler type.

È possibile registrare più gestori nell'ordine di esecuzione.Multiple handlers can be registered in the order that they should execute. Ogni gestore esegue il wrapping del gestore successivo fino a quando l'elemento HttpClientHandler finale non esegue la richiesta:Each handler wraps the next handler until the final HttpClientHandler executes the request:

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:Use one of the following approaches to share per-request state with message handlers:

  • Passare i dati al gestore usando HttpRequestMessage.Properties.Pass data into the handler using HttpRequestMessage.Properties.
  • Usare IHttpContextAccessor per accedere alla richiesta corrente.Use IHttpContextAccessor to access the current request.
  • Creare un oggetto di archiviazione AsyncLocal personalizzato per passare i dati.Create a custom AsyncLocal storage object to pass the data.

Usare gestori basati su PollyUse Polly-based handlers

IHttpClientFactory si integra con una libreria di terze parti piuttosto diffusa denominata Polly.IHttpClientFactory integrates with a popular third-party library called Polly. Polly è una libreria di gestione degli errori temporanei e di resilienza completa per .NET.Polly is a comprehensive resilience and transient fault-handling library for .NET. Consente agli sviluppatori di esprimere criteri quali Retry, Circuit Breaker, Timeout, Bulkhead Isolation e Fallback in modo fluido e thread-safe.It allows developers to express policies such as Retry, Circuit Breaker, Timeout, Bulkhead Isolation, and Fallback in a fluent and thread-safe manner.

Per consentire l'uso dei criteri Polly con le istanze configurate di HttpClient sono disponibili metodi di estensione.Extension methods are provided to enable the use of Polly policies with configured HttpClient instances. Le estensioni Polly:The Polly extensions:

  • Supportano l'aggiunta di gestori basati su Polly ai client.Support adding Polly-based handlers to clients.
  • Possono essere usate dopo l'installazione del pacchetto NuGet Microsoft.Extensions.Http.Polly.Can be used after installing the Microsoft.Extensions.Http.Polly NuGet package. Il pacchetto non è incluso nel framework condiviso ASP.NET Core.The package isn't included in the ASP.NET Core shared framework.

Gestire gli errori temporaneiHandle transient faults

La maggior parte degli errori comuni si verifica quando le chiamate HTTP esterne sono temporanee.Most common faults occur when external HTTP calls are transient. Per definire un criterio in grado di gestire gli errori temporanei è disponibile un pratico metodo di estensione denominato AddTransientHttpErrorPolicy.A convenient extension method called AddTransientHttpErrorPolicy is included which allows a policy to be defined to handle transient errors. I criteri configurati con questo metodo di estensione gestiscono HttpRequestException, risposte HTTP 5xx e risposte HTTP 408.Policies configured with this extension method handle HttpRequestException, HTTP 5xx responses, and HTTP 408 responses.

L'estensione AddTransientHttpErrorPolicy può essere usata all'interno di Startup.ConfigureServices.The AddTransientHttpErrorPolicy extension can be used within Startup.ConfigureServices. L'estensione consente l'accesso a un oggetto PolicyBuilder configurato per gestire gli errori che rappresentano un possibile errore temporaneo:The extension provides access to a PolicyBuilder object configured to handle errors representing a possible transient fault:

services.AddHttpClient<UnreliableEndpointCallerService>()
    .AddTransientHttpErrorPolicy(p => 
        p.WaitAndRetryAsync(3, _ => TimeSpan.FromMilliseconds(600)));

Nel codice precedente viene definito un criterio WaitAndRetryAsync.In the preceding code, a WaitAndRetryAsync policy is defined. Le richieste non riuscite vengono ritentate fino a tre volte con un ritardo di 600 millisecondi tra i tentativi.Failed requests are retried up to three times with a delay of 600 ms between attempts.

Selezionare i criteri in modo dinamicoDynamically select policies

Per aggiungere gestori basati su Polly è possibile usare altri metodi di estensione.Additional extension methods exist which can be used to add Polly-based handlers. Una di queste estensioni è AddPolicyHandler, che include più overload.One such extension is AddPolicyHandler, which has multiple overloads. Un overload consente l'ispezione della richiesta al momento della definizione del criterio da applicare:One overload allows the request to be inspected when defining which policy to apply:

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.In the preceding code, if the outgoing request is an HTTP GET, a 10-second timeout is applied. Per qualsiasi altro metodo HTTP viene usato un timeout di 30 secondi.For any other HTTP method, a 30-second timeout is used.

Aggiungere più gestori PollyAdd multiple Polly handlers

In molti casi i criteri Polly vengono annidati per offrire funzionalità avanzate:It's common to nest Polly policies to provide enhanced functionality:

services.AddHttpClient("multiplepolicies")
    .AddTransientHttpErrorPolicy(p => p.RetryAsync(3))
    .AddTransientHttpErrorPolicy(
        p => p.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));

Nell'esempio precedente vengono aggiunti due gestori.In the preceding example, two handlers are added. Il primo usa l'estensione AddTransientHttpErrorPolicy per aggiungere criteri di ripetizione.The first uses the AddTransientHttpErrorPolicy extension to add a retry policy. Le richieste non riuscite vengono ritentate fino a tre volte.Failed requests are retried up to three times. La seconda chiamata a AddTransientHttpErrorPolicy aggiunge criteri dell'interruttore di circuito.The second call to AddTransientHttpErrorPolicy adds a circuit breaker policy. Ulteriori richieste esterne vengono bloccate per 30 secondi nel caso si verifichino cinque tentativi non riusciti consecutivi.Further external requests are blocked for 30 seconds if five failed attempts occur sequentially. I criteri dell'interruttore di circuito sono con stato.Circuit breaker policies are stateful. Tutte le chiamate tramite questo client condividono lo stesso stato di circuito.All calls through this client share the same circuit state.

Aggiungere criteri dal registro PollyAdd policies from the Polly registry

Un approccio alla gestione dei criteri usati di frequente consiste nel definirli una volta e registrarli in un elemento PolicyRegistry.An approach to managing regularly used policies is to define them once and register them with a PolicyRegistry. Per aggiungere un gestore usando un criterio del registro è disponibile un metodo di estensione:An extension method is provided which allows a handler to be added using a policy from the registry:

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.In the preceding code, two policies are registered when the PolicyRegistry is added to the ServiceCollection. Per usare un criterio dal registro viene usato il metodo AddPolicyHandlerFromRegistry passando il nome del criterio da applicare.To use a policy from the registry, the AddPolicyHandlerFromRegistry method is used, passing the name of the policy to apply.

Altre informazioni su IHttpClientFactory e le integrazioni con Polly sono disponibili nel wiki di Polly.Further information about IHttpClientFactory and Polly integrations can be found on the Polly wiki.

Gestione di HttpClient e durataHttpClient and lifetime management

Viene restituita una nuova istanza di HttpClient per ogni chiamata di CreateClient per IHttpClientFactory.A new HttpClient instance is returned each time CreateClient is called on the IHttpClientFactory. È presente un HttpMessageHandler per ogni client denominato.There's an HttpMessageHandler per named client. La factory gestisce le durate delle istanze di HttpMessageHandler.The factory manages the lifetimes of the HttpMessageHandler instances.

IHttpClientFactory esegue il pooling delle istanze di HttpMessageHandler create dalla factory per ridurre il consumo di risorse.IHttpClientFactory pools the HttpMessageHandler instances created by the factory to reduce resource consumption. Un'istanza di HttpMessageHandler può essere riusata dal pool quando si crea una nuova istanza di HttpClient se la relativa durata non è scaduta.An HttpMessageHandler instance may be reused from the pool when creating a new HttpClient instance if its lifetime hasn't expired.

Il pooling dei gestori è consigliabile in quanto ogni gestore gestisce in genere le proprie connessioni HTTP sottostanti.Pooling of handlers is desirable as each handler typically manages its own underlying HTTP connections. La creazione di più gestori del necessario può causare ritardi di connessione.Creating more handlers than necessary can result in connection delays. Alcuni gestori mantengono inoltre le connessioni aperte a tempo indefinito. Ciò può impedire al gestore di reagire alle modifiche DNS.Some handlers also keep connections open indefinitely, which can prevent the handler from reacting to DNS changes.

La durata del gestore predefinito è di due minuti.The default handler lifetime is two minutes. Il valore predefinito può essere sottoposto a override per ogni client denominato.The default value can be overridden on a per named client basis. Per eseguire l'override, chiamare SetHandlerLifetime nell'elemento IHttpClientBuilder restituito al momento della creazione del client:To override it, call SetHandlerLifetime on the IHttpClientBuilder that is returned when creating the client:

services.AddHttpClient("extendedhandlerlifetime")
    .SetHandlerLifetime(TimeSpan.FromMinutes(5));

L'eliminazione del client non è obbligatoria.Disposal of the client isn't required. L'eliminazione annulla le richieste in uscita e garantisce che l'istanza di HttpClient specificata non possa essere usata dopo la chiamata a Dispose.Disposal cancels outgoing requests and guarantees the given HttpClient instance can't be used after calling Dispose. IHttpClientFactory tiene traccia ed elimina le risorse usate dalle istanze di HttpClient.IHttpClientFactory tracks and disposes resources used by HttpClient instances. Le istanze di HttpClient possono essere considerate a livello generale come oggetti .NET che non richiedono l'eliminazione.The HttpClient instances can generally be treated as .NET objects not requiring disposal.

Mantenere una sola istanza di HttpClient attiva per un lungo periodo di tempo è un modello comune usato prima dell'avvento di IHttpClientFactory.Keeping a single HttpClient instance alive for a long duration is a common pattern used before the inception of IHttpClientFactory. Questo modello non è più necessario dopo la migrazione a IHttpClientFactory.This pattern becomes unnecessary after migrating to IHttpClientFactory.

Alternative a IHttpClientFactoryAlternatives to IHttpClientFactory

L'uso di IHttpClientFactory in un'app abilitata per la funzionalità consente DI evitare:Using IHttpClientFactory in a DI-enabled app avoids:

  • Problemi di esaurimento delle risorse raggruppando le istanze HttpMessageHandler.Resource exhaustion problems by pooling HttpMessageHandler instances.
  • Problemi relativi a DNS obsoleti ciclando HttpMessageHandler istanze a intervalli regolari.Stale DNS problems by cycling HttpMessageHandler instances at regular intervals.

Esistono modi alternativi per risolvere i problemi precedenti utilizzando un'istanza di SocketsHttpHandler di lunga durata.There are alternative ways to solve the preceding problems using a long-lived SocketsHttpHandler instance.

  • Creare un'istanza di SocketsHttpHandler quando l'app viene avviata e usata per il ciclo di vita dell'app.Create an instance of SocketsHttpHandler when the app starts and use it for the life of the app.
  • Configurare PooledConnectionLifetime su un valore appropriato in base alle ore di aggiornamento DNS.Configure PooledConnectionLifetime to an appropriate value based on DNS refresh times.
  • Creare istanze di HttpClient usando new HttpClient(handler, disposeHandler: false) in base alle esigenze.Create HttpClient instances using new HttpClient(handler, disposeHandler: false) as needed.

Gli approcci precedenti risolvono i problemi di gestione delle risorse che IHttpClientFactory risolve in modo analogo.The preceding approaches solve the resource management problems that IHttpClientFactory solves in a similar way.

  • Il SocketsHttpHandler condivide le connessioni tra le istanze di HttpClient.The SocketsHttpHandler shares connections across HttpClient instances. Questa condivisione impedisce l'esaurimento del socket.This sharing prevents socket exhaustion.
  • Il SocketsHttpHandler cicla le connessioni in base a PooledConnectionLifetime per evitare problemi DNS non aggiornati.The SocketsHttpHandler cycles connections according to PooledConnectionLifetime to avoid stale DNS problems.

CookieCookies

Le istanze di HttpMessageHandler in pool generano CookieContainer oggetti condivisi.The pooled HttpMessageHandler instances results in CookieContainer objects being shared. La condivisione di oggetti CookieContainer non prevista spesso genera codice errato.Unanticipated CookieContainer object sharing often results in incorrect code. Per le app che richiedono cookie, prendere in considerazione una delle seguenti operazioni:For apps that require cookies, consider either:

  • Disabilitazione della gestione automatica dei cookieDisabling automatic cookie handling
  • Evitare IHttpClientFactoryAvoiding IHttpClientFactory

Chiamare ConfigurePrimaryHttpMessageHandler per disabilitare la gestione automatica dei cookie:Call ConfigurePrimaryHttpMessageHandler to disable automatic cookie handling:

services.AddHttpClient("configured-disable-automatic-cookies")
    .ConfigurePrimaryHttpMessageHandler(() =>
    {
        return new SocketsHttpHandler()
        {
            UseCookies = false,
        };
    });

RegistrazioneLogging

I client creati tramite IHttpClientFactory registrano i messaggi di log per tutte le richieste.Clients created via IHttpClientFactory record log messages for all requests. Per visualizzare i messaggi di log predefiniti, abilitare il livello di informazioni appropriato nella configurazione di registrazione.Enable the appropriate information level in your logging configuration to see the default log messages. La registrazione aggiuntiva, ad esempio quella delle intestazioni delle richieste, è inclusa solo a livello di traccia.Additional logging, such as the logging of request headers, is only included at trace level.

La categoria di log usata per ogni client include il nome del client.The log category used for each client includes the name of the client. Un client denominato MyNamedClient, ad esempio, registra i messaggi con una categoria System.Net.Http.HttpClient.MyNamedClient.LogicalHandler.A client named MyNamedClient, for example, logs messages with a category of System.Net.Http.HttpClient.MyNamedClient.LogicalHandler. I messaggi con suffisso LogicalHandler sono esterni alla pipeline del gestore delle richieste.Messages suffixed with LogicalHandler occur outside the request handler pipeline. Nella richiesta i messaggi vengono registrati prima che qualsiasi altro gestore nella pipeline l'abbia elaborata.On the request, messages are logged before any other handlers in the pipeline have processed it. Nella risposta i messaggi vengono registrati dopo che qualsiasi altro gestore nella pipeline ha ricevuto la risposta.On the response, messages are logged after any other pipeline handlers have received the response.

La registrazione avviene anche all'interno della pipeline del gestore delle richieste.Logging also occurs inside the request handler pipeline. Nell'esempio MyNamedClient, i messaggi vengono registrati nella categoria di log System.Net.Http.HttpClient.MyNamedClient.ClientHandler.In the MyNamedClient example, those messages are logged against the log category 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.For the request, this occurs after all other handlers have run and immediately before the request is sent out on the network. Nella risposta la registrazione include lo stato della risposta prima di restituirla attraverso la pipeline del gestore.On the response, this logging includes the state of the response before it passes back through the handler pipeline.

L'abilitazione della registrazione all'esterno e all'interno della pipeline consente l'ispezione delle modifiche apportate da altri gestori nella pipeline.Enabling logging outside and inside the pipeline enables inspection of the changes made by the other pipeline handlers. Le modifiche possono ad esempio riguardare le intestazioni delle richieste o il codice di stato della risposta.This may include changes to request headers, for example, or to the response status code.

L'inclusione del nome del client nella categoria di log consente di filtrare i log in base a client denominati specifici, se necessario.Including the name of the client in the log category enables log filtering for specific named clients where necessary.

Configurare HttpMessageHandlerConfigure the HttpMessageHandler

Può essere necessario controllare la configurazione dell'elemento HttpMessageHandler interno usato da un client.It may be necessary to control the configuration of the inner HttpMessageHandler used by a client.

Quando si aggiungono client denominati o tipizzati viene restituito un elemento IHttpClientBuilder.An IHttpClientBuilder is returned when adding named or typed clients. È possibile usare il metodo di estensione ConfigurePrimaryHttpMessageHandler per definire un delegato.The ConfigurePrimaryHttpMessageHandler extension method can be used to define a delegate. Il delegato viene usato per creare e configurare l'elemento HttpMessageHandler primario usato dal client:The delegate is used to create and configure the primary HttpMessageHandler used by that client:

services.AddHttpClient("configured-inner-handler")
    .ConfigurePrimaryHttpMessageHandler(() =>
    {
        return new HttpClientHandler()
        {
            AllowAutoRedirect = false,
            UseDefaultCredentials = true
        };
    });

Usare IHttpClientFactory in un'app consoleUse IHttpClientFactory in a console app

In un'app console aggiungere al progetto i riferimenti ai pacchetti seguenti:In a console app, add the following package references to the project:

Nell'esempio seguente:In the following example:

  • IHttpClientFactory è registrato nel contenitore di servizi dell'host generico.IHttpClientFactory is registered in the Generic Host's service container.
  • MyService crea un'istanza della factory client dal servizio, che viene usata per creare una classe HttpClient.MyService creates a client factory instance from the service, which is used to create an HttpClient. HttpClient viene usato per recuperare una pagina Web.HttpClient is used to retrieve a webpage.
  • Main crea un ambito per eseguire il metodo GetPage del servizio e scrivere i primi 500 caratteri del contenuto della pagina Web nella console.Main creates a scope to execute the service's GetPage method and write the first 500 characters of the webpage content to the 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();

        using (var serviceScope = host.Services.CreateScope())
        {
            var services = serviceScope.ServiceProvider;

            try
            {
                var myService = services.GetRequiredService<IMyService>();
                var pageContent = await myService.GetPage();

                Console.WriteLine(pageContent.Substring(0, 500));
            }
            catch (Exception ex)
            {
                var logger = 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 intestazioniHeader propagation middleware

La propagazione dell'intestazione è un middleware supportato dalla community per propagare le intestazioni HTTP dalla richiesta in ingresso alle richieste del client HTTP in uscita.Header propagation is a community supported middleware to propagate HTTP headers from the incoming request to the outgoing HTTP Client requests. Per usare la propagazione delle intestazioni:To use header propagation:

  • Fare riferimento alla porta supportata dalla community del pacchetto HeaderPropagation.Reference the community supported port of the package HeaderPropagation. ASP.NET Core 3,1 e versioni successive supporta Microsoft. AspNetCore. HeaderPropagation.ASP.NET Core 3.1 and later supports Microsoft.AspNetCore.HeaderPropagation.

  • Configurare il middleware e HttpClient in Startup:Configure the middleware and 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:The client includes the configured headers on outbound requests:

    var client = clientFactory.CreateClient("MyForwardingClient");
    var response = client.GetAsync(...);
    

Risorse aggiuntiveAdditional resources