Delen via


HTTP-aanvragen maken met behulp van IHttpClientFactory in ASP.NET Core

Note

Dit is niet de nieuwste versie van dit artikel. Zie de .NET 9-versie van dit artikel voor de huidige release.

Warning

Deze versie van ASP.NET Core wordt niet meer ondersteund. Zie het .NET- en .NET Core-ondersteuningsbeleid voor meer informatie. Zie de .NET 9-versie van dit artikel voor de huidige release.

Important

Deze informatie heeft betrekking op een pre-releaseproduct dat aanzienlijk kan worden gewijzigd voordat het commercieel wordt uitgebracht. Microsoft geeft geen garanties, uitdrukkelijk of impliciet, met betrekking tot de informatie die hier wordt verstrekt.

Zie de .NET 9-versie van dit artikel voor de huidige release.

Door Kirk Larkin, Steve Gordon, Glenn Condron en Ryan Nowak.

Een IHttpClientFactory kan worden geregistreerd en gebruikt voor het configureren en maken van HttpClient exemplaren in een app. IHttpClientFactory biedt de volgende voordelen:

  • Biedt een centrale locatie voor het benoemen en configureren van logische HttpClient instanties. Een client met de naam github kan bijvoorbeeld worden geregistreerd en geconfigureerd voor toegang tot GitHub. Een standaardclient kan worden geregistreerd voor algemene toegang.
  • Codifeert het concept van uitgaande middleware via het delegeren van handlers in HttpClient. Biedt uitbreidingen voor op Polly gebaseerde middleware om te profiteren van het delegeren van handlers in HttpClient.
  • Beheert de pooling en levensduur van onderliggende HttpClientMessageHandler exemplaren. Automatisch beheer voorkomt veelvoorkomende DNS-problemen (Domain Name System) die optreden bij het handmatig beheren van HttpClient levensduur.
  • Voegt een configureerbare logboekregistratie-ervaring (via ILogger) toe voor alle aanvragen die worden verzonden via clients die door de fabriek zijn gemaakt.

De voorbeeldcode in deze onderwerpversie gebruikt System.Text.Json voor het deserialiseren van JSON-inhoud die wordt geretourneerd in HTTP-antwoorden. Voor voorbeelden die gebruikmaken van Json.NET en ReadAsAsync<T>, gebruik de versiekiezer om een 2.x-versie van dit onderwerp te selecteren.

Consumptiepatronen

Er zijn verschillende manieren IHttpClientFactory om in een app te gebruiken:

De beste aanpak is afhankelijk van de vereisten van de app.

Basaal gebruik

Registreer IHttpClientFactory door te bellen naar AddHttpClient in Program.cs:

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddHttpClient();

Een IHttpClientFactory kan worden aangevraagd met behulp van afhankelijkheidsinjectie (DI). De volgende code gebruikt IHttpClientFactory om een HttpClient exemplaar te maken:

public class BasicModel : PageModel
{
    private readonly IHttpClientFactory _httpClientFactory;

    public BasicModel(IHttpClientFactory httpClientFactory) =>
        _httpClientFactory = httpClientFactory;

    public IEnumerable<GitHubBranch>? GitHubBranches { get; set; }

    public async Task OnGet()
    {
        var httpRequestMessage = new HttpRequestMessage(
            HttpMethod.Get,
            "https://api.github.com/repos/dotnet/AspNetCore.Docs/branches")
        {
            Headers =
            {
                { HeaderNames.Accept, "application/vnd.github.v3+json" },
                { HeaderNames.UserAgent, "HttpRequestsSample" }
            }
        };

        var httpClient = _httpClientFactory.CreateClient();
        var httpResponseMessage = await httpClient.SendAsync(httpRequestMessage);

        if (httpResponseMessage.IsSuccessStatusCode)
        {
            using var contentStream =
                await httpResponseMessage.Content.ReadAsStreamAsync();
            
            GitHubBranches = await JsonSerializer.DeserializeAsync
                <IEnumerable<GitHubBranch>>(contentStream);
        }
    }
}

Het gebruik van IHttpClientFactory zoals in het voorgaande voorbeeld is een goede manier om een bestaande app te herstructureren. Het heeft geen invloed op hoe HttpClient wordt gebruikt. Op plaatsen waar HttpClient exemplaren worden gemaakt in een bestaande app, vervangt u deze exemplaren door aanroepen naar CreateClient.

Benoemde clients

Benoemde clients zijn een goede keuze wanneer:

  • Voor de app zijn veel verschillende toepassingen van HttpClientvereist.
  • Veel HttpClients hebben een andere configuratie.

Geef de configuratie op voor een naam HttpClient tijdens de registratie in Program.cs.

builder.Services.AddHttpClient("GitHub", httpClient =>
{
    httpClient.BaseAddress = new Uri("https://api.github.com/");

    // using Microsoft.Net.Http.Headers;
    // The GitHub API requires two headers.
    httpClient.DefaultRequestHeaders.Add(
        HeaderNames.Accept, "application/vnd.github.v3+json");
    httpClient.DefaultRequestHeaders.Add(
        HeaderNames.UserAgent, "HttpRequestsSample");
});

In de voorgaande code is de client geconfigureerd met:

  • Het basisadres https://api.github.com/.
  • Er zijn twee headers vereist om te werken met de GitHub-API.

CreateClient

Elke keer CreateClient wordt het volgende aangeroepen:

  • Er wordt een nieuw exemplaar gemaakt HttpClient .
  • De configuratieactie wordt aangeroepen.

Als u een benoemde client wilt maken, geeft u de naam door in CreateClient:

public class NamedClientModel : PageModel
{
    private readonly IHttpClientFactory _httpClientFactory;

    public NamedClientModel(IHttpClientFactory httpClientFactory) =>
        _httpClientFactory = httpClientFactory;

    public IEnumerable<GitHubBranch>? GitHubBranches { get; set; }

    public async Task OnGet()
    {
        var httpClient = _httpClientFactory.CreateClient("GitHub");
        var httpResponseMessage = await httpClient.GetAsync(
            "repos/dotnet/AspNetCore.Docs/branches");

        if (httpResponseMessage.IsSuccessStatusCode)
        {
            using var contentStream =
                await httpResponseMessage.Content.ReadAsStreamAsync();
            
            GitHubBranches = await JsonSerializer.DeserializeAsync
                <IEnumerable<GitHubBranch>>(contentStream);
        }
    }
}

In de voorgaande code hoeft de aanvraag geen hostnaam op te geven. De code kan alleen het pad doorgeven, omdat het basisadres dat is geconfigureerd voor de client wordt gebruikt.

Getypte clients

Getypte clients:

  • Bied dezelfde mogelijkheden als benoemde clients zonder dat u tekenreeksen als sleutels hoeft te gebruiken.
  • Biedt IntelliSense en compilerhulp voor het gebruik van cliënten.
  • Geef één locatie op om een bepaalde HttpClientlocatie te configureren en ermee te communiceren. Er kan bijvoorbeeld één getypeerde client worden gebruikt:
    • Voor één back-endeindpunt.
    • Als u alle logica voor het eindpunt wilt inkapselen.
  • Werk met DI en kan waar nodig worden geïnjecteerd in de app.

Een getypte client accepteert een HttpClient parameter in de constructor:

public class GitHubService
{
    private readonly HttpClient _httpClient;

    public GitHubService(HttpClient httpClient)
    {
        _httpClient = httpClient;

        _httpClient.BaseAddress = new Uri("https://api.github.com/");

        // using Microsoft.Net.Http.Headers;
        // The GitHub API requires two headers.
        _httpClient.DefaultRequestHeaders.Add(
            HeaderNames.Accept, "application/vnd.github.v3+json");
        _httpClient.DefaultRequestHeaders.Add(
            HeaderNames.UserAgent, "HttpRequestsSample");
    }

    public async Task<IEnumerable<GitHubBranch>?> GetAspNetCoreDocsBranchesAsync() =>
        await _httpClient.GetFromJsonAsync<IEnumerable<GitHubBranch>>(
            "repos/dotnet/AspNetCore.Docs/branches");
}

In de voorgaande code:

  • De configuratie wordt verplaatst naar de getypte client.
  • Het opgegeven HttpClient exemplaar wordt opgeslagen als een privéveld.

API-specifieke methoden kunnen worden gemaakt die functionaliteit beschikbaar HttpClient maken. Met de GetAspNetCoreDocsBranches-methode wordt bijvoorbeeld code ingekapseld om documenten van GitHub-vertakkingen op te halen.

De volgende code roept AddHttpClient aan in Program.cs om de GitHubService getypte clientklasse te registreren:

builder.Services.AddHttpClient<GitHubService>();

De getypte client wordt geregistreerd als tijdelijk bij DI. In de voorgaande code AddHttpClient wordt geregistreerd GitHubService als een tijdelijke service. Deze registratie maakt gebruik van een factory-methode voor het volgende:

  1. Maak een exemplaar van HttpClient.
  2. Maak een exemplaar van GitHubService, door te geven in het exemplaar van HttpClient de constructor.

De getypte client kan worden geïnjecteerd en direct gebruikt.

public class TypedClientModel : PageModel
{
    private readonly GitHubService _gitHubService;

    public TypedClientModel(GitHubService gitHubService) =>
        _gitHubService = gitHubService;

    public IEnumerable<GitHubBranch>? GitHubBranches { get; set; }

    public async Task OnGet()
    {
        try
        {
            GitHubBranches = await _gitHubService.GetAspNetCoreDocsBranchesAsync();
        }
        catch (HttpRequestException)
        {
            // ...
        }
    }
}

De configuratie voor een getypte client kan ook worden opgegeven tijdens de registratie in Program.cs, in plaats van in de constructor van de getypte client:

builder.Services.AddHttpClient<GitHubService>(httpClient =>
{
    httpClient.BaseAddress = new Uri("https://api.github.com/");

    // ...
});

Gegenereerde clients

IHttpClientFactory kan worden gebruikt in combinatie met bibliotheken van derden, zoals Refit. Refit is een REST bibliotheek voor .NET. Het converteert REST API's naar live-interfaces. Aanroep AddRefitClient voor het genereren van een dynamische implementatie van een interface, die wordt gebruikt HttpClient om de externe HTTP-aanroepen uit te voeren.

Een aangepaste interface vertegenwoordigt de externe API:

public interface IGitHubClient
{
    [Get("/repos/dotnet/AspNetCore.Docs/branches")]
    Task<IEnumerable<GitHubBranch>> GetAspNetCoreDocsBranchesAsync();
}

Aanroep AddRefitClient om de dynamische implementatie te genereren en vervolgens aan te roepen ConfigureHttpClient om de onderliggende HttpClientimplementatie te configureren:

builder.Services.AddRefitClient<IGitHubClient>()
    .ConfigureHttpClient(httpClient =>
    {
        httpClient.BaseAddress = new Uri("https://api.github.com/");

        // using Microsoft.Net.Http.Headers;
        // The GitHub API requires two headers.
        httpClient.DefaultRequestHeaders.Add(
            HeaderNames.Accept, "application/vnd.github.v3+json");
        httpClient.DefaultRequestHeaders.Add(
            HeaderNames.UserAgent, "HttpRequestsSample");
    });

Gebruik DI om toegang te krijgen tot de dynamische implementatie van IGitHubClient:

public class RefitModel : PageModel
{
    private readonly IGitHubClient _gitHubClient;

    public RefitModel(IGitHubClient gitHubClient) =>
        _gitHubClient = gitHubClient;

    public IEnumerable<GitHubBranch>? GitHubBranches { get; set; }

    public async Task OnGet()
    {
        try
        {
            GitHubBranches = await _gitHubClient.GetAspNetCoreDocsBranchesAsync();
        }
        catch (ApiException)
        {
            // ...
        }
    }
}

POST-, PUT- en DELETE-aanvragen maken

In de voorgaande voorbeelden gebruiken alle HTTP-aanvragen het GET HTTP-werkwoord. HttpClient ondersteunt ook andere HTTP-woorden, waaronder:

  • POST
  • PUT
  • DELETE
  • PATCH

Zie voor een volledige lijst met ondersteunde HTTP-woorden HttpMethod.

In het volgende voorbeeld ziet u hoe u een HTTP POST-aanvraag maakt:

public async Task CreateItemAsync(TodoItem todoItem)
{
    var todoItemJson = new StringContent(
        JsonSerializer.Serialize(todoItem),
        Encoding.UTF8,
        Application.Json); // using static System.Net.Mime.MediaTypeNames;

    using var httpResponseMessage =
        await _httpClient.PostAsync("/api/TodoItems", todoItemJson);

    httpResponseMessage.EnsureSuccessStatusCode();
}

In de voorgaande code is de CreateItemAsync methode:

  • Serialiseert de TodoItem parameter naar JSON met behulp van System.Text.Json.
  • Hiermee maakt u een exemplaar van StringContent het pakket van de geserialiseerde JSON voor verzending in de hoofdtekst van de HTTP-aanvraag.
  • Aanroepen PostAsync om de JSON-inhoud naar de opgegeven URL te verzenden. Dit is een relatieve URL die wordt toegevoegd aan het HttpClient.BaseAddress.
  • Aanroepen EnsureSuccessStatusCode om een uitzondering te genereren als de antwoordstatuscode geen succes aangeeft.

HttpClient ondersteunt ook andere typen inhoud. Bijvoorbeeld MultipartContent en StreamContent. Zie voor een volledige lijst met ondersteunde inhoud HttpContent.

In het volgende voorbeeld ziet u een HTTP PUT-aanvraag:

public async Task SaveItemAsync(TodoItem todoItem)
{
    var todoItemJson = new StringContent(
        JsonSerializer.Serialize(todoItem),
        Encoding.UTF8,
        Application.Json);

    using var httpResponseMessage =
        await _httpClient.PutAsync($"/api/TodoItems/{todoItem.Id}", todoItemJson);

    httpResponseMessage.EnsureSuccessStatusCode();
}

De voorgaande code is vergelijkbaar met het POST-voorbeeld. De SaveItemAsync methode roept PutAsync in plaats van PostAsync.

In het volgende voorbeeld ziet u een HTTP DELETE-aanvraag:

public async Task DeleteItemAsync(long itemId)
{
    using var httpResponseMessage =
        await _httpClient.DeleteAsync($"/api/TodoItems/{itemId}");

    httpResponseMessage.EnsureSuccessStatusCode();
}

In de voorgaande code roept DeleteItemAsyncde methode aanDeleteAsync. Omdat HTTP DELETE-aanvragen doorgaans geen hoofdtekst bevatten, biedt de DeleteAsync methode geen overbelasting die een exemplaar accepteert.HttpContent

Zie HttpClientvoor meer informatie over het gebruik van verschillende HTTP-woorden met HttpClient.

Middleware voor uitgaande aanvragen

HttpClient heeft het concept van het delegeren van handlers die kunnen worden gekoppeld voor uitgaande HTTP-aanvragen. IHttpClientFactory:

  • Vereenvoudigt het definiëren van de handlers die voor elke benoemde client moeten worden toegepast.
  • Ondersteunt registratie en ketening van meerdere handlers voor het bouwen van een middleware-pijplijn voor uitgaande aanvragen. Elk van deze handlers kan werk uitvoeren voor en na de uitgaande aanvraag. Dit patroon:
    • Is vergelijkbaar met de binnenkomende middleware-pijplijn in ASP.NET Core.
    • Biedt een mechanisme voor het beheren van kruislingse problemen rond HTTP-aanvragen, zoals:
      • caching
      • error handling
      • serialization
      • logging

Een delegerende handler maken:

  • Afgeleid van DelegatingHandler.
  • Overschrijf SendAsync. Voer code uit voordat u de aanvraag doorgeeft aan de volgende handler in de pijplijn:
public class ValidateHeaderHandler : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        if (!request.Headers.Contains("X-API-KEY"))
        {
            return new HttpResponseMessage(HttpStatusCode.BadRequest)
            {
                Content = new StringContent(
                    "The API key header X-API-KEY is required.")
            };
        }

        return await base.SendAsync(request, cancellationToken);
    }
}

Met de voorgaande code wordt gecontroleerd of de X-API-KEY header zich in de aanvraag bevindt. Als X-API-KEY ontbreekt, wordt BadRequest geretourneerd.

Meer dan één handler kan worden toegevoegd aan de configuratie voor een HttpClient met Microsoft.Extensions.DependencyInjection.HttpClientBuilderExtensions.AddHttpMessageHandler:

builder.Services.AddTransient<ValidateHeaderHandler>();

builder.Services.AddHttpClient("HttpMessageHandler")
    .AddHttpMessageHandler<ValidateHeaderHandler>();

In de voorgaande code wordt de ValidateHeaderHandler code geregistreerd bij DI. Zodra geregistreerd, kan AddHttpMessageHandler worden aangeroepen, waarbij het type voor de handler wordt doorgegeven.

Meerdere handlers kunnen worden geregistreerd in de volgorde waarin ze moeten worden uitgevoerd. Elke handler verpakt de volgende handler totdat de laatste HttpClientHandler de aanvraag uitvoert:

builder.Services.AddTransient<SampleHandler1>();
builder.Services.AddTransient<SampleHandler2>();

builder.Services.AddHttpClient("MultipleHttpMessageHandlers")
    .AddHttpMessageHandler<SampleHandler1>()
    .AddHttpMessageHandler<SampleHandler2>();

In de voorgaande code wordt SampleHandler1 eerst uitgevoerd, vóór SampleHandler2.

DI gebruiken in middleware voor uitgaande aanvragen

Wanneer IHttpClientFactory er een nieuwe delegeringshandler wordt gemaakt, wordt DI gebruikt om te voldoen aan de constructorparameters van de handler. IHttpClientFactory maakt een afzonderlijk DI-bereik voor elke handler, wat kan leiden tot verrassend gedrag wanneer een handler een scoped service verbruikt.

Denk bijvoorbeeld aan de volgende interface en de implementatie, die een taak vertegenwoordigt als een bewerking met een id: OperationId

public interface IOperationScoped
{
    string OperationId { get; }
}

public class OperationScoped : IOperationScoped
{
    public string OperationId { get; } = Guid.NewGuid().ToString()[^4..];
}

Zoals de naam al aangeeft, wordt IOperationScoped geregistreerd bij DI met behulp van een scoped levensduur:

builder.Services.AddScoped<IOperationScoped, OperationScoped>();

De volgende delegerende handler verbruikt en gebruikt IOperationScoped om de X-OPERATION-ID header voor de uitgaande aanvraag in te stellen:

public class OperationHandler : DelegatingHandler
{
    private readonly IOperationScoped _operationScoped;

    public OperationHandler(IOperationScoped operationScoped) =>
        _operationScoped = operationScoped;

    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        request.Headers.Add("X-OPERATION-ID", _operationScoped.OperationId);

        return await base.SendAsync(request, cancellationToken);
    }
}

Navigeer in het HttpRequestsSample downloadbestand naar /Operation en vernieuw de pagina. De waarde van het aanvraagbereik wordt voor elke aanvraag gewijzigd, maar de scopewaarde van de handler wordt slechts elke 5 seconden gewijzigd.

Handlers kunnen gebruikmaken van services van enige omvang. Services waarop handlers afhankelijk zijn, worden verwijderd wanneer de handler wordt verwijderd.

Gebruik een van de volgende methoden om de status per aanvraag te delen met berichthandlers:

Op Polly gebaseerde handlers gebruiken

IHttpClientFactory kan worden geïntegreerd met de bibliotheek van derden Polly. Polly is een uitgebreide bibliotheek voor tolerantie en tijdelijke foutafhandeling voor .NET. Hiermee kunnen ontwikkelaars beleid uitdrukken, zoals Retry, Circuit Breaker, Timeout, Bulkhead Isolation en Fallback op een vloeiende en thread-veilige manier.

Extensiemethoden worden geboden om het gebruik van Polly-beleidsregels met geconfigureerde HttpClient exemplaren in te schakelen. De Polly-extensies ondersteunen het toevoegen van op Polly gebaseerde handlers aan clients. Polly vereist het Microsoft.Extensions.Http.Polly NuGet-pakket.

Tijdelijke fouten afhandelen

Fouten treden meestal op wanneer externe HTTP-aanroepen tijdelijk zijn. AddTransientHttpErrorPolicy hiermee kan een beleid worden gedefinieerd voor het afhandelen van tijdelijke fouten. Beleidsregels die zijn geconfigureerd met AddTransientHttpErrorPolicy de volgende antwoorden verwerken:

AddTransientHttpErrorPolicy biedt toegang tot een PolicyBuilder object dat is geconfigureerd voor het afhandelen van fouten die een mogelijke tijdelijke fout vertegenwoordigen:

builder.Services.AddHttpClient("PollyWaitAndRetry")
    .AddTransientHttpErrorPolicy(policyBuilder =>
        policyBuilder.WaitAndRetryAsync(
            3, retryNumber => TimeSpan.FromMilliseconds(600)));

In de voorgaande code wordt een WaitAndRetryAsync beleid gedefinieerd. Mislukte aanvragen worden maximaal drie keer opnieuw geprobeerd met een vertraging van 600 ms tussen pogingen.

Dynamisch beleid selecteren

Extensiemethoden worden geleverd om bijvoorbeeld AddPolicyHandlerop Polly gebaseerde handlers toe te voegen. De volgende AddPolicyHandler overload inspecteert het verzoek om te bepalen welk beleid moet worden toegepast.

var timeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(
    TimeSpan.FromSeconds(10));
var longTimeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(
    TimeSpan.FromSeconds(30));

builder.Services.AddHttpClient("PollyDynamic")
    .AddPolicyHandler(httpRequestMessage =>
        httpRequestMessage.Method == HttpMethod.Get ? timeoutPolicy : longTimeoutPolicy);

Als de uitgaande aanvraag een HTTP GET is, wordt in de voorgaande code een time-out van 10 seconden toegepast. Voor elke andere HTTP-methode wordt een time-out van 30 seconden gebruikt.

Meerdere Polly-handlers toevoegen

Het is gebruikelijk om Polly-beleid te nesten:

builder.Services.AddHttpClient("PollyMultiple")
    .AddTransientHttpErrorPolicy(policyBuilder =>
        policyBuilder.RetryAsync(3))
    .AddTransientHttpErrorPolicy(policyBuilder =>
        policyBuilder.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));

In het voorgaande voorbeeld:

  • Er worden twee handlers toegevoegd.
  • De eerste handler gebruikt AddTransientHttpErrorPolicy om een beleid voor opnieuw proberen toe te voegen. Mislukte aanvragen worden maximaal drie keer opnieuw geprobeerd.
  • Met de tweede AddTransientHttpErrorPolicy aanroep wordt een beleid voor circuitonderbrekers toegevoegd. Verdere externe aanvragen worden 30 seconden geblokkeerd als 5 mislukte pogingen opeenvolgend plaatsvinden. Circuitonderbreker beleid is toestandsafhankelijk. Alle aanroepen via deze client delen dezelfde circuitstatus.

Beleid toevoegen uit het Polly-register

Een benadering voor het beheren van regelmatig gebruikte beleidsregels is om ze eenmaal te definiëren en te registreren bij een PolicyRegistry. Voorbeeld:

var timeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(
    TimeSpan.FromSeconds(10));
var longTimeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(
    TimeSpan.FromSeconds(30));

var policyRegistry = builder.Services.AddPolicyRegistry();

policyRegistry.Add("Regular", timeoutPolicy);
policyRegistry.Add("Long", longTimeoutPolicy);

builder.Services.AddHttpClient("PollyRegistryRegular")
    .AddPolicyHandlerFromRegistry("Regular");

builder.Services.AddHttpClient("PollyRegistryLong")
    .AddPolicyHandlerFromRegistry("Long");

In de voorgaande code:

  • Er worden twee beleidsregels RegularLongtoegevoegd aan het Polly-register.
  • AddPolicyHandlerFromRegistry configureert individuele benoemde clients om deze beleidsregels uit het Polly-register te gebruiken.

Zie de IHttpClientFactory voor meer informatie over en Polly-integraties.

HttpClient en levensduurbeheer

Er wordt telkens een nieuw HttpClient exemplaar geretourneerd wanneer CreateClient deze IHttpClientFactorywordt aangeroepen. Een HttpMessageHandler wordt gecreëerd per benoemde cliënt. De fabriek beheert de levensduur van de HttpMessageHandler exemplaren.

IHttpClientFactory voegt de instanties van HttpMessageHandler die door de fabriek zijn gecreëerd samen om het gebruik van middelen te verminderen. Een HttpMessageHandler exemplaar kan opnieuw worden gebruikt vanuit de pool bij het maken van een nieuw HttpClient exemplaar als de levensduur ervan niet is verlopen.

Pooling van handlers is wenselijk omdat elke handler doorgaans zijn eigen onderliggende HTTP-verbindingen beheert. Het maken van meer handlers dan nodig is, kan leiden tot verbindingsvertragingen. Sommige handlers houden verbindingen ook voor onbepaalde tijd open, waardoor de handler niet kan reageren op DNS-wijzigingen (Domain Name System).

De standaardlevensduur van de handler is twee minuten. De standaardwaarde kan per benoemde client worden overschreven:

builder.Services.AddHttpClient("HandlerLifetime")
    .SetHandlerLifetime(TimeSpan.FromMinutes(5));

HttpClient exemplaren kunnen over het algemeen worden behandeld als .NET-objecten die geen verwijdering vereisen. Opruimen annuleert uitgaande aanvragen en garandeert dat de gegeven HttpClient instantie na gebruik van Dispose niet meer kan worden gebruikt. IHttpClientFactory houdt de resources bij en verwijdert deze die door exemplaren van HttpClient worden gebruikt.

Het levend houden van één HttpClient exemplaar voor een lange duur is een veelgebruikte methode voordat de opkomst van IHttpClientFactory. Dit patroon wordt overbodig na de migratie naar IHttpClientFactory.

Alternatieven voor IHttpClientFactory

Door gebruik te maken van IHttpClientFactory in een DI-ingeschakelde applicatie worden de volgende zaken vermeden:

  • Problemen met uitputting van middelen door het groeperen van HttpMessageHandler instanties.
  • Verouderde DNS-problemen oplossen door HttpMessageHandler instanties regelmatig te rouleren.

Er zijn alternatieve manieren om de voorgaande problemen op te lossen met behulp van een SocketsHttpHandler-exemplaar met een lange levensduur.

  • Maak een exemplaar van SocketsHttpHandler wanneer de app wordt gestart en gebruik deze voor de levensduur van de app.
  • Configureer PooledConnectionLifetime naar een geschikte waarde op basis van DNS-vernieuwingstijden.
  • Maak HttpClient-instanties zo nodig met new HttpClient(handler, disposeHandler: false).

Met de voorgaande benaderingen worden de problemen met resourcebeheer op een vergelijkbare manier opgelost als IHttpClientFactory.

  • De SocketsHttpHandler deelt verbindingen tussen HttpClient exemplaren. Dit delen voorkomt uitputting van sockets.
  • De SocketsHttpHandler cyclet verbindingen volgens PooledConnectionLifetime om verouderde DNS-problemen te voorkomen.

Logging

Clients die zijn gemaakt via IHttpClientFactory leggen logboekberichten vast voor alle verzoeken. Schakel het juiste informatieniveau in de logboekconfiguratie in om de standaardlogboekberichten te zien. Aanvullende logboeken, zoals de logboeken van aanvraagheaders, worden alleen opgenomen op trace-niveau.

De logboekcategorie die voor elke client wordt gebruikt, bevat de naam van de client. Een client met de naam MyNamedClient registreert bijvoorbeeld berichten met een categorie System.Net.Http.HttpClient. MyNamedClient. LogicalHandler". Berichten met het achtervoegsel LogicalHandler komen voor buiten de pijplijn van de aanvraaghandler. Op de aanvraag worden berichten geregistreerd voordat andere handlers in de pijplijn deze hebben verwerkt. Bij het antwoord worden berichten geregistreerd nadat andere pipeline-handlers het antwoord hebben ontvangen.

Logboekregistratie vindt ook plaats in de pijplijn van de aanvraaghandler. In het voorbeeld van MyNamedClient worden deze berichten vastgelegd met de logboekcategorie System.Net.Http.HttpClient. MyNamedClient. ClientHandler". Voor de aanvraag gebeurt dit nadat alle andere handlers zijn uitgevoerd en direct voordat de aanvraag wordt verzonden. In het antwoord bevat deze logboekregistratie de status van het antwoord voordat deze wordt doorgestuurd via de handler-pijplijn.

Door logboekregistratie buiten en binnen de pijplijn in te schakelen, kunnen de wijzigingen die door de andere pijplijnhandlers zijn aangebracht, worden gecontroleerd. Dit kunnen wijzigingen in aanvraagheaders of de antwoordstatuscode zijn.

Als u de naam van de client in de logboekcategorie opvoegt, kunt u logboekfiltering voor specifieke benoemde clients inschakelen.

HttpMessageHandler configureren

Het kan nodig zijn om de configuratie van de binnenste HttpMessageHandler te beheren die door een client wordt gebruikt.

Er IHttpClientBuilder wordt een geretourneerd bij het toevoegen van benoemde of getypte clients. De ConfigurePrimaryHttpMessageHandler extensiemethode kan worden gebruikt om een gemachtigde te definiëren. De gemachtigde wordt gebruikt voor het maken en configureren van de primaire die HttpMessageHandler door die client wordt gebruikt:

builder.Services.AddHttpClient("ConfiguredHttpMessageHandler")
    .ConfigurePrimaryHttpMessageHandler(() =>
        new HttpClientHandler
        {
            AllowAutoRedirect = true,
            UseDefaultCredentials = true
        });

Cookies

De gegroepeerde HttpMessageHandler exemplaren resulteren in CookieContainer gedeelde objecten. CookieContainer Onverwacht object delen leidt vaak tot onjuiste code. Voor apps waarvoor cookies zijn vereist, kunt u het volgende overwegen:

  • cookie Automatische verwerking uitschakelen
  • Het vermijden van IHttpClientFactory

Aanroep ConfigurePrimaryHttpMessageHandler om automatische cookie verwerking uit te schakelen:

builder.Services.AddHttpClient("NoAutomaticCookies")
    .ConfigurePrimaryHttpMessageHandler(() =>
        new HttpClientHandler
        {
            UseCookies = false
        });

IHttpClientFactory gebruiken in een console-app

Voeg in een console-app de volgende pakketverwijzingen toe aan het project:

In het volgende voorbeeld:

  • IHttpClientFactory en GitHubService zijn geregistreerd in de servicecontainer van de Generic Host .
  • GitHubService wordt aangevraagd bij DI, die op zijn beurt een instantie van IHttpClientFactory aanvraagt.
  • GitHubService gebruikt IHttpClientFactory voor het maken van een instantie van HttpClient, die wordt gebruikt om Docs GitHub-vertakkingen op te halen.
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

var host = new HostBuilder()
    .ConfigureServices(services =>
    {
        services.AddHttpClient();
        services.AddTransient<GitHubService>();
    })
    .Build();

try
{
    var gitHubService = host.Services.GetRequiredService<GitHubService>();
    var gitHubBranches = await gitHubService.GetAspNetCoreDocsBranchesAsync();

    Console.WriteLine($"{gitHubBranches?.Count() ?? 0} GitHub Branches");

    if (gitHubBranches is not null)
    {
        foreach (var gitHubBranch in gitHubBranches)
        {
            Console.WriteLine($"- {gitHubBranch.Name}");
        }
    }
}
catch (Exception ex)
{
    host.Services.GetRequiredService<ILogger<Program>>()
        .LogError(ex, "Unable to load branches from GitHub.");
}

public class GitHubService
{
    private readonly IHttpClientFactory _httpClientFactory;

    public GitHubService(IHttpClientFactory httpClientFactory) =>
        _httpClientFactory = httpClientFactory;

    public async Task<IEnumerable<GitHubBranch>?> GetAspNetCoreDocsBranchesAsync()
    {
        var httpRequestMessage = new HttpRequestMessage(
            HttpMethod.Get,
            "https://api.github.com/repos/dotnet/AspNetCore.Docs/branches")
        {
            Headers =
            {
                { "Accept", "application/vnd.github.v3+json" },
                { "User-Agent", "HttpRequestsConsoleSample" }
            }
        };

        var httpClient = _httpClientFactory.CreateClient();
        var httpResponseMessage = await httpClient.SendAsync(httpRequestMessage);

        httpResponseMessage.EnsureSuccessStatusCode();

        using var contentStream =
            await httpResponseMessage.Content.ReadAsStreamAsync();
        
        return await JsonSerializer.DeserializeAsync
            <IEnumerable<GitHubBranch>>(contentStream);
    }
}

public record GitHubBranch(
    [property: JsonPropertyName("name")] string Name);

Middleware voor headerdoorgifte

Headerdoorgifte is een ASP.NET Core-middleware voor het doorgeven van HTTP-headers van de binnenkomende aanvraag naar de uitgaande HttpClient verzoeken. Koptekst propagatie gebruiken:

  • Installeer het Microsoft.AspNetCore.HeaderPropagation-pakket .

  • Configureer de HttpClient en middleware pijplijn in Program.cs:

    // Add services to the container.
    builder.Services.AddControllers();
    
    builder.Services.AddHttpClient("PropagateHeaders")
        .AddHeaderPropagation();
    
    builder.Services.AddHeaderPropagation(options =>
    {
        options.Headers.Add("X-TraceId");
    });
    
    var app = builder.Build();
    
    // Configure the HTTP request pipeline.
    app.UseHttpsRedirection();
    
    app.UseHeaderPropagation();
    
    app.MapControllers();
    
  • Maak uitgaande aanvragen met behulp van het geconfigureerde HttpClient exemplaar, dat de toegevoegde headers bevat.

Aanvullende bronnen

Door Kirk Larkin, Steve Gordon, Glenn Condron en Ryan Nowak.

Een IHttpClientFactory kan worden geregistreerd en gebruikt voor het configureren en maken van HttpClient exemplaren in een app. IHttpClientFactory biedt de volgende voordelen:

  • Biedt een centrale locatie voor het benoemen en configureren van logische HttpClient instanties. Een client met de naam github kan bijvoorbeeld worden geregistreerd en geconfigureerd voor toegang tot GitHub. Een standaardclient kan worden geregistreerd voor algemene toegang.
  • Codifeert het concept van uitgaande middleware via het delegeren van handlers in HttpClient. Biedt uitbreidingen voor op Polly gebaseerde middleware om te profiteren van het delegeren van handlers in HttpClient.
  • Beheert de pooling en levensduur van onderliggende HttpClientMessageHandler exemplaren. Automatisch beheer voorkomt veelvoorkomende DNS-problemen (Domain Name System) die optreden bij het handmatig beheren van HttpClient levensduur.
  • Voegt een configureerbare logboekregistratie-ervaring (via ILogger) toe voor alle aanvragen die worden verzonden via clients die door de fabriek zijn gemaakt.

Voorbeeldcode bekijken of downloaden (hoe u kunt downloaden).

De voorbeeldcode in deze onderwerpversie gebruikt System.Text.Json voor het deserialiseren van JSON-inhoud die wordt geretourneerd in HTTP-antwoorden. Voor voorbeelden die gebruikmaken van Json.NET en ReadAsAsync<T>, gebruik de versiekiezer om een 2.x-versie van dit onderwerp te selecteren.

Consumptiepatronen

Er zijn verschillende manieren IHttpClientFactory om in een app te gebruiken:

De beste aanpak is afhankelijk van de vereisten van de app.

Basaal gebruik

IHttpClientFactory kan worden geregistreerd door te bellen 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.

Een IHttpClientFactory kan worden aangevraagd met behulp van afhankelijkheidsinjectie (DI). De volgende code gebruikt IHttpClientFactory om een HttpClient exemplaar te maken:

public class BasicUsageModel : PageModel
{
    private readonly IHttpClientFactory _clientFactory;

    public IEnumerable<GitHubBranch> Branches { get; private set; }

    public bool GetBranchesError { get; private set; }

    public BasicUsageModel(IHttpClientFactory clientFactory)
    {
        _clientFactory = clientFactory;
    }

    public async Task OnGet()
    {
        var request = new HttpRequestMessage(HttpMethod.Get,
            "https://api.github.com/repos/dotnet/AspNetCore.Docs/branches");
        request.Headers.Add("Accept", "application/vnd.github.v3+json");
        request.Headers.Add("User-Agent", "HttpClientFactory-Sample");

        var client = _clientFactory.CreateClient();

        var response = await client.SendAsync(request);

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

Het gebruik van IHttpClientFactory zoals in het voorgaande voorbeeld is een goede manier om een bestaande app te herstructureren. Het heeft geen invloed op hoe HttpClient wordt gebruikt. Op plaatsen waar HttpClient exemplaren worden gemaakt in een bestaande app, vervangt u deze exemplaren door aanroepen naar CreateClient.

Benoemde clients

Benoemde clients zijn een goede keuze wanneer:

  • Voor de app zijn veel verschillende toepassingen van HttpClientvereist.
  • Veel HttpClients hebben een andere configuratie.

Configuratie voor een genummerde HttpClient kan tijdens de registratie in Startup.ConfigureServices worden opgegeven.

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

In de voorgaande code is de client geconfigureerd met:

  • Het basisadres https://api.github.com/.
  • Er zijn twee headers vereist om te werken met de GitHub-API.

CreateClient

Elke keer CreateClient wordt het volgende aangeroepen:

  • Er wordt een nieuw exemplaar gemaakt HttpClient .
  • De configuratieactie wordt aangeroepen.

Als u een benoemde client wilt maken, geeft u de naam door in CreateClient:

public class NamedClientModel : PageModel
{
    private readonly IHttpClientFactory _clientFactory;

    public IEnumerable<GitHubPullRequest> PullRequests { get; private set; }

    public bool GetPullRequestsError { get; private set; }

    public bool HasPullRequests => PullRequests.Any();

    public NamedClientModel(IHttpClientFactory clientFactory)
    {
        _clientFactory = clientFactory;
    }

    public async Task OnGet()
    {
        var request = new HttpRequestMessage(HttpMethod.Get,
            "repos/dotnet/AspNetCore.Docs/pulls");

        var client = _clientFactory.CreateClient("github");

        var response = await client.SendAsync(request);

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

In de voorgaande code hoeft de aanvraag geen hostnaam op te geven. De code kan alleen het pad doorgeven, omdat het basisadres dat is geconfigureerd voor de client wordt gebruikt.

Getypte clients

Getypte clients:

  • Bied dezelfde mogelijkheden als benoemde clients zonder dat u tekenreeksen als sleutels hoeft te gebruiken.
  • Biedt IntelliSense en compilerhulp voor het gebruik van cliënten.
  • Geef één locatie op om een bepaalde HttpClientlocatie te configureren en ermee te communiceren. Er kan bijvoorbeeld één getypeerde client worden gebruikt:
    • Voor één back-endeindpunt.
    • Als u alle logica voor het eindpunt wilt inkapselen.
  • Werk met DI en kan waar nodig worden geïnjecteerd in de app.

Een getypte client accepteert een HttpClient parameter in de 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()
    {
        return await Client.GetFromJsonAsync<IEnumerable<GitHubIssue>>(
          "/repos/aspnet/AspNetCore.Docs/issues?state=open&sort=created&direction=desc");
    }
}

In de voorgaande code:

  • De configuratie wordt verplaatst naar de getypte client.
  • Het HttpClient object wordt weergegeven als een openbare eigenschap.

API-specifieke methoden kunnen worden gemaakt die functionaliteit beschikbaar HttpClient maken. Met de GetAspNetDocsIssues methode wordt bijvoorbeeld code ingekapseld om openstaande problemen op te halen.

De volgende code roept AddHttpClient aan binnen Startup.ConfigureServices om een getypte clientklasse te registreren.

services.AddHttpClient<GitHubService>();

De getypte client wordt geregistreerd als tijdelijk bij DI. In de voorgaande code AddHttpClient wordt geregistreerd GitHubService als een tijdelijke service. Deze registratie maakt gebruik van een factory-methode voor het volgende:

  1. Maak een exemplaar van HttpClient.
  2. Maak een exemplaar van GitHubService, door te geven in het exemplaar van HttpClient de constructor.

De getypte client kan worden geïnjecteerd en direct gebruikt.

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

De configuratie voor een getypte client kan worden opgegeven tijdens de registratie in Startup.ConfigureServices, in plaats van in de constructor van de getypte client:

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

De HttpClient kan worden ingekapseld in een getypte client. In plaats van deze als eigenschap weer te geven, definieert u een methode waarmee het HttpClient exemplaar intern wordt aangeroepen:

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

In de voorgaande code wordt de HttpClient code opgeslagen in een privéveld. Toegang tot de HttpClient is via de openbare GetRepos methode.

Gegenereerde clients

IHttpClientFactory kan worden gebruikt in combinatie met bibliotheken van derden, zoals Refit. Refit is een REST bibliotheek voor .NET. Het converteert REST API's naar live-interfaces. Er wordt dynamisch een implementatie van de interface gegenereerd door de RestService, waarbij HttpClient de externe HTTP-aanroepen worden uitgevoerd.

Een interface en een antwoord worden gedefinieerd om de externe API en het bijbehorende antwoord weer te geven:

public interface IHelloClient
{
    [Get("/helloworld")]
    Task<Reply> GetMessageAsync();
}

public class Reply
{
    public string Message { get; set; }
}

Een getypte client kan worden toegevoegd met Behulp van Refit om de implementatie te genereren:

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

De gedefinieerde interface kan waar nodig worden gebruikt, waarbij DI en Refit de implementatie leveren.

[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();
    }
}

POST-, PUT- en DELETE-aanvragen maken

In de voorgaande voorbeelden gebruiken alle HTTP-aanvragen het GET HTTP-werkwoord. HttpClient ondersteunt ook andere HTTP-woorden, waaronder:

  • POST
  • PUT
  • DELETE
  • PATCH

Zie voor een volledige lijst met ondersteunde HTTP-woorden HttpMethod.

In het volgende voorbeeld ziet u hoe u een HTTP POST-aanvraag maakt:

public async Task CreateItemAsync(TodoItem todoItem)
{
    var todoItemJson = new StringContent(
        JsonSerializer.Serialize(todoItem, _jsonSerializerOptions),
        Encoding.UTF8,
        "application/json");

    using var httpResponse =
        await _httpClient.PostAsync("/api/TodoItems", todoItemJson);

    httpResponse.EnsureSuccessStatusCode();
}

In de voorgaande code is de CreateItemAsync methode:

  • Serialiseert de TodoItem parameter naar JSON met behulp van System.Text.Json. Dit maakt gebruik van een exemplaar van JsonSerializerOptions het serialisatieproces te configureren.
  • Hiermee maakt u een exemplaar van StringContent het pakket van de geserialiseerde JSON voor verzending in de hoofdtekst van de HTTP-aanvraag.
  • Aanroepen PostAsync om de JSON-inhoud naar de opgegeven URL te verzenden. Dit is een relatieve URL die wordt toegevoegd aan het HttpClient.BaseAddress.
  • Aanroepen EnsureSuccessStatusCode om een uitzondering te genereren als de antwoordstatuscode niet aangeeft dat deze is geslaagd.

HttpClient ondersteunt ook andere typen inhoud. Bijvoorbeeld MultipartContent en StreamContent. Zie voor een volledige lijst met ondersteunde inhoud HttpContent.

In het volgende voorbeeld ziet u een HTTP PUT-aanvraag:

public async Task SaveItemAsync(TodoItem todoItem)
{
    var todoItemJson = new StringContent(
        JsonSerializer.Serialize(todoItem),
        Encoding.UTF8,
        "application/json");

    using var httpResponse =
        await _httpClient.PutAsync($"/api/TodoItems/{todoItem.Id}", todoItemJson);

    httpResponse.EnsureSuccessStatusCode();
}

De voorgaande code is vergelijkbaar met het POST-voorbeeld. De SaveItemAsync methode roept PutAsync in plaats van PostAsync.

In het volgende voorbeeld ziet u een HTTP DELETE-aanvraag:

public async Task DeleteItemAsync(long itemId)
{
    using var httpResponse =
        await _httpClient.DeleteAsync($"/api/TodoItems/{itemId}");

    httpResponse.EnsureSuccessStatusCode();
}

In de voorgaande code roept DeleteItemAsyncde methode aanDeleteAsync. Omdat HTTP DELETE-aanvragen doorgaans geen hoofdtekst bevatten, biedt de DeleteAsync methode geen overbelasting die een exemplaar accepteert.HttpContent

Zie HttpClientvoor meer informatie over het gebruik van verschillende HTTP-woorden met HttpClient.

Middleware voor uitgaande aanvragen

HttpClient heeft het concept van het delegeren van handlers die kunnen worden gekoppeld voor uitgaande HTTP-aanvragen. IHttpClientFactory:

  • Vereenvoudigt het definiëren van de handlers die voor elke benoemde client moeten worden toegepast.
  • Ondersteunt registratie en ketening van meerdere handlers voor het bouwen van een middleware-pijplijn voor uitgaande aanvragen. Elk van deze handlers kan werk uitvoeren voor en na de uitgaande aanvraag. Dit patroon:
    • Is vergelijkbaar met de binnenkomende middleware-pijplijn in ASP.NET Core.
    • Biedt een mechanisme voor het beheren van kruislingse problemen rond HTTP-aanvragen, zoals:
      • caching
      • error handling
      • serialization
      • logging

Een delegerende handler maken:

  • Afgeleid van DelegatingHandler.
  • Overschrijf SendAsync. Voer code uit voordat u de aanvraag doorgeeft aan de volgende handler in de pijplijn:
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);
    }
}

Met de voorgaande code wordt gecontroleerd of de X-API-KEY header zich in de aanvraag bevindt. Als X-API-KEY ontbreekt, wordt BadRequest geretourneerd.

Meer dan één handler kan worden toegevoegd aan de configuratie voor een HttpClient met 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.

In de voorgaande code wordt de ValidateHeaderHandler code geregistreerd bij DI. Zodra geregistreerd, kan AddHttpMessageHandler worden aangeroepen, waarbij het type voor de handler wordt doorgegeven.

Meerdere handlers kunnen worden geregistreerd in de volgorde waarin ze moeten worden uitgevoerd. Elke handler verpakt de volgende handler totdat de laatste HttpClientHandler de aanvraag uitvoert:

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

DI gebruiken in middleware voor uitgaande aanvragen

Wanneer IHttpClientFactory er een nieuwe delegeringshandler wordt gemaakt, wordt DI gebruikt om te voldoen aan de constructorparameters van de handler. IHttpClientFactory maakt een afzonderlijk DI-bereik voor elke handler, wat kan leiden tot verrassend gedrag wanneer een handler een scoped service verbruikt.

Denk bijvoorbeeld aan de volgende interface en de implementatie, die een taak vertegenwoordigt als een bewerking met een id: OperationId

public interface IOperationScoped 
{
    string OperationId { get; }
}

public class OperationScoped : IOperationScoped
{
    public string OperationId { get; } = Guid.NewGuid().ToString()[^4..];
}

Zoals de naam al aangeeft, wordt IOperationScoped geregistreerd bij DI met behulp van een scoped levensduur:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<TodoContext>(options =>
        options.UseInMemoryDatabase("TodoItems"));

    services.AddHttpContextAccessor();

    services.AddHttpClient<TodoClient>((sp, httpClient) =>
    {
        var httpRequest = sp.GetRequiredService<IHttpContextAccessor>().HttpContext.Request;

        // For sample purposes, assume TodoClient is used in the context of an incoming request.
        httpClient.BaseAddress = new Uri(UriHelper.BuildAbsolute(httpRequest.Scheme,
                                         httpRequest.Host, httpRequest.PathBase));
        httpClient.Timeout = TimeSpan.FromSeconds(5);
    });

    services.AddScoped<IOperationScoped, OperationScoped>();
    
    services.AddTransient<OperationHandler>();
    services.AddTransient<OperationResponseHandler>();

    services.AddHttpClient("Operation")
        .AddHttpMessageHandler<OperationHandler>()
        .AddHttpMessageHandler<OperationResponseHandler>()
        .SetHandlerLifetime(TimeSpan.FromSeconds(5));

    services.AddControllers();
    services.AddRazorPages();
}

De volgende delegerende handler verbruikt en gebruikt IOperationScoped om de X-OPERATION-ID header voor de uitgaande aanvraag in te stellen:

public class OperationHandler : DelegatingHandler
{
    private readonly IOperationScoped _operationService;

    public OperationHandler(IOperationScoped operationScoped)
    {
        _operationService = operationScoped;
    }

    protected async override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        request.Headers.Add("X-OPERATION-ID", _operationService.OperationId);

        return await base.SendAsync(request, cancellationToken);
    }
}

Navigeer in het HttpRequestsSample downloaden naar /Operation en vernieuw deze. De waarde van het aanvraagbereik wordt voor elke aanvraag gewijzigd, maar de scopewaarde van de handler wordt slechts elke 5 seconden gewijzigd.

Handlers kunnen gebruikmaken van services van enige omvang. Services waarop handlers afhankelijk zijn, worden verwijderd wanneer de handler wordt verwijderd.

Gebruik een van de volgende methoden om de status per aanvraag te delen met berichthandlers:

Op Polly gebaseerde handlers gebruiken

IHttpClientFactory kan worden geïntegreerd met de bibliotheek van derden Polly. Polly is een uitgebreide bibliotheek voor tolerantie en tijdelijke foutafhandeling voor .NET. Hiermee kunnen ontwikkelaars beleid uitdrukken, zoals Retry, Circuit Breaker, Timeout, Bulkhead Isolation en Fallback op een vloeiende en thread-veilige manier.

Extensiemethoden worden geboden om het gebruik van Polly-beleidsregels met geconfigureerde HttpClient exemplaren in te schakelen. De Polly-extensies ondersteunen het toevoegen van op Polly gebaseerde handlers aan clients. Polly vereist het Microsoft.Extensions.Http.Polly NuGet-pakket.

Tijdelijke fouten afhandelen

Fouten treden meestal op wanneer externe HTTP-aanroepen tijdelijk zijn. AddTransientHttpErrorPolicy hiermee kan een beleid worden gedefinieerd voor het afhandelen van tijdelijke fouten. Beleidsregels die zijn geconfigureerd met AddTransientHttpErrorPolicy de volgende antwoorden verwerken:

AddTransientHttpErrorPolicy biedt toegang tot een PolicyBuilder object dat is geconfigureerd voor het afhandelen van fouten die een mogelijke tijdelijke fout vertegenwoordigen:

public void ConfigureServices(IServiceCollection services)
{           
    services.AddHttpClient<UnreliableEndpointCallerService>()
        .AddTransientHttpErrorPolicy(p => 
            p.WaitAndRetryAsync(3, _ => TimeSpan.FromMilliseconds(600)));

    // Remaining code deleted for brevity.

In de voorgaande code wordt een WaitAndRetryAsync beleid gedefinieerd. Mislukte aanvragen worden maximaal drie keer opnieuw geprobeerd met een vertraging van 600 ms tussen pogingen.

Dynamisch beleid selecteren

Extensiemethoden worden geleverd om bijvoorbeeld AddPolicyHandlerop Polly gebaseerde handlers toe te voegen. De volgende AddPolicyHandler overload inspecteert het verzoek om te bepalen welk beleid moet worden toegepast.

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

Als de uitgaande aanvraag een HTTP GET is, wordt in de voorgaande code een time-out van 10 seconden toegepast. Voor elke andere HTTP-methode wordt een time-out van 30 seconden gebruikt.

Meerdere Polly-handlers toevoegen

Het is gebruikelijk om Polly-beleid te nesten:

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

In het voorgaande voorbeeld:

  • Er worden twee handlers toegevoegd.
  • De eerste handler gebruikt AddTransientHttpErrorPolicy om een beleid voor opnieuw proberen toe te voegen. Mislukte aanvragen worden maximaal drie keer opnieuw geprobeerd.
  • Met de tweede AddTransientHttpErrorPolicy aanroep wordt een beleid voor circuitonderbrekers toegevoegd. Verdere externe aanvragen worden 30 seconden geblokkeerd als 5 mislukte pogingen opeenvolgend plaatsvinden. Circuitonderbreker beleid is toestandsafhankelijk. Alle aanroepen via deze client delen dezelfde circuitstatus.

Beleid toevoegen uit het Polly-register

Een benadering voor het beheren van regelmatig gebruikte beleidsregels is om ze eenmaal te definiëren en te registreren bij een PolicyRegistry.

In de volgende code:

  • De beleidsregels 'normaal' en 'lang' worden toegevoegd.
  • AddPolicyHandlerFromRegistry voegt de beleidsregels 'regular' en 'long' toe uit het register.
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.

Zie de IHttpClientFactory voor meer informatie over en Polly-integraties.

HttpClient en levensduurbeheer

Er wordt telkens een nieuw HttpClient exemplaar geretourneerd wanneer CreateClient deze IHttpClientFactorywordt aangeroepen. Een HttpMessageHandler wordt gecreëerd per benoemde cliënt. De fabriek beheert de levensduur van de HttpMessageHandler exemplaren.

IHttpClientFactory voegt de instanties van HttpMessageHandler die door de fabriek zijn gecreëerd samen om het gebruik van middelen te verminderen. Een HttpMessageHandler exemplaar kan opnieuw worden gebruikt vanuit de pool bij het maken van een nieuw HttpClient exemplaar als de levensduur ervan niet is verlopen.

Pooling van handlers is wenselijk omdat elke handler doorgaans zijn eigen onderliggende HTTP-verbindingen beheert. Het maken van meer handlers dan nodig is, kan leiden tot verbindingsvertragingen. Sommige handlers houden verbindingen ook voor onbepaalde tijd open, waardoor de handler niet kan reageren op DNS-wijzigingen (Domain Name System).

De standaardlevensduur van de handler is twee minuten. De standaardwaarde kan per benoemde client worden overschreven:

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

    // Remaining code deleted for brevity.

HttpClient exemplaren kunnen over het algemeen worden behandeld als .NET-objecten die geen verwijdering vereisen. Opruimen annuleert uitgaande aanvragen en garandeert dat de gegeven HttpClient instantie na gebruik van Dispose niet meer kan worden gebruikt. IHttpClientFactory houdt de resources bij en verwijdert deze die door exemplaren van HttpClient worden gebruikt.

Het levend houden van één HttpClient exemplaar voor een lange duur is een veelgebruikte methode voordat de opkomst van IHttpClientFactory. Dit patroon wordt overbodig na de migratie naar IHttpClientFactory.

Alternatieven voor IHttpClientFactory

Door gebruik te maken van IHttpClientFactory in een DI-ingeschakelde applicatie worden de volgende zaken vermeden:

  • Problemen met uitputting van middelen door het groeperen van HttpMessageHandler instanties.
  • Verouderde DNS-problemen oplossen door HttpMessageHandler instanties regelmatig te rouleren.

Er zijn alternatieve manieren om de voorgaande problemen op te lossen met behulp van een SocketsHttpHandler-exemplaar met een lange levensduur.

  • Maak een exemplaar van SocketsHttpHandler wanneer de app wordt gestart en gebruik deze voor de levensduur van de app.
  • Configureer PooledConnectionLifetime naar een geschikte waarde op basis van DNS-vernieuwingstijden.
  • Maak HttpClient-instanties zo nodig met new HttpClient(handler, disposeHandler: false).

Met de voorgaande benaderingen worden de problemen met resourcebeheer op een vergelijkbare manier opgelost als IHttpClientFactory.

  • De SocketsHttpHandler deelt verbindingen tussen HttpClient exemplaren. Dit delen voorkomt uitputting van sockets.
  • De SocketsHttpHandler cyclet verbindingen volgens PooledConnectionLifetime om verouderde DNS-problemen te voorkomen.

Cookies

De gegroepeerde HttpMessageHandler exemplaren resulteren in CookieContainer gedeelde objecten. CookieContainer Onverwacht object delen leidt vaak tot onjuiste code. Voor apps waarvoor cookies zijn vereist, kunt u het volgende overwegen:

  • cookie Automatische verwerking uitschakelen
  • Het vermijden van IHttpClientFactory

Aanroep ConfigurePrimaryHttpMessageHandler om automatische cookie verwerking uit te schakelen:

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

Logging

Clients die zijn gemaakt via IHttpClientFactory leggen logboekberichten vast voor alle verzoeken. Schakel het juiste informatieniveau in de logboekconfiguratie in om de standaardlogboekberichten te zien. Aanvullende logboeken, zoals de logboeken van aanvraagheaders, worden alleen opgenomen op trace-niveau.

De logboekcategorie die voor elke client wordt gebruikt, bevat de naam van de client. Een client met de naam MyNamedClient registreert bijvoorbeeld berichten met een categorie System.Net.Http.HttpClient. MyNamedClient. LogicalHandler". Berichten met het achtervoegsel LogicalHandler komen voor buiten de pijplijn van de aanvraaghandler. Op de aanvraag worden berichten geregistreerd voordat andere handlers in de pijplijn deze hebben verwerkt. Bij het antwoord worden berichten geregistreerd nadat andere pipeline-handlers het antwoord hebben ontvangen.

Logboekregistratie vindt ook plaats in de pijplijn van de aanvraaghandler. In het voorbeeld van MyNamedClient worden deze berichten vastgelegd met de logboekcategorie System.Net.Http.HttpClient. MyNamedClient. ClientHandler". Voor de aanvraag gebeurt dit nadat alle andere handlers zijn uitgevoerd en direct voordat de aanvraag wordt verzonden. In het antwoord bevat deze logboekregistratie de status van het antwoord voordat deze wordt doorgestuurd via de handler-pijplijn.

Door logboekregistratie buiten en binnen de pijplijn in te schakelen, kunnen de wijzigingen die door de andere pijplijnhandlers zijn aangebracht, worden gecontroleerd. Dit kunnen wijzigingen in aanvraagheaders of de antwoordstatuscode zijn.

Als u de naam van de client in de logboekcategorie opvoegt, kunt u logboekfiltering voor specifieke benoemde clients inschakelen.

HttpMessageHandler configureren

Het kan nodig zijn om de configuratie van de binnenste HttpMessageHandler te beheren die door een client wordt gebruikt.

Er IHttpClientBuilder wordt een geretourneerd bij het toevoegen van benoemde of getypte clients. De ConfigurePrimaryHttpMessageHandler extensiemethode kan worden gebruikt om een gemachtigde te definiëren. De gemachtigde wordt gebruikt voor het maken en configureren van de primaire die HttpMessageHandler door die client wordt gebruikt:

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

    // Remaining code deleted for brevity.

IHttpClientFactory gebruiken in een console-app

Voeg in een console-app de volgende pakketverwijzingen toe aan het project:

In het volgende voorbeeld:

  • IHttpClientFactory is geregistreerd in de servicecontainer van de Generic Host.
  • MyService maakt een client factory-exemplaar van de service, die wordt gebruikt voor het maken van een HttpClient. HttpClient wordt gebruikt om een webpagina op te halen.
  • Main creëert een scope om de GetPage methode van de service uit te voeren en om de eerste 500 tekens van de inhoud van de webpagina naar de console te schrijven.
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
class Program
{
    static async Task<int> Main(string[] args)
    {
        var builder = new HostBuilder()
            .ConfigureServices((hostContext, services) =>
            {
                services.AddHttpClient();
                services.AddTransient<IMyService, MyService>();
            }).UseConsoleLifetime();

        var host = builder.Build();

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

            Console.WriteLine(pageContent.Substring(0, 500));
        }
        catch (Exception ex)
        {
            var logger = host.Services.GetRequiredService<ILogger<Program>>();

            logger.LogError(ex, "An error occurred.");
        }

        return 0;
    }

    public interface IMyService
    {
        Task<string> GetPage();
    }

    public class MyService : IMyService
    {
        private readonly IHttpClientFactory _clientFactory;

        public MyService(IHttpClientFactory clientFactory)
        {
            _clientFactory = clientFactory;
        }

        public async Task<string> GetPage()
        {
            // Content from BBC One: Dr. Who website (©BBC)
            var request = new HttpRequestMessage(HttpMethod.Get,
                "https://www.bbc.co.uk/programmes/b006q2x0");
            var client = _clientFactory.CreateClient();
            var response = await client.SendAsync(request);

            if (response.IsSuccessStatusCode)
            {
                return await response.Content.ReadAsStringAsync();
            }
            else
            {
                return $"StatusCode: {response.StatusCode}";
            }
        }
    }
}

Middleware voor headerdoorgifte

Headerdoorgifte is een ASP.NET Core-middleware om HTTP-headers van de binnenkomende aanvraag door te geven aan de uitgaande HTTP-clientaanvragen. Koptekst propagatie gebruiken:

  • Verwijs naar het Microsoft.AspNetCore.HeaderPropagation-pakket .

  • Configureer de middleware en 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();
        });
    }
    
  • De client bevat de geconfigureerde headers voor uitgaande aanvragen:

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

Aanvullende bronnen

Door Kirk Larkin, Steve Gordon, Glenn Condron en Ryan Nowak.

Een IHttpClientFactory kan worden geregistreerd en gebruikt voor het configureren en maken van HttpClient exemplaren in een app. IHttpClientFactory biedt de volgende voordelen:

  • Biedt een centrale locatie voor het benoemen en configureren van logische HttpClient instanties. Een client met de naam github kan bijvoorbeeld worden geregistreerd en geconfigureerd voor toegang tot GitHub. Een standaardclient kan worden geregistreerd voor algemene toegang.
  • Codifeert het concept van uitgaande middleware via het delegeren van handlers in HttpClient. Biedt uitbreidingen voor op Polly gebaseerde middleware om te profiteren van het delegeren van handlers in HttpClient.
  • Beheert de pooling en levensduur van onderliggende HttpClientMessageHandler exemplaren. Automatisch beheer voorkomt veelvoorkomende DNS-problemen (Domain Name System) die optreden bij het handmatig beheren van HttpClient levensduur.
  • Voegt een configureerbare logboekregistratie-ervaring (via ILogger) toe voor alle aanvragen die worden verzonden via clients die door de fabriek zijn gemaakt.

Voorbeeldcode bekijken of downloaden (hoe u kunt downloaden).

De voorbeeldcode in deze onderwerpversie gebruikt System.Text.Json voor het deserialiseren van JSON-inhoud die wordt geretourneerd in HTTP-antwoorden. Voor voorbeelden die gebruikmaken van Json.NET en ReadAsAsync<T>, gebruik de versiekiezer om een 2.x-versie van dit onderwerp te selecteren.

Consumptiepatronen

Er zijn verschillende manieren IHttpClientFactory om in een app te gebruiken:

De beste aanpak is afhankelijk van de vereisten van de app.

Basaal gebruik

IHttpClientFactory kan worden geregistreerd door te bellen 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.

Een IHttpClientFactory kan worden aangevraagd met behulp van afhankelijkheidsinjectie (DI). De volgende code gebruikt IHttpClientFactory om een HttpClient exemplaar te maken:

public class BasicUsageModel : PageModel
{
    private readonly IHttpClientFactory _clientFactory;

    public IEnumerable<GitHubBranch> Branches { get; private set; }

    public bool GetBranchesError { get; private set; }

    public BasicUsageModel(IHttpClientFactory clientFactory)
    {
        _clientFactory = clientFactory;
    }

    public async Task OnGet()
    {
        var request = new HttpRequestMessage(HttpMethod.Get,
            "https://api.github.com/repos/dotnet/AspNetCore.Docs/branches");
        request.Headers.Add("Accept", "application/vnd.github.v3+json");
        request.Headers.Add("User-Agent", "HttpClientFactory-Sample");

        var client = _clientFactory.CreateClient();

        var response = await client.SendAsync(request);

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

Het gebruik van IHttpClientFactory zoals in het voorgaande voorbeeld is een goede manier om een bestaande app te herstructureren. Het heeft geen invloed op hoe HttpClient wordt gebruikt. Op plaatsen waar HttpClient exemplaren worden gemaakt in een bestaande app, vervangt u deze exemplaren door aanroepen naar CreateClient.

Benoemde clients

Benoemde clients zijn een goede keuze wanneer:

  • Voor de app zijn veel verschillende toepassingen van HttpClientvereist.
  • Veel HttpClients hebben een andere configuratie.

Configuratie voor een genummerde HttpClient kan tijdens de registratie in Startup.ConfigureServices worden opgegeven.

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

In de voorgaande code is de client geconfigureerd met:

  • Het basisadres https://api.github.com/.
  • Er zijn twee headers vereist om te werken met de GitHub-API.

CreateClient

Elke keer CreateClient wordt het volgende aangeroepen:

  • Er wordt een nieuw exemplaar gemaakt HttpClient .
  • De configuratieactie wordt aangeroepen.

Als u een benoemde client wilt maken, geeft u de naam door in CreateClient:

public class NamedClientModel : PageModel
{
    private readonly IHttpClientFactory _clientFactory;

    public IEnumerable<GitHubPullRequest> PullRequests { get; private set; }

    public bool GetPullRequestsError { get; private set; }

    public bool HasPullRequests => PullRequests.Any();

    public NamedClientModel(IHttpClientFactory clientFactory)
    {
        _clientFactory = clientFactory;
    }

    public async Task OnGet()
    {
        var request = new HttpRequestMessage(HttpMethod.Get,
            "repos/dotnet/AspNetCore.Docs/pulls");

        var client = _clientFactory.CreateClient("github");

        var response = await client.SendAsync(request);

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

In de voorgaande code hoeft de aanvraag geen hostnaam op te geven. De code kan alleen het pad doorgeven, omdat het basisadres dat is geconfigureerd voor de client wordt gebruikt.

Getypte clients

Getypte clients:

  • Bied dezelfde mogelijkheden als benoemde clients zonder dat u tekenreeksen als sleutels hoeft te gebruiken.
  • Biedt IntelliSense en compilerhulp voor het gebruik van cliënten.
  • Geef één locatie op om een bepaalde HttpClientlocatie te configureren en ermee te communiceren. Er kan bijvoorbeeld één getypeerde client worden gebruikt:
    • Voor één back-endeindpunt.
    • Als u alle logica voor het eindpunt wilt inkapselen.
  • Werk met DI en kan waar nodig worden geïnjecteerd in de app.

Een getypte client accepteert een HttpClient parameter in de 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/dotnet/AspNetCore.Docs/issues?state=open&sort=created&direction=desc");

        response.EnsureSuccessStatusCode();

        using var responseStream = await response.Content.ReadAsStreamAsync();
        return await JsonSerializer.DeserializeAsync
            <IEnumerable<GitHubIssue>>(responseStream);
    }
}

Als u codeopmerkingen wilt zien die zijn vertaald naar andere talen dan Engels, laat het ons dan weten in dit GitHub-discussieprobleem.

In de voorgaande code:

  • De configuratie wordt verplaatst naar de getypte client.
  • Het HttpClient object wordt weergegeven als een openbare eigenschap.

API-specifieke methoden kunnen worden gemaakt die functionaliteit beschikbaar HttpClient maken. Met de GetAspNetDocsIssues methode wordt bijvoorbeeld code ingekapseld om openstaande problemen op te halen.

De volgende code roept AddHttpClient aan binnen Startup.ConfigureServices om een getypte clientklasse te registreren.

services.AddHttpClient<GitHubService>();

De getypte client wordt geregistreerd als tijdelijk bij DI. In de voorgaande code AddHttpClient wordt geregistreerd GitHubService als een tijdelijke service. Deze registratie maakt gebruik van een factory-methode voor het volgende:

  1. Maak een exemplaar van HttpClient.
  2. Maak een exemplaar van GitHubService, door te geven in het exemplaar van HttpClient de constructor.

De getypte client kan worden geïnjecteerd en direct gebruikt.

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

De configuratie voor een getypte client kan worden opgegeven tijdens de registratie in Startup.ConfigureServices, in plaats van in de constructor van de getypte client:

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

De HttpClient kan worden ingekapseld in een getypte client. In plaats van deze als eigenschap weer te geven, definieert u een methode waarmee het HttpClient exemplaar intern wordt aangeroepen:

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

In de voorgaande code wordt de HttpClient code opgeslagen in een privéveld. Toegang tot de HttpClient is via de openbare GetRepos methode.

Gegenereerde clients

IHttpClientFactory kan worden gebruikt in combinatie met bibliotheken van derden, zoals Refit. Refit is een REST bibliotheek voor .NET. Het converteert REST API's naar live-interfaces. Er wordt dynamisch een implementatie van de interface gegenereerd door de RestService, waarbij HttpClient de externe HTTP-aanroepen worden uitgevoerd.

Een interface en een antwoord worden gedefinieerd om de externe API en het bijbehorende antwoord weer te geven:

public interface IHelloClient
{
    [Get("/helloworld")]
    Task<Reply> GetMessageAsync();
}

public class Reply
{
    public string Message { get; set; }
}

Een getypte client kan worden toegevoegd met Behulp van Refit om de implementatie te genereren:

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

De gedefinieerde interface kan waar nodig worden gebruikt, waarbij DI en Refit de implementatie leveren.

[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();
    }
}

POST-, PUT- en DELETE-aanvragen maken

In de voorgaande voorbeelden gebruiken alle HTTP-aanvragen het GET HTTP-werkwoord. HttpClient ondersteunt ook andere HTTP-woorden, waaronder:

  • POST
  • PUT
  • DELETE
  • PATCH

Zie voor een volledige lijst met ondersteunde HTTP-woorden HttpMethod.

In het volgende voorbeeld ziet u hoe u een HTTP POST-aanvraag maakt:

public async Task CreateItemAsync(TodoItem todoItem)
{
    var todoItemJson = new StringContent(
        JsonSerializer.Serialize(todoItem, _jsonSerializerOptions),
        Encoding.UTF8,
        "application/json");

    using var httpResponse =
        await _httpClient.PostAsync("/api/TodoItems", todoItemJson);

    httpResponse.EnsureSuccessStatusCode();
}

In de voorgaande code is de CreateItemAsync methode:

  • Serialiseert de TodoItem parameter naar JSON met behulp van System.Text.Json. Dit maakt gebruik van een exemplaar van JsonSerializerOptions het serialisatieproces te configureren.
  • Hiermee maakt u een exemplaar van StringContent het pakket van de geserialiseerde JSON voor verzending in de hoofdtekst van de HTTP-aanvraag.
  • Aanroepen PostAsync om de JSON-inhoud naar de opgegeven URL te verzenden. Dit is een relatieve URL die wordt toegevoegd aan het HttpClient.BaseAddress.
  • Aanroepen EnsureSuccessStatusCode om een uitzondering te genereren als de antwoordstatuscode niet aangeeft dat deze is geslaagd.

HttpClient ondersteunt ook andere typen inhoud. Bijvoorbeeld MultipartContent en StreamContent. Zie voor een volledige lijst met ondersteunde inhoud HttpContent.

In het volgende voorbeeld ziet u een HTTP PUT-aanvraag:

public async Task SaveItemAsync(TodoItem todoItem)
{
    var todoItemJson = new StringContent(
        JsonSerializer.Serialize(todoItem),
        Encoding.UTF8,
        "application/json");

    using var httpResponse =
        await _httpClient.PutAsync($"/api/TodoItems/{todoItem.Id}", todoItemJson);

    httpResponse.EnsureSuccessStatusCode();
}

De voorgaande code is vergelijkbaar met het POST-voorbeeld. De SaveItemAsync methode roept PutAsync in plaats van PostAsync.

In het volgende voorbeeld ziet u een HTTP DELETE-aanvraag:

public async Task DeleteItemAsync(long itemId)
{
    using var httpResponse =
        await _httpClient.DeleteAsync($"/api/TodoItems/{itemId}");

    httpResponse.EnsureSuccessStatusCode();
}

In de voorgaande code roept DeleteItemAsyncde methode aanDeleteAsync. Omdat HTTP DELETE-aanvragen doorgaans geen hoofdtekst bevatten, biedt de DeleteAsync methode geen overbelasting die een exemplaar accepteert.HttpContent

Zie HttpClientvoor meer informatie over het gebruik van verschillende HTTP-woorden met HttpClient.

Middleware voor uitgaande aanvragen

HttpClient heeft het concept van het delegeren van handlers die kunnen worden gekoppeld voor uitgaande HTTP-aanvragen. IHttpClientFactory:

  • Vereenvoudigt het definiëren van de handlers die voor elke benoemde client moeten worden toegepast.
  • Ondersteunt registratie en ketening van meerdere handlers voor het bouwen van een middleware-pijplijn voor uitgaande aanvragen. Elk van deze handlers kan werk uitvoeren voor en na de uitgaande aanvraag. Dit patroon:
    • Is vergelijkbaar met de binnenkomende middleware-pijplijn in ASP.NET Core.
    • Biedt een mechanisme voor het beheren van kruislingse problemen rond HTTP-aanvragen, zoals:
      • caching
      • error handling
      • serialization
      • logging

Een delegerende handler maken:

  • Afgeleid van DelegatingHandler.
  • Overschrijf SendAsync. Voer code uit voordat u de aanvraag doorgeeft aan de volgende handler in de pijplijn:
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);
    }
}

Met de voorgaande code wordt gecontroleerd of de X-API-KEY header zich in de aanvraag bevindt. Als X-API-KEY ontbreekt, wordt BadRequest geretourneerd.

Meer dan één handler kan worden toegevoegd aan de configuratie voor een HttpClient met 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.

In de voorgaande code wordt de ValidateHeaderHandler code geregistreerd bij DI. Zodra geregistreerd, kan AddHttpMessageHandler worden aangeroepen, waarbij het type voor de handler wordt doorgegeven.

Meerdere handlers kunnen worden geregistreerd in de volgorde waarin ze moeten worden uitgevoerd. Elke handler verpakt de volgende handler totdat de laatste HttpClientHandler de aanvraag uitvoert:

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

DI gebruiken in middleware voor uitgaande aanvragen

Wanneer IHttpClientFactory er een nieuwe delegeringshandler wordt gemaakt, wordt DI gebruikt om te voldoen aan de constructorparameters van de handler. IHttpClientFactory maakt een afzonderlijk DI-bereik voor elke handler, wat kan leiden tot verrassend gedrag wanneer een handler een scoped service verbruikt.

Denk bijvoorbeeld aan de volgende interface en de implementatie, die een taak vertegenwoordigt als een bewerking met een id: OperationId

public interface IOperationScoped 
{
    string OperationId { get; }
}

public class OperationScoped : IOperationScoped
{
    public string OperationId { get; } = Guid.NewGuid().ToString()[^4..];
}

Zoals de naam al aangeeft, wordt IOperationScoped geregistreerd bij DI met behulp van een scoped levensduur:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<TodoContext>(options =>
        options.UseInMemoryDatabase("TodoItems"));

    services.AddHttpContextAccessor();

    services.AddHttpClient<TodoClient>((sp, httpClient) =>
    {
        var httpRequest = sp.GetRequiredService<IHttpContextAccessor>().HttpContext.Request;

        // For sample purposes, assume TodoClient is used in the context of an incoming request.
        httpClient.BaseAddress = new Uri(UriHelper.BuildAbsolute(httpRequest.Scheme,
                                         httpRequest.Host, httpRequest.PathBase));
        httpClient.Timeout = TimeSpan.FromSeconds(5);
    });

    services.AddScoped<IOperationScoped, OperationScoped>();
    
    services.AddTransient<OperationHandler>();
    services.AddTransient<OperationResponseHandler>();

    services.AddHttpClient("Operation")
        .AddHttpMessageHandler<OperationHandler>()
        .AddHttpMessageHandler<OperationResponseHandler>()
        .SetHandlerLifetime(TimeSpan.FromSeconds(5));

    services.AddControllers();
    services.AddRazorPages();
}

De volgende delegerende handler verbruikt en gebruikt IOperationScoped om de X-OPERATION-ID header voor de uitgaande aanvraag in te stellen:

public class OperationHandler : DelegatingHandler
{
    private readonly IOperationScoped _operationService;

    public OperationHandler(IOperationScoped operationScoped)
    {
        _operationService = operationScoped;
    }

    protected async override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        request.Headers.Add("X-OPERATION-ID", _operationService.OperationId);

        return await base.SendAsync(request, cancellationToken);
    }
}

Navigeer in het HttpRequestsSample downloaden naar /Operation en vernieuw deze. De waarde van het aanvraagbereik wordt voor elke aanvraag gewijzigd, maar de scopewaarde van de handler wordt slechts elke 5 seconden gewijzigd.

Handlers kunnen gebruikmaken van services van enige omvang. Services waarop handlers afhankelijk zijn, worden verwijderd wanneer de handler wordt verwijderd.

Gebruik een van de volgende methoden om de status per aanvraag te delen met berichthandlers:

Op Polly gebaseerde handlers gebruiken

IHttpClientFactory kan worden geïntegreerd met de bibliotheek van derden Polly. Polly is een uitgebreide bibliotheek voor tolerantie en tijdelijke foutafhandeling voor .NET. Hiermee kunnen ontwikkelaars beleid uitdrukken, zoals Retry, Circuit Breaker, Timeout, Bulkhead Isolation en Fallback op een vloeiende en thread-veilige manier.

Extensiemethoden worden geboden om het gebruik van Polly-beleidsregels met geconfigureerde HttpClient exemplaren in te schakelen. De Polly-extensies ondersteunen het toevoegen van op Polly gebaseerde handlers aan clients. Polly vereist het Microsoft.Extensions.Http.Polly NuGet-pakket.

Tijdelijke fouten afhandelen

Fouten treden meestal op wanneer externe HTTP-aanroepen tijdelijk zijn. AddTransientHttpErrorPolicy hiermee kan een beleid worden gedefinieerd voor het afhandelen van tijdelijke fouten. Beleidsregels die zijn geconfigureerd met AddTransientHttpErrorPolicy de volgende antwoorden verwerken:

AddTransientHttpErrorPolicy biedt toegang tot een PolicyBuilder object dat is geconfigureerd voor het afhandelen van fouten die een mogelijke tijdelijke fout vertegenwoordigen:

public void ConfigureServices(IServiceCollection services)
{           
    services.AddHttpClient<UnreliableEndpointCallerService>()
        .AddTransientHttpErrorPolicy(p => 
            p.WaitAndRetryAsync(3, _ => TimeSpan.FromMilliseconds(600)));

    // Remaining code deleted for brevity.

In de voorgaande code wordt een WaitAndRetryAsync beleid gedefinieerd. Mislukte aanvragen worden maximaal drie keer opnieuw geprobeerd met een vertraging van 600 ms tussen pogingen.

Dynamisch beleid selecteren

Extensiemethoden worden geleverd om bijvoorbeeld AddPolicyHandlerop Polly gebaseerde handlers toe te voegen. De volgende AddPolicyHandler overload inspecteert het verzoek om te bepalen welk beleid moet worden toegepast.

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

Als de uitgaande aanvraag een HTTP GET is, wordt in de voorgaande code een time-out van 10 seconden toegepast. Voor elke andere HTTP-methode wordt een time-out van 30 seconden gebruikt.

Meerdere Polly-handlers toevoegen

Het is gebruikelijk om Polly-beleid te nesten:

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

In het voorgaande voorbeeld:

  • Er worden twee handlers toegevoegd.
  • De eerste handler gebruikt AddTransientHttpErrorPolicy om een beleid voor opnieuw proberen toe te voegen. Mislukte aanvragen worden maximaal drie keer opnieuw geprobeerd.
  • Met de tweede AddTransientHttpErrorPolicy aanroep wordt een beleid voor circuitonderbrekers toegevoegd. Verdere externe aanvragen worden 30 seconden geblokkeerd als 5 mislukte pogingen opeenvolgend plaatsvinden. Circuitonderbreker beleid is toestandsafhankelijk. Alle aanroepen via deze client delen dezelfde circuitstatus.

Beleid toevoegen uit het Polly-register

Een benadering voor het beheren van regelmatig gebruikte beleidsregels is om ze eenmaal te definiëren en te registreren bij een PolicyRegistry.

In de volgende code:

  • De beleidsregels 'normaal' en 'lang' worden toegevoegd.
  • AddPolicyHandlerFromRegistry voegt de beleidsregels 'regular' en 'long' toe uit het register.
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.

Zie de IHttpClientFactory voor meer informatie over en Polly-integraties.

HttpClient en levensduurbeheer

Er wordt telkens een nieuw HttpClient exemplaar geretourneerd wanneer CreateClient deze IHttpClientFactorywordt aangeroepen. Een HttpMessageHandler wordt gecreëerd per benoemde cliënt. De fabriek beheert de levensduur van de HttpMessageHandler exemplaren.

IHttpClientFactory voegt de instanties van HttpMessageHandler die door de fabriek zijn gecreëerd samen om het gebruik van middelen te verminderen. Een HttpMessageHandler exemplaar kan opnieuw worden gebruikt vanuit de pool bij het maken van een nieuw HttpClient exemplaar als de levensduur ervan niet is verlopen.

Pooling van handlers is wenselijk omdat elke handler doorgaans zijn eigen onderliggende HTTP-verbindingen beheert. Het maken van meer handlers dan nodig is, kan leiden tot verbindingsvertragingen. Sommige handlers houden verbindingen ook voor onbepaalde tijd open, waardoor de handler niet kan reageren op DNS-wijzigingen (Domain Name System).

De standaardlevensduur van de handler is twee minuten. De standaardwaarde kan per benoemde client worden overschreven:

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

    // Remaining code deleted for brevity.

HttpClient exemplaren kunnen over het algemeen worden behandeld als .NET-objecten die geen verwijdering vereisen. Opruimen annuleert uitgaande aanvragen en garandeert dat de gegeven HttpClient instantie na gebruik van Dispose niet meer kan worden gebruikt. IHttpClientFactory houdt de resources bij en verwijdert deze die door exemplaren van HttpClient worden gebruikt.

Het levend houden van één HttpClient exemplaar voor een lange duur is een veelgebruikte methode voordat de opkomst van IHttpClientFactory. Dit patroon wordt overbodig na de migratie naar IHttpClientFactory.

Alternatieven voor IHttpClientFactory

Door gebruik te maken van IHttpClientFactory in een DI-ingeschakelde applicatie worden de volgende zaken vermeden:

  • Problemen met uitputting van middelen door het groeperen van HttpMessageHandler instanties.
  • Verouderde DNS-problemen oplossen door HttpMessageHandler instanties regelmatig te rouleren.

Er zijn alternatieve manieren om de voorgaande problemen op te lossen met behulp van een SocketsHttpHandler-exemplaar met een lange levensduur.

  • Maak een exemplaar van SocketsHttpHandler wanneer de app wordt gestart en gebruik deze voor de levensduur van de app.
  • Configureer PooledConnectionLifetime naar een geschikte waarde op basis van DNS-vernieuwingstijden.
  • Maak HttpClient-instanties zo nodig met new HttpClient(handler, disposeHandler: false).

Met de voorgaande benaderingen worden de problemen met resourcebeheer op een vergelijkbare manier opgelost als IHttpClientFactory.

  • De SocketsHttpHandler deelt verbindingen tussen HttpClient exemplaren. Dit delen voorkomt uitputting van sockets.
  • De SocketsHttpHandler cyclet verbindingen volgens PooledConnectionLifetime om verouderde DNS-problemen te voorkomen.

Cookies

De gegroepeerde HttpMessageHandler exemplaren resulteren in CookieContainer gedeelde objecten. CookieContainer Onverwacht object delen leidt vaak tot onjuiste code. Voor apps waarvoor cookies zijn vereist, kunt u het volgende overwegen:

  • cookie Automatische verwerking uitschakelen
  • Het vermijden van IHttpClientFactory

Aanroep ConfigurePrimaryHttpMessageHandler om automatische cookie verwerking uit te schakelen:

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

Logging

Clients die zijn gemaakt via IHttpClientFactory leggen logboekberichten vast voor alle verzoeken. Schakel het juiste informatieniveau in de logboekconfiguratie in om de standaardlogboekberichten te zien. Aanvullende logboeken, zoals de logboeken van aanvraagheaders, worden alleen opgenomen op trace-niveau.

De logboekcategorie die voor elke client wordt gebruikt, bevat de naam van de client. Een client met de naam MyNamedClient registreert bijvoorbeeld berichten met een categorie System.Net.Http.HttpClient. MyNamedClient. LogicalHandler". Berichten met het achtervoegsel LogicalHandler komen voor buiten de pijplijn van de aanvraaghandler. Op de aanvraag worden berichten geregistreerd voordat andere handlers in de pijplijn deze hebben verwerkt. Bij het antwoord worden berichten geregistreerd nadat andere pipeline-handlers het antwoord hebben ontvangen.

Logboekregistratie vindt ook plaats in de pijplijn van de aanvraaghandler. In het voorbeeld van MyNamedClient worden deze berichten vastgelegd met de logboekcategorie System.Net.Http.HttpClient. MyNamedClient. ClientHandler". Voor de aanvraag gebeurt dit nadat alle andere handlers zijn uitgevoerd en direct voordat de aanvraag wordt verzonden. In het antwoord bevat deze logboekregistratie de status van het antwoord voordat deze wordt doorgestuurd via de handler-pijplijn.

Door logboekregistratie buiten en binnen de pijplijn in te schakelen, kunnen de wijzigingen die door de andere pijplijnhandlers zijn aangebracht, worden gecontroleerd. Dit kunnen wijzigingen in aanvraagheaders of de antwoordstatuscode zijn.

Als u de naam van de client in de logboekcategorie opvoegt, kunt u logboekfiltering voor specifieke benoemde clients inschakelen.

HttpMessageHandler configureren

Het kan nodig zijn om de configuratie van de binnenste HttpMessageHandler te beheren die door een client wordt gebruikt.

Er IHttpClientBuilder wordt een geretourneerd bij het toevoegen van benoemde of getypte clients. De ConfigurePrimaryHttpMessageHandler extensiemethode kan worden gebruikt om een gemachtigde te definiëren. De gemachtigde wordt gebruikt voor het maken en configureren van de primaire die HttpMessageHandler door die client wordt gebruikt:

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

    // Remaining code deleted for brevity.

IHttpClientFactory gebruiken in een console-app

Voeg in een console-app de volgende pakketverwijzingen toe aan het project:

In het volgende voorbeeld:

  • IHttpClientFactory is geregistreerd in de servicecontainer van de Generic Host.
  • MyService maakt een client factory-exemplaar van de service, die wordt gebruikt voor het maken van een HttpClient. HttpClient wordt gebruikt om een webpagina op te halen.
  • Main creëert een scope om de GetPage methode van de service uit te voeren en om de eerste 500 tekens van de inhoud van de webpagina naar de console te schrijven.
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
class Program
{
    static async Task<int> Main(string[] args)
    {
        var builder = new HostBuilder()
            .ConfigureServices((hostContext, services) =>
            {
                services.AddHttpClient();
                services.AddTransient<IMyService, MyService>();
            }).UseConsoleLifetime();

        var host = builder.Build();

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

            Console.WriteLine(pageContent.Substring(0, 500));
        }
        catch (Exception ex)
        {
            var logger = host.Services.GetRequiredService<ILogger<Program>>();

            logger.LogError(ex, "An error occurred.");
        }

        return 0;
    }

    public interface IMyService
    {
        Task<string> GetPage();
    }

    public class MyService : IMyService
    {
        private readonly IHttpClientFactory _clientFactory;

        public MyService(IHttpClientFactory clientFactory)
        {
            _clientFactory = clientFactory;
        }

        public async Task<string> GetPage()
        {
            // Content from BBC One: Dr. Who website (©BBC)
            var request = new HttpRequestMessage(HttpMethod.Get,
                "https://www.bbc.co.uk/programmes/b006q2x0");
            var client = _clientFactory.CreateClient();
            var response = await client.SendAsync(request);

            if (response.IsSuccessStatusCode)
            {
                return await response.Content.ReadAsStringAsync();
            }
            else
            {
                return $"StatusCode: {response.StatusCode}";
            }
        }
    }
}

Middleware voor headerdoorgifte

Headerdoorgifte is een ASP.NET Core-middleware om HTTP-headers van de binnenkomende aanvraag door te geven aan de uitgaande HTTP-clientaanvragen. Koptekst propagatie gebruiken:

  • Verwijs naar het Microsoft.AspNetCore.HeaderPropagation-pakket .

  • Configureer de middleware en 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();
        });
    }
    
  • De client bevat de geconfigureerde headers voor uitgaande aanvragen:

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

Aanvullende bronnen

Door Glenn Condron, Ryan Nowak en Steve Gordon

Een IHttpClientFactory kan worden geregistreerd en gebruikt voor het configureren en maken van HttpClient exemplaren in een app. Het biedt de volgende voordelen:

  • Biedt een centrale locatie voor het benoemen en configureren van logische HttpClient instanties. Een github-client kan bijvoorbeeld worden geregistreerd en geconfigureerd voor toegang tot GitHub. Een standaardclient kan worden geregistreerd voor andere doeleinden.
  • Codifeert het concept van uitgaande middleware via het delegeren van handlers in HttpClient en biedt uitbreidingen voor op Polly gebaseerde middleware om hiervan te profiteren.
  • Beheert de pooling en levensduur van onderliggende HttpClientMessageHandler exemplaren om veelvoorkomende DNS-problemen te voorkomen die optreden bij het handmatig beheren van de levensduur van HttpClient.
  • Voegt een configureerbare logboekregistratie-ervaring (via ILogger) toe voor alle aanvragen die worden verzonden via clients die door de fabriek zijn gemaakt.

Voorbeeldcode bekijken of downloaden (hoe download je)

Prerequisites

Voor projecten die zijn gericht op .NET Framework, moet het Microsoft.Extensions.Http NuGet-pakket worden geïnstalleerd. Projecten die gericht zijn op .NET Core en verwijzen naar de Microsoft.AspNetCore.App metapackage bevatten al het Microsoft.Extensions.Http pakket.

Consumptiepatronen

Er zijn verschillende manieren IHttpClientFactory om in een app te gebruiken:

Geen van hen zijn strikt boven elkaar. De beste aanpak is afhankelijk van de beperkingen van de app.

Basaal gebruik

De IHttpClientFactory kan worden geregistreerd door de AddHttpClient extensiemethode aan te roepen op de IServiceCollection, binnen de Startup.ConfigureServices methode.

services.AddHttpClient();

Zodra geregistreerd, kan code een IHttpClientFactory overal accepteren waar services kunnen worden geïnjecteerd met behulp van afhankelijkheidsinjectie (DI). Deze IHttpClientFactory kan worden gebruikt om een HttpClient exemplaar te maken:

public class BasicUsageModel : PageModel
{
    private readonly IHttpClientFactory _clientFactory;

    public IEnumerable<GitHubBranch> Branches { get; private set; }

    public bool GetBranchesError { get; private set; }

    public BasicUsageModel(IHttpClientFactory clientFactory)
    {
        _clientFactory = clientFactory;
    }

    public async Task OnGet()
    {
        var request = new HttpRequestMessage(HttpMethod.Get, 
            "https://api.github.com/repos/dotnet/AspNetCore.Docs/branches");
        request.Headers.Add("Accept", "application/vnd.github.v3+json");
        request.Headers.Add("User-Agent", "HttpClientFactory-Sample");

        var client = _clientFactory.CreateClient();

        var response = await client.SendAsync(request);

        if (response.IsSuccessStatusCode)
        {
            Branches = await response.Content
                .ReadAsAsync<IEnumerable<GitHubBranch>>();
        }
        else
        {
            GetBranchesError = true;
            Branches = Array.Empty<GitHubBranch>();
        }                               
    }
}

Het gebruik IHttpClientFactory op deze manier is een goede manier om een bestaande app te herstructureren. Het heeft geen invloed op de manier die HttpClient wordt gebruikt. Op plaatsen waar momenteel HttpClient-instanties worden gemaakt, vervang deze door een aanroep naar CreateClient.

Benoemde clients

Als een app veel verschillende toepassingen vereist, waarbij HttpClient ieder een andere configuratie heeft, kunt u de optie gebruiken om benoemde clients in te zetten. De configuratie voor een benoemde HttpClient kan worden opgegeven tijdens de registratie 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");
});

In de voorgaande code AddHttpClient wordt de naam github opgegeven. Voor deze client is een aantal standaardconfiguraties toegepast, namelijk het basisadres en twee headers die nodig zijn om te werken met de GitHub-API.

Telkens wanneer CreateClient wordt aangeroepen, wordt er een nieuw exemplaar van HttpClient gemaakt en wordt de configuratieactie aangeroepen.

Als u een benoemde client wilt gebruiken, kan een tekenreeksparameter worden doorgegeven aan CreateClient. Geef de naam op van de client die moet worden gemaakt:

public class NamedClientModel : PageModel
{
    private readonly IHttpClientFactory _clientFactory;

    public IEnumerable<GitHubPullRequest> PullRequests { get; private set; }

    public bool GetPullRequestsError { get; private set; }

    public bool HasPullRequests => PullRequests.Any();

    public NamedClientModel(IHttpClientFactory clientFactory)
    {
        _clientFactory = clientFactory;
    }

    public async Task OnGet()
    {
        var request = new HttpRequestMessage(HttpMethod.Get, 
            "repos/dotnet/AspNetCore.Docs/pulls");

        var client = _clientFactory.CreateClient("github");

        var response = await client.SendAsync(request);

        if (response.IsSuccessStatusCode)
        {
            PullRequests = await response.Content
                .ReadAsAsync<IEnumerable<GitHubPullRequest>>();
        }
        else
        {
            GetPullRequestsError = true;
            PullRequests = Array.Empty<GitHubPullRequest>();
        }
    }
}

In de voorgaande code hoeft de aanvraag geen hostnaam op te geven. Het kan alleen het pad doorgeven, omdat het basisadres dat is geconfigureerd voor de client wordt gebruikt.

Getypte clients

Getypte clients:

  • Bied dezelfde mogelijkheden als benoemde clients zonder dat u tekenreeksen als sleutels hoeft te gebruiken.
  • Biedt IntelliSense en compilerhulp voor het gebruik van cliënten.
  • Geef één locatie op om een bepaalde HttpClientlocatie te configureren en ermee te communiceren. Een getypte client kan bijvoorbeeld worden gebruikt voor een enkel backend-eindpunt en de volledige logica voor dat eindpunt inkapselen.
  • Gebruik DI en injecteer het waar nodig in uw app.

Een getypte client accepteert een HttpClient parameter in de 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/dotnet/AspNetCore.Docs/issues?state=open&sort=created&direction=desc");

        response.EnsureSuccessStatusCode();

        var result = await response.Content
            .ReadAsAsync<IEnumerable<GitHubIssue>>();

        return result;
    }
}

In de voorgaande code wordt de configuratie verplaatst naar de getypte client. Het HttpClient object wordt weergegeven als een openbare eigenschap. Het is mogelijk om API-specifieke methoden te definiëren die HttpClient functionaliteit blootleggen. De GetAspNetDocsIssues methode bevat de code die nodig is om de meest recente openstaande problemen op te vragen en te parseren vanuit een GitHub-opslagplaats.

Als u een getypte client wilt registreren, kan de algemene AddHttpClient extensiemethode worden gebruikt binnen Startup.ConfigureServices, waarbij u de getypte clientklasse opgeeft:

services.AddHttpClient<GitHubService>();

De getypte client wordt geregistreerd als tijdelijk bij DI. De getypte client kan worden geïnjecteerd en direct gebruikt.

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

Indien gewenst kan de configuratie voor een getypte client worden opgegeven tijdens de registratie in Startup.ConfigureServices, in plaats van in de constructor van de getypte client:

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

Het is mogelijk om HttpClient volledig in een getypte client in te kapselen. In plaats van deze als eigenschap beschikbaar te maken, kunnen openbare methoden worden opgegeven die het HttpClient exemplaar intern aanroepen.

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

In de voorgaande code wordt het HttpClient opgeslagen als een privéveld. Alle toegang voor het maken van externe aanroepen verloopt via de GetRepos methode.

Gegenereerde clients

IHttpClientFactory kan worden gebruikt in combinatie met andere bibliotheken van derden, zoals Refit. Refit is een REST bibliotheek voor .NET. Het converteert REST API's naar live-interfaces. Er wordt dynamisch een implementatie van de interface gegenereerd door de RestService, waarbij HttpClient de externe HTTP-aanroepen worden uitgevoerd.

Een interface en een antwoord worden gedefinieerd om de externe API en het bijbehorende antwoord weer te geven:

public interface IHelloClient
{
    [Get("/helloworld")]
    Task<Reply> GetMessageAsync();
}

public class Reply
{
    public string Message { get; set; }
}

Een getypte client kan worden toegevoegd met Behulp van Refit om de implementatie te genereren:

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

De gedefinieerde interface kan waar nodig worden gebruikt, waarbij DI en Refit de implementatie leveren.

[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 voor uitgaande aanvragen

HttpClient heeft al het concept van het delegeren van handlers die kunnen worden gekoppeld voor uitgaande HTTP-aanvragen. Hiermee IHttpClientFactory kunt u eenvoudig de handlers definiëren die voor elke benoemde client moeten worden toegepast. Het biedt ondersteuning voor registratie en ketening van meerdere handlers om een middleware-pijplijn voor uitgaande aanvragen te bouwen. Elk van deze handlers kan werk uitvoeren voor en na de uitgaande aanvraag. Dit patroon is vergelijkbaar met de binnenkomende middleware-pijplijn in ASP.NET Core. Het patroon biedt een mechanisme voor het beheren van kruislingse problemen rond HTTP-aanvragen, waaronder caching, foutafhandeling, serialisatie en logboekregistratie.

Als u een handler wilt maken, definieert u een klasse die is afgeleid van DelegatingHandler. Overschrijf de methode voor het SendAsync uitvoeren van code voordat u de aanvraag doorgeeft aan de volgende handler in de pijplijn:

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

De voorgaande code definieert een basishandler. Er wordt gecontroleerd of er een X-API-KEY header is opgenomen in de aanvraag. Als de header ontbreekt, kan deze de HTTP-aanroep voorkomen en een geschikt antwoord retourneren.

Tijdens de registratie kunnen een of meer handlers worden toegevoegd aan de configuratie voor een HttpClient. Deze taak wordt uitgevoerd via extensiemethoden op de 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>();

In de voorgaande code wordt de ValidateHeaderHandler code geregistreerd bij DI. De handler moet in DI worden geregistreerd als een tijdelijke service, nooit als een gescopeerde service. Als de handler is geregistreerd als een scoped service en alle services waarop de handler afhankelijk is, wegwerpbaar zijn:

  • De services van de handler kunnen worden verwijderd voordat de handler buiten het bereik valt.
  • De weggegooide handlerservices zorgen ervoor dat de handler mislukt.

Zodra het is geregistreerd, AddHttpMessageHandler kan het worden aangeroepen, waarbij het handlertype wordt doorgegeven.

Meerdere handlers kunnen worden geregistreerd in de volgorde waarin ze moeten worden uitgevoerd. Elke handler verpakt de volgende handler totdat de laatste HttpClientHandler de aanvraag uitvoert:

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

Gebruik een van de volgende methoden om de status per aanvraag te delen met berichthandlers:

  • Geef gegevens door aan de handler met behulp van HttpRequestMessage.Properties.
  • Gebruiken IHttpContextAccessor om toegang te krijgen tot de huidige aanvraag.
  • Maak een aangepast AsyncLocal opslagobject om de gegevens door te geven.

Op Polly gebaseerde handlers gebruiken

IHttpClientFactory kan worden geïntegreerd met een populaire bibliotheek van derden met de naam Polly. Polly is een uitgebreide bibliotheek voor tolerantie en tijdelijke foutafhandeling voor .NET. Hiermee kunnen ontwikkelaars beleid uitdrukken, zoals Retry, Circuit Breaker, Timeout, Bulkhead Isolation en Fallback op een vloeiende en thread-veilige manier.

Extensiemethoden worden geboden om het gebruik van Polly-beleidsregels met geconfigureerde HttpClient exemplaren in te schakelen. De Polly-extensies:

  • Ondersteuning voor het toevoegen van Polly-gebaseerde handlers aan clients.
  • Kan worden gebruikt na het installeren van het Microsoft.Extensions.Http.Polly NuGet-pakket. Het pakket is niet opgenomen in het gedeelde framework ASP.NET Core.

Tijdelijke fouten afhandelen

De meest voorkomende fouten treden op wanneer externe HTTP-aanroepen tijdelijk zijn. Er is een handige extensiemethode AddTransientHttpErrorPolicy opgenomen waarmee een beleid kan worden gedefinieerd voor het afhandelen van tijdelijke fouten. Beleidsregels die zijn geconfigureerd met deze extensiemethode verwerken HttpRequestException, HTTP 5xx-antwoorden en HTTP 408-antwoorden.

De AddTransientHttpErrorPolicy extensie kan worden gebruikt binnen Startup.ConfigureServices. De extensie biedt toegang tot een PolicyBuilder object dat is geconfigureerd voor het afhandelen van fouten die een mogelijke tijdelijke fout vertegenwoordigen:

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

In de voorgaande code wordt een WaitAndRetryAsync beleid gedefinieerd. Mislukte aanvragen worden maximaal drie keer opnieuw geprobeerd met een vertraging van 600 ms tussen pogingen.

Dynamisch beleid selecteren

Er bestaan aanvullende uitbreidingsmethoden die kunnen worden gebruikt om handlers op basis van Polly toe te voegen. Een dergelijke extensie is AddPolicyHandler, die meerdere overbelastingen heeft. Met één overload functie kan het verzoek worden geïnspecteerd om te bepalen welk beleid moet worden toegepast.

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

Als de uitgaande aanvraag een HTTP GET is, wordt in de voorgaande code een time-out van 10 seconden toegepast. Voor elke andere HTTP-methode wordt een time-out van 30 seconden gebruikt.

Meerdere Polly-handlers toevoegen

Het is gebruikelijk om Polly-beleid te nesten om verbeterde functionaliteit te bieden:

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

In het voorgaande voorbeeld worden twee handlers toegevoegd. De eerste gebruikt de AddTransientHttpErrorPolicy extensie om een beleid voor opnieuw proberen toe te voegen. Mislukte aanvragen worden maximaal drie keer opnieuw geprobeerd. De tweede aanroep voegt een circuitonderbrekerbeleid toe aan AddTransientHttpErrorPolicy. Verdere externe aanvragen worden 30 seconden geblokkeerd als vijf mislukte pogingen opeenvolgend plaatsvinden. Circuitonderbreker beleid is toestandsafhankelijk. Alle aanroepen via deze client delen dezelfde circuitstatus.

Beleid toevoegen uit het Polly-register

Een benadering voor het beheren van regelmatig gebruikte beleidsregels is om ze eenmaal te definiëren en te registreren bij een PolicyRegistry. Er wordt een extensiemethode opgegeven waarmee een handler kan worden toegevoegd met behulp van een beleid uit het register:

var registry = services.AddPolicyRegistry();

registry.Add("regular", timeout);
registry.Add("long", longTimeout);

services.AddHttpClient("regulartimeouthandler")
    .AddPolicyHandlerFromRegistry("regular");

In de voorgaande code worden twee beleidsregels geregistreerd wanneer de PolicyRegistry wordt toegevoegd aan de ServiceCollection. Als u een beleid uit het register wilt gebruiken, wordt de AddPolicyHandlerFromRegistry methode gebruikt, waarbij de naam van het beleid wordt doorgegeven die moet worden toegepast.

Meer informatie over IHttpClientFactory en Polly-integraties vindt u op de Polly-wiki.

HttpClient en levensduurbeheer

Er wordt telkens een nieuw HttpClient exemplaar geretourneerd wanneer CreateClient deze IHttpClientFactorywordt aangeroepen. Er is een HttpMessageHandler per genoemde klant. De fabriek beheert de levensduur van de HttpMessageHandler exemplaren.

IHttpClientFactory voegt de instanties van HttpMessageHandler die door de fabriek zijn gecreëerd samen om het gebruik van middelen te verminderen. Een HttpMessageHandler exemplaar kan opnieuw worden gebruikt vanuit de pool bij het maken van een nieuw HttpClient exemplaar als de levensduur ervan niet is verlopen.

Pooling van handlers is wenselijk omdat elke handler doorgaans zijn eigen onderliggende HTTP-verbindingen beheert. Het maken van meer handlers dan nodig is, kan leiden tot verbindingsvertragingen. Sommige handlers houden verbindingen ook voor onbepaalde tijd open, waardoor de handler niet kan reageren op DNS-wijzigingen.

De standaardlevensduur van de handler is twee minuten. De standaardwaarde kan per benoemde client worden overschreven. Als u deze wilt overschrijven, roept SetHandlerLifetime u de IHttpClientBuilder geretourneerde aan bij het maken van de client:

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

Verwijdering van de klant is niet vereist. Opruimen annuleert uitgaande aanvragen en garandeert dat de gegeven HttpClient instantie na gebruik van Dispose niet meer kan worden gebruikt. IHttpClientFactory houdt de resources bij en verwijdert deze die door exemplaren van HttpClient worden gebruikt. De HttpClient exemplaren kunnen over het algemeen worden behandeld als .NET-objecten die geen verwijdering vereisen.

Het levend houden van één HttpClient exemplaar voor een lange duur is een veelgebruikte methode voordat de opkomst van IHttpClientFactory. Dit patroon wordt overbodig na de migratie naar IHttpClientFactory.

Alternatieven voor IHttpClientFactory

Door gebruik te maken van IHttpClientFactory in een DI-ingeschakelde applicatie worden de volgende zaken vermeden:

  • Problemen met uitputting van middelen door het groeperen van HttpMessageHandler instanties.
  • Verouderde DNS-problemen oplossen door HttpMessageHandler instanties regelmatig te rouleren.

Er zijn alternatieve manieren om de voorgaande problemen op te lossen met behulp van een SocketsHttpHandler-exemplaar met een lange levensduur.

  • Maak een exemplaar van SocketsHttpHandler wanneer de app wordt gestart en gebruik deze voor de levensduur van de app.
  • Configureer PooledConnectionLifetime naar een geschikte waarde op basis van DNS-vernieuwingstijden.
  • Maak HttpClient-instanties zo nodig met new HttpClient(handler, disposeHandler: false).

Met de voorgaande benaderingen worden de problemen met resourcebeheer op een vergelijkbare manier opgelost als IHttpClientFactory.

  • De SocketsHttpHandler deelt verbindingen tussen HttpClient exemplaren. Dit delen voorkomt uitputting van sockets.
  • De SocketsHttpHandler cyclet verbindingen volgens PooledConnectionLifetime om verouderde DNS-problemen te voorkomen.

Cookies

De gegroepeerde HttpMessageHandler exemplaren resulteren in CookieContainer gedeelde objecten. CookieContainer Onverwacht object delen leidt vaak tot onjuiste code. Voor apps waarvoor cookies zijn vereist, kunt u het volgende overwegen:

  • cookie Automatische verwerking uitschakelen
  • Het vermijden van IHttpClientFactory

Aanroep ConfigurePrimaryHttpMessageHandler om automatische cookie verwerking uit te schakelen:

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

Logging

Clients die zijn gemaakt via IHttpClientFactory leggen logboekberichten vast voor alle verzoeken. Schakel het juiste informatieniveau in uw logboekconfiguratie in om de standaardlogboekberichten te zien. Aanvullende logboeken, zoals de logboeken van aanvraagheaders, worden alleen opgenomen op trace-niveau.

De logboekcategorie die voor elke client wordt gebruikt, bevat de naam van de client. Een client met de naam MyNamedClient registreert bijvoorbeeld berichten met een categorie van System.Net.Http.HttpClient.MyNamedClient.LogicalHandler. Berichten met het achtervoegsel LogicalHandler komen voor buiten de pijplijn van de aanvraaghandler. Op de aanvraag worden berichten geregistreerd voordat andere handlers in de pijplijn deze hebben verwerkt. Bij het antwoord worden berichten geregistreerd nadat andere pipeline-handlers het antwoord hebben ontvangen.

Logboekregistratie vindt ook plaats in de pijplijn van de aanvraaghandler. In het voorbeeld van MyNamedClient worden deze berichten vastgelegd in de logboekcategorie System.Net.Http.HttpClient.MyNamedClient.ClientHandler. Voor de aanvraag gebeurt dit nadat alle andere handlers zijn uitgevoerd en direct voordat de aanvraag op het netwerk wordt verzonden. In het antwoord bevat deze logboekregistratie de status van het antwoord voordat deze wordt doorgestuurd via de handler-pijplijn.

Door logboekregistratie buiten en binnen de pijplijn in te schakelen, kunnen de wijzigingen die door de andere pijplijnhandlers zijn aangebracht, worden gecontroleerd. Dit kunnen wijzigingen in aanvraagheaders zijn, bijvoorbeeld of de antwoordstatuscode.

Als u de naam van de client in de logboekcategorie opvoegt, kunt u waar nodig logboekfiltering voor specifieke benoemde clients inschakelen.

HttpMessageHandler configureren

Het kan nodig zijn om de configuratie van de binnenste HttpMessageHandler te beheren die door een client wordt gebruikt.

Er IHttpClientBuilder wordt een geretourneerd bij het toevoegen van benoemde of getypte clients. De ConfigurePrimaryHttpMessageHandler extensiemethode kan worden gebruikt om een gemachtigde te definiëren. De gemachtigde wordt gebruikt voor het maken en configureren van de primaire die HttpMessageHandler door die client wordt gebruikt:

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

IHttpClientFactory gebruiken in een console-app

Voeg in een console-app de volgende pakketverwijzingen toe aan het project:

In het volgende voorbeeld:

  • IHttpClientFactory is geregistreerd in de servicecontainer van de Generic Host.
  • MyService maakt een client factory-exemplaar van de service, die wordt gebruikt voor het maken van een HttpClient. HttpClient wordt gebruikt om een webpagina op te halen.
  • De methode van GetPage de service wordt uitgevoerd om de eerste 500 tekens van de webpagina-inhoud naar de console te schrijven. Zie Program.Main voor meer informatie over het aanroepen van services.
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
class Program
{
    static async Task<int> Main(string[] args)
    {
        var builder = new HostBuilder()
            .ConfigureServices((hostContext, services) =>
            {
                services.AddHttpClient();
                services.AddTransient<IMyService, MyService>();
            }).UseConsoleLifetime();

        var host = builder.Build();

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

            Console.WriteLine(pageContent.Substring(0, 500));
        }
        catch (Exception ex)
        {
            var logger = host.Services.GetRequiredService<ILogger<Program>>();

            logger.LogError(ex, "An error occurred.");
        }

        return 0;
    }

    public interface IMyService
    {
        Task<string> GetPage();
    }

    public class MyService : IMyService
    {
        private readonly IHttpClientFactory _clientFactory;

        public MyService(IHttpClientFactory clientFactory)
        {
            _clientFactory = clientFactory;
        }

        public async Task<string> GetPage()
        {
            // Content from BBC One: Dr. Who website (©BBC)
            var request = new HttpRequestMessage(HttpMethod.Get,
                "https://www.bbc.co.uk/programmes/b006q2x0");
            var client = _clientFactory.CreateClient();
            var response = await client.SendAsync(request);

            if (response.IsSuccessStatusCode)
            {
                return await response.Content.ReadAsStringAsync();
            }
            else
            {
                return $"StatusCode: {response.StatusCode}";
            }
        }
    }
}

Middleware voor headerdoorgifte

Headerdoorgifte is een door de community ondersteunde middleware om HTTP-headers van de binnenkomende aanvraag door te geven aan de uitgaande HTTP-clientaanvragen. Koptekst propagatie gebruiken:

  • Verwijs naar de door de community ondersteunde poort van het pakket HeaderPropagation. ASP.NET Core 3.1 of hoger ondersteunt Microsoft.AspNetCore.HeaderPropagation.

  • Configureer de middleware en 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();
    }
    
  • De client bevat de geconfigureerde headers voor uitgaande aanvragen:

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

Aanvullende bronnen