Dela via


Göra HTTP-begäranden med IHttpClientFactory i ASP.NET Core

Note

Det här är inte den senaste versionen av den här artikeln. För den nuvarande utgåvan, se .NET 9-versionen av den här artikeln .

Warning

Den här versionen av ASP.NET Core stöds inte längre. Mer information finns i supportpolicyn för .NET och .NET Core. För den nuvarande utgåvan, se .NET 9-versionen av den här artikeln .

Important

Den här informationen gäller en förhandsversionsprodukt som kan ändras avsevärt innan den släpps kommersiellt. Microsoft lämnar inga garantier, uttryckliga eller underförstådda, med avseende på den information som tillhandahålls här.

För den nuvarande utgåvan, se .NET 9-versionen av den här artikeln .

Av Kirk Larkin, Steve Gordon, Glenn Condron och Ryan Nowak.

En IHttpClientFactory kan registreras och användas för att konfigurera och skapa HttpClient instanser i en app. IHttpClientFactory erbjuder följande fördelar:

  • Tillhandahåller en central plats för att namnge och konfigurera logiska HttpClient instanser. En klient med namnet github kan till exempel registreras och konfigureras för åtkomst till GitHub. En standardklient kan registreras för allmän åtkomst.
  • Kodifierar begreppet utgående mellanprogram via delegering av hanterare i HttpClient. Tillhandahåller tillägg för Polly-baserade mellanprogram för att dra nytta av delegering av hanterare i HttpClient.
  • Hanterar pooleringen och livslängden för underliggande HttpClientMessageHandler instanser. Automatisk hantering undviker vanliga DNS-problem (Domain Name System) som uppstår när HttpClient livslängder hanteras manuellt.
  • Lägger till en konfigurerbar loggningsupplevelse (via ILogger) för alla begäranden som skickas via klienter som skapats av fabriken.

Exempelkoden i den här ämnesversionen använder System.Text.Json för att deserialisera JSON-innehåll som returneras i HTTP-svar. För exempel som använder Json.NET och ReadAsAsync<T>använder du versionsväljaren för att välja en 2.x-version av det här avsnittet.

Konsumtionsmönster

Det finns flera sätt IHttpClientFactory att använda i en app:

Den bästa metoden beror på appens krav.

Grundläggande användning

Registrera IHttpClientFactory genom att ringa AddHttpClient i Program.cs:

var builder = WebApplication.CreateBuilder(args);

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

En IHttpClientFactory kan begäras med hjälp av beroendeinjektion (DI). Följande kod använder IHttpClientFactory för att skapa en HttpClient instans:

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

Att använda IHttpClientFactory som i föregående exempel är ett bra sätt att omstrukturera en befintlig app. Det har ingen inverkan på hur HttpClient används. På platser där HttpClient instanser skapas i en befintlig app ersätter du dessa förekomster med anrop till CreateClient.

Namngivna klienter

Namngivna klienter är ett bra val när:

  • Appen kräver många distinkta användningsområden för HttpClient.
  • Många HttpClienthar olika konfiguration.

Ange konfiguration för en namngiven HttpClient under registreringen i 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");
});

I föregående kod konfigureras klienten med:

  • Basadressen https://api.github.com/.
  • Två headers behövs för att arbeta med GitHub-API:et.

CreateClient

Varje gång CreateClient anropas:

  • En ny instans av HttpClient skapas.
  • Konfigurationsåtgärden utförs.

Om du vill skapa en namngiven klient skickar du namnet till 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);
        }
    }
}

I föregående kod behöver begäran inte ange ett värdnamn. Koden kan bara skicka sökvägen eftersom basadressen som konfigurerats för klienten används.

Inskrivna klienter

Inskrivna klienter:

  • Ge samma funktioner som namngivna klienter utan att behöva använda strängar som nycklar.
  • Ger IntelliSense och kompilatorhjälp vid användning av klienter.
  • Ange en enda plats för att konfigurera och interagera med en viss HttpClient. Till exempel kan en enskild typad klient användas:
    • För en enskild backend-endpunkt.
    • Att kapsla in all logik som hanterar slutpunkten.
  • Arbeta med DI och kan matas in där det behövs i appen.

En typad klient accepterar en HttpClient parameter i konstruktorn:

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

I koden ovan:

  • Konfigurationen flyttas till den typade klienten.
  • Den angivna HttpClient instansen lagras som ett privat fält.

API-specifika metoder kan skapas som exponerar HttpClient funktioner. Metoden kapslar till exempel in GetAspNetCoreDocsBranches kod för att hämta grenar i GitHub-dokumentationen.

Följande kod anropar AddHttpClient i Program.cs för att registrera den GitHubService typspecifika klientklassen:

builder.Services.AddHttpClient<GitHubService>();

Den typspecifika klienten är registrerad som övergående med DI. I föregående kod AddHttpClient registreras GitHubService som en tillfällig tjänst. Den här registreringen använder en fabriksmetod för att:

  1. Skapa en instans av HttpClient.
  2. Skapa en instans av GitHubServiceoch skicka in instansen av HttpClient till konstruktorn.

Den inskrivna klienten kan matas in och användas direkt:

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)
        {
            // ...
        }
    }
}

Konfigurationen för en typad klient kan också anges under registreringen i Program.cs, i stället för i den typade klientens konstruktor.

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

    // ...
});

Genererade klienter

IHttpClientFactory kan användas i kombination med bibliotek från tredje part, till exempel Refit. Refit är ett REST bibliotek för .NET. Den konverterar REST API:er till livegränssnitt. Anropa AddRefitClient för att generera en dynamisk implementering av ett gränssnitt som använder HttpClient för att göra externa HTTP-anrop.

Ett anpassat gränssnitt representerar det externa API:et:

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

Anropa AddRefitClient för att generera den dynamiska implementeringen och anropa ConfigureHttpClient sedan för att konfigurera den underliggande HttpClient:

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

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

Använd DI för att komma åt den dynamiska implementeringen av 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)
        {
            // ...
        }
    }
}

Göra POST-, PUT- och DELETE-begäranden

I föregående exempel använder alla HTTP-begäranden GET HTTP-verbet. HttpClient stöder även andra HTTP-verb, inklusive:

  • POST
  • PUT
  • DELETE
  • PATCH

En fullständig lista över HTTP-verb som stöds finns i HttpMethod.

I följande exempel visas hur du gör en HTTP POST-begäran:

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

I föregående kod, CreateItemAsync metoden:

  • Serialiserar parametern TodoItem till JSON med .System.Text.Json
  • Skapar en instans av StringContent för att paketera den serialiserade JSON:en för att skicka i HTTP-förfrågans kropp.
  • Anropar PostAsync för att skicka JSON-innehållet till den angivna URL:en. Det här är en relativ URL som läggs till i HttpClient.BaseAddress.
  • Anrop EnsureSuccessStatusCode för att utlösa ett undantag om svarsstatuskoden inte indikerar att åtgärden lyckades.

HttpClient stöder även andra typer av innehåll. Till exempel MultipartContent och StreamContent. En fullständig lista över innehåll som stöds finns i HttpContent.

I följande exempel visas en HTTP PUT-begäran:

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

Föregående kod liknar POST-exemplet. Metoden SaveItemAsync anropar PutAsync i stället för PostAsync.

I följande exempel visas en HTTP DELETE-begäran:

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

    httpResponseMessage.EnsureSuccessStatusCode();
}

I föregående kod DeleteItemAsync anropar DeleteAsyncmetoden . Eftersom HTTP DELETE-begäranden vanligtvis inte innehåller någon brödtext tillhandahåller DeleteAsync metoden inte någon överlagring som accepterar en instans av HttpContent.

Mer information om hur du använder olika HTTP-verb med HttpClientfinns i HttpClient.

Mellanprogram för utgående begäran

HttpClient har konceptet att delegera hanterare som kan länkas samman för utgående HTTP-begäranden. IHttpClientFactory:

  • Förenklar definitionen av hanterare som ska användas för varje namngiven klient.
  • Understödjer registrering och kedjning av flera handlers för att bygga en middleware-pipeline för utgående begäranden. Var och en av dessa hanterare kan utföra arbete före och efter den utgående begäran. Det här mönstret:
    • Liknar pipelinen för inkommande mellanprogram i ASP.NET Core.
    • Tillhandahåller en mekanism för att hantera övergripande problem kring HTTP-begäranden, till exempel:
      • caching
      • error handling
      • serialization
      • logging

Så här skapar du en delegeringshanterare:

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

Den tidigare koden kontrollerar om X-API-KEY header finns i begäran. Om X-API-KEY saknas BadRequest returneras.

Mer än en hanterare kan läggas till i konfigurationen för en HttpClient med Microsoft.Extensions.DependencyInjection.HttpClientBuilderExtensions.AddHttpMessageHandler:

builder.Services.AddTransient<ValidateHeaderHandler>();

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

I föregående kod är den ValidateHeaderHandler registrerad med DI. När AddHttpMessageHandler har registrerats kan du anropa det och skicka in typen för hanteraren.

Flera hanterare kan registreras i den ordning de ska köras. Varje hanterare omsluter nästa hanterare tills den slutgiltiga HttpClientHandler kör begäran:

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

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

I föregående kod SampleHandler1 körs först, före SampleHandler2.

Använd DI i mellanmjukvara för utgående förfrågningar

När IHttpClientFactory skapar en ny delegeringshanterare använder den DI för att uppfylla konstruktorparametrarna för hanteraren. IHttpClientFactory skapar ett separat DI-omfång för varje hanterare, vilket kan leda till överraskande beteende när en hanterare använder en begränsad tjänst.

Tänk till exempel på följande gränssnitt och dess implementering, som representerar en uppgift som en åtgärd med en identifierare, OperationId:

public interface IOperationScoped
{
    string OperationId { get; }
}

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

Som namnet antyder IOperationScoped registreras med DI med en begränsad livslängd:

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

Följande delegeringshanterare konsumerar och använder IOperationScoped för att ange X-OPERATION-ID huvud för den utgående begäran.

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

I nedladdningsmappenHttpRequestsSample går du till /Operation och uppdaterar sidan. Värdet för omfånget för begäran ändras för varje begäran, men värdet för hanterarens omfång ändras bara var femte sekund.

Hanterare kan vara beroende av tjänster i alla omfång. Tjänster som hanterarna är beroende av tas bort när hanteraren tas bort.

Använd någon av följande metoder för att dela status per begäran med meddelandehanterare:

Använda Polly-baserade hanterare

IHttpClientFactory integreras med biblioteket Polly från tredje part. Polly är ett omfattande återhämtnings- och tillfälligt bibliotek för felhantering för .NET. Det gör att utvecklare kan uttrycka principer som Retry, Circuit Breaker, Timeout, Bulkhead Isolation och Fallback på ett flytande och trådsäkert sätt.

Tilläggsmetoder tillhandahålls för att möjliggöra användning av Polly-policies med konfigurerade HttpClient instanser. Polly-tilläggen har stöd för att lägga till Polly-baserade hanterare till klienter. Polly kräver NuGet-paketet Microsoft.Extensions.Http.Polly .

Hantera tillfälliga fel

Fel uppstår vanligtvis när externa HTTP-anrop är tillfälliga. AddTransientHttpErrorPolicy tillåter att en princip definieras för att hantera tillfälliga fel. Principer som konfigurerats med AddTransientHttpErrorPolicy hanterar följande svar:

AddTransientHttpErrorPolicy ger åtkomst till ett PolicyBuilder objekt som har konfigurerats för att hantera fel som representerar ett möjligt tillfälligt fel:

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

I föregående kod definieras en WaitAndRetryAsync princip. Misslyckade begäranden görs på nytt upp till tre gånger med en fördröjning på 600 ms mellan försöken.

Välj principer dynamiskt

Tilläggsmetoder tillhandahålls för att lägga till Polly-baserade hanterare, AddPolicyHandlertill exempel . Följande AddPolicyHandler överlagring inspekterar begäran för att avgöra vilken policy som ska tillämpas.

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

Om den utgående begäran i föregående kod är en HTTP GET tillämpas en tidsgräns på 10 sekunder. För andra HTTP-metoder används en tidsgräns på 30 sekunder.

Lägga till flera Polly-hanterare

Det är vanligt att kapsla Polly-principer:

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

I föregående exempel:

  • Två hanterare läggs till.
  • Den första hanteraren använder AddTransientHttpErrorPolicy för att lägga till en återförsöksprincip. Misslyckade begäranden görs på nytt upp till tre gånger.
  • Det andra AddTransientHttpErrorPolicy anropet lägger till en kretsbrytarstrategi. Ytterligare externa begäranden blockeras i 30 sekunder om 5 misslyckade försök görs sekventiellt. Kretsbrytarpolicys är tillståndsbaserade. Alla anrop via den här klienten delar samma kretstillstånd.

Lägga till principer från Polly-registret

En metod för att hantera principer som används regelbundet är att definiera dem en gång och registrera dem med en PolicyRegistry. Till exempel:

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

I koden ovan:

  • Två policies, Regular och Long, läggs till i Polly-registret.
  • AddPolicyHandlerFromRegistry konfigurerar enskilda namngivna klienter för att använda dessa riktlinjer från Polly-registret.

Mer information om IHttpClientFactory och Polly-integreringar finns i Polly-wikin.

HttpClient och livslängdshantering

En ny HttpClient instans returneras varje gång CreateClient anropas på IHttpClientFactory. En HttpMessageHandler skapas för varje namngiven klient. Fabriken hanterar livslängden på HttpMessageHandler instanserna.

IHttpClientFactory poolar de HttpMessageHandler instanser som skapats av fabriken för att minska resursförbrukningen. En HttpMessageHandler instans kan återanvändas från poolen när du skapar en ny HttpClient instans om dess livslängd inte har upphört att gälla.

Poolning av hanterare är önskvärt eftersom varje hanterare vanligtvis hanterar sina egna underliggande HTTP-anslutningar. Om du skapar fler hanterare än nödvändigt kan det leda till anslutningsfördröjningar. Vissa hanterare håller också anslutningarna öppna på obestämd tid, vilket kan hindra hanteraren från att reagera på DNS-ändringar (Domännamnssystem).

Standardhanterarlivslängden är två minuter. Standardvärdet kan åsidosättas per namngiven klient:

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

HttpClient instanser kan vanligtvis behandlas som .NET-objekt som inte kräver bortskaffande. Att kassera avbryter utgående begäranden och garanterar att den angivna HttpClient instansen inte kan användas efter att Dispose har kallats. IHttpClientFactory spårar och avyttrar resurser som används av HttpClient instanser.

Att hålla en enskild HttpClient instans vid liv under en längre tid är ett vanligt mönster som används före starten av IHttpClientFactory. Det här mönstret blir onödigt när du har migrerat till IHttpClientFactory.

Alternativ till IHttpClientFactory

Om du använder IHttpClientFactory i en DI-aktiverad app undviker du:

  • Problem med resursöverbelastning genom att poola HttpMessageHandler instanser.
  • Åtgärda inaktuella DNS-problem genom att cykla HttpMessageHandler instanser med jämna mellanrum.

Det finns alternativa sätt att lösa de föregående problemen med hjälp av en långlivad SocketsHttpHandler instans.

  • Skapa en instans av SocketsHttpHandler när appen startar och använd den under appens livslängd.
  • Konfigurera PooledConnectionLifetime till ett lämpligt värde baserat på DNS-uppdateringstider.
  • Skapa HttpClient instanser med hjälp av new HttpClient(handler, disposeHandler: false) efter behov.

De föregående metoderna löser de resurshanteringsproblem som IHttpClientFactory löser på ett liknande sätt.

  • Den SocketsHttpHandler delar anslutningar mellan HttpClient instanser. Denna delning förhindrar uttömning av socketar.
  • SocketsHttpHandler cyklar anslutningar enligt PooledConnectionLifetime för att undvika de inaktuella DNS-problemen.

Logging

Klienter som skapats via IHttpClientFactory registrerar loggmeddelanden för alla förfrågningar. Aktivera lämplig informationsnivå i loggningskonfigurationen för att se standardloggmeddelandena. Ytterligare loggning, till exempel loggning av begärandehuvuden, ingår endast på spårningsnivå.

Loggkategorin som används för varje klient innehåller namnet på klienten. En klient med namnet MyNamedClient loggar till exempel meddelanden med kategorin "System.Net.Http.HttpClient. MyNamedClient. LogicalHandler". Meddelanden som är suffixerade med LogicalHandler sker utanför pipelinen för begärhanteraren. På begäran loggas meddelanden innan andra hanterare i pipelinen har bearbetat den. I svaret loggas meddelanden efter att andra pipelinehanterare har tagit emot svaret.

Loggning sker också i pipelinen för begärandehanteraren. I exemplet MyNamedClient loggas dessa meddelanden med loggkategorin "System.Net.Http.HttpClient. MyNamedClient. ClientHandler". För denna begäran sker detta när alla andra hanterare har körts och omedelbart innan begäran skickas. I svaret innehåller den här loggningen tillståndet för svaret innan det skickas tillbaka via hanteringspipelinen.

Om du aktiverar loggning utanför och inuti pipelinen kan du kontrollera de ändringar som gjorts av de andra pipelinehantarna. Detta kan inkludera ändringar i begärandehuvuden eller i svarsstatuskoden.

Att inkludera namnet på klienten i loggkategorin möjliggör loggfiltrering för specifika namngivna klienter.

Konfigurera HttpMessageHandler

Det kan vara nödvändigt att styra konfigurationen av det inre HttpMessageHandler som används av en klient.

En IHttpClientBuilder returneras när du lägger till namngivna eller inskrivna klienter. Tilläggsmetoden ConfigurePrimaryHttpMessageHandler kan användas för att definiera ett ombud. Ombudet används för att skapa och konfigurera den primära HttpMessageHandler som används av klienten:

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

Cookies

Poolinstanserna HttpMessageHandler leder till att CookieContainer objekt delas. Oväntad CookieContainer objektdelning resulterar ofta i felaktig kod. För appar som kräver cookies bör du överväga något av följande:

  • Inaktivera automatisk cookie hantering
  • Undvika IHttpClientFactory

Anropa ConfigurePrimaryHttpMessageHandler för att inaktivera automatisk cookie hantering:

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

Använda IHttpClientFactory i en konsolapp

Lägg till följande paketreferenser i projektet i en konsolapp:

I följande exempel:

  • IHttpClientFactory och GitHubService är registrerade i den generiska värdens tjänstcontainer.
  • GitHubService begärs av DI, som i sin tur begär en instans av IHttpClientFactory.
  • GitHubService använder IHttpClientFactory för att skapa en instans av HttpClient, som den använder för att hämta GitHub-dokumentgrenar.
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);

Mellanprogram för rubrikspridning

Headerpropagering är ett ASP.NET Core-mellanlager för att propagera HTTP-huvuden från den inkommande begäran till utgående HttpClient begäranden. Så här använder du sidhuvudspridning:

  • Installera paketet Microsoft.AspNetCore.HeaderPropagation .

  • Konfigurera HttpClient och mellanvarupipelinen i 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();
    
  • Gör utgående begäranden med hjälp av den konfigurerade HttpClient instansen, som innehåller de tillagda rubrikerna.

Ytterligare resurser

Av Kirk Larkin, Steve Gordon, Glenn Condron och Ryan Nowak.

En IHttpClientFactory kan registreras och användas för att konfigurera och skapa HttpClient instanser i en app. IHttpClientFactory erbjuder följande fördelar:

  • Tillhandahåller en central plats för att namnge och konfigurera logiska HttpClient instanser. En klient med namnet github kan till exempel registreras och konfigureras för åtkomst till GitHub. En standardklient kan registreras för allmän åtkomst.
  • Kodifierar begreppet utgående mellanprogram via delegering av hanterare i HttpClient. Tillhandahåller tillägg för Polly-baserade mellanprogram för att dra nytta av delegering av hanterare i HttpClient.
  • Hanterar pooleringen och livslängden för underliggande HttpClientMessageHandler instanser. Automatisk hantering undviker vanliga DNS-problem (Domain Name System) som uppstår när HttpClient livslängder hanteras manuellt.
  • Lägger till en konfigurerbar loggningsupplevelse (via ILogger) för alla begäranden som skickas via klienter som skapats av fabriken.

Visa eller ladda ned exempelkod (hur du laddar ned).

Exempelkoden i den här ämnesversionen använder System.Text.Json för att deserialisera JSON-innehåll som returneras i HTTP-svar. För exempel som använder Json.NET och ReadAsAsync<T>använder du versionsväljaren för att välja en 2.x-version av det här avsnittet.

Konsumtionsmönster

Det finns flera sätt IHttpClientFactory att använda i en app:

Den bästa metoden beror på appens krav.

Grundläggande användning

IHttpClientFactory kan registreras genom att anropa 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.

En IHttpClientFactory kan begäras med hjälp av beroendeinjektion (DI). Följande kod använder IHttpClientFactory för att skapa en HttpClient instans:

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

Att använda IHttpClientFactory som i föregående exempel är ett bra sätt att omstrukturera en befintlig app. Det har ingen inverkan på hur HttpClient används. På platser där HttpClient instanser skapas i en befintlig app ersätter du dessa förekomster med anrop till CreateClient.

Namngivna klienter

Namngivna klienter är ett bra val när:

  • Appen kräver många distinkta användningsområden för HttpClient.
  • Många HttpClienthar olika konfiguration.

Konfiguration för ett namngivet HttpClient kan anges under registreringen i 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");
});

I föregående kod konfigureras klienten med:

  • Basadressen https://api.github.com/.
  • Två headers behövs för att arbeta med GitHub-API:et.

CreateClient

Varje gång CreateClient anropas:

  • En ny instans av HttpClient skapas.
  • Konfigurationsåtgärden utförs.

Om du vill skapa en namngiven klient skickar du namnet till 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>();
        }
    }
}

I föregående kod behöver begäran inte ange ett värdnamn. Koden kan bara skicka sökvägen eftersom basadressen som konfigurerats för klienten används.

Inskrivna klienter

Inskrivna klienter:

  • Ge samma funktioner som namngivna klienter utan att behöva använda strängar som nycklar.
  • Ger IntelliSense och kompilatorhjälp vid användning av klienter.
  • Ange en enda plats för att konfigurera och interagera med en viss HttpClient. Till exempel kan en enskild typad klient användas:
    • För en enskild backend-endpunkt.
    • Att kapsla in all logik som hanterar slutpunkten.
  • Arbeta med DI och kan matas in där det behövs i appen.

En typad klient accepterar en HttpClient parameter i konstruktorn:

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

I koden ovan:

  • Konfigurationen flyttas till den typade klienten.
  • Objektet HttpClient exponeras som en offentlig egenskap.

API-specifika metoder kan skapas som exponerar HttpClient funktioner. Metoden kapslar till exempel GetAspNetDocsIssues in kod för att hämta öppna problem.

Följande kod anropar AddHttpClientStartup.ConfigureServices för att registrera en typad klientklass:

services.AddHttpClient<GitHubService>();

Den typspecifika klienten är registrerad som övergående med DI. I föregående kod AddHttpClient registreras GitHubService som en tillfällig tjänst. Den här registreringen använder en fabriksmetod för att:

  1. Skapa en instans av HttpClient.
  2. Skapa en instans av GitHubServiceoch skicka in instansen av HttpClient till konstruktorn.

Den inskrivna klienten kan matas in och användas direkt:

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

Konfigurationen för en typad klient kan anges under registreringen i Startup.ConfigureServices istället för i den typade klientens konstruktor.

services.AddHttpClient<RepoService>(c =>
{
    c.BaseAddress = new Uri("https://api.github.com/");
    c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
    c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});

HttpClient kan kapslas in i en typad klient. I stället för att exponera den som en egenskap definierar du en metod som anropar instansen HttpClient internt:

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

I föregående kod HttpClient lagras den i ett privat fält. Åtkomst till HttpClient sker via den offentliga metoden GetRepos.

Genererade klienter

IHttpClientFactory kan användas i kombination med bibliotek från tredje part, till exempel Refit. Refit är ett REST bibliotek för .NET. Den konverterar REST API:er till livegränssnitt. En implementering av gränssnittet genereras dynamiskt av RestService, med hjälp av HttpClient för att göra externa HTTP-anrop.

Ett gränssnitt och ett svar definieras för att representera det externa API:et och dess svar:

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

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

En typad klient kan läggas till med hjälp av Refit för att generera implementeringen.

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

Det definierade gränssnittet kan användas vid behov, med implementeringen som tillhandahålls av DI och Refit:

[ApiController]
public class ValuesController : ControllerBase
{
    private readonly IHelloClient _client;

    public ValuesController(IHelloClient client)
    {
        _client = client;
    }

    [HttpGet("/")]
    public async Task<ActionResult<Reply>> Index()
    {
        return await _client.GetMessageAsync();
    }
}

Göra POST-, PUT- och DELETE-begäranden

I föregående exempel använder alla HTTP-begäranden GET HTTP-verbet. HttpClient stöder även andra HTTP-verb, inklusive:

  • POST
  • PUT
  • DELETE
  • PATCH

En fullständig lista över HTTP-verb som stöds finns i HttpMethod.

I följande exempel visas hur du gör en HTTP POST-begäran:

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

I föregående kod, CreateItemAsync metoden:

  • Serialiserar parametern TodoItem till JSON med .System.Text.Json Detta använder en instans av JsonSerializerOptions för att konfigurera serialiseringsprocessen.
  • Skapar en instans av StringContent för att paketera den serialiserade JSON:en för att skicka i HTTP-förfrågans kropp.
  • Anropar PostAsync för att skicka JSON-innehållet till den angivna URL:en. Det här är en relativ URL som läggs till i HttpClient.BaseAddress.
  • Anrop EnsureSuccessStatusCode för att utlösa ett undantag om svarsstatuskoden inte indikerar att åtgärden lyckades.

HttpClient stöder även andra typer av innehåll. Till exempel MultipartContent och StreamContent. En fullständig lista över innehåll som stöds finns i HttpContent.

I följande exempel visas en HTTP PUT-begäran:

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

Föregående kod liknar postexemplet. Metoden SaveItemAsync anropar PutAsync i stället för PostAsync.

I följande exempel visas en HTTP DELETE-begäran:

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

    httpResponse.EnsureSuccessStatusCode();
}

I föregående kod DeleteItemAsync anropar DeleteAsyncmetoden . Eftersom HTTP DELETE-begäranden vanligtvis inte innehåller någon brödtext tillhandahåller DeleteAsync metoden inte någon överlagring som accepterar en instans av HttpContent.

Mer information om hur du använder olika HTTP-verb med HttpClientfinns i HttpClient.

Mellanprogram för utgående begäran

HttpClient har konceptet att delegera hanterare som kan länkas samman för utgående HTTP-begäranden. IHttpClientFactory:

  • Förenklar definitionen av hanterare som ska användas för varje namngiven klient.
  • Understödjer registrering och kedjning av flera handlers för att bygga en middleware-pipeline för utgående begäranden. Var och en av dessa hanterare kan utföra arbete före och efter den utgående begäran. Det här mönstret:
    • Liknar pipelinen för inkommande mellanprogram i ASP.NET Core.
    • Tillhandahåller en mekanism för att hantera övergripande problem kring HTTP-begäranden, till exempel:
      • caching
      • error handling
      • serialization
      • logging

Så här skapar du en delegeringshanterare:

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

Den tidigare koden kontrollerar om X-API-KEY header finns i begäran. Om X-API-KEY saknas BadRequest returneras.

Mer än en hanterare kan läggas till i konfigurationen för en HttpClient med 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.

I föregående kod är den ValidateHeaderHandler registrerad med DI. När AddHttpMessageHandler har registrerats kan du anropa det och skicka in typen för hanteraren.

Flera hanterare kan registreras i den ordning de ska köras. Varje hanterare omsluter nästa hanterare tills den slutgiltiga HttpClientHandler kör begäran:

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

Använd DI i mellanmjukvara för utgående förfrågningar

När IHttpClientFactory skapar en ny delegeringshanterare använder den DI för att uppfylla konstruktorparametrarna för hanteraren. IHttpClientFactory skapar ett separat DI-omfång för varje hanterare, vilket kan leda till överraskande beteende när en hanterare använder en begränsad tjänst.

Tänk till exempel på följande gränssnitt och dess implementering, som representerar en uppgift som en åtgärd med en identifierare, OperationId:

public interface IOperationScoped 
{
    string OperationId { get; }
}

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

Som namnet antyder IOperationScoped registreras med DI med en begränsad livslängd:

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

Följande delegeringshanterare konsumerar och använder IOperationScoped för att ange X-OPERATION-ID huvud för den utgående begäran.

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

I HttpRequestsSample nedladdningsområdet, navigera till /Operation och ladda om sidan. Värdet för omfånget för begäran ändras för varje begäran, men värdet för hanterarens omfång ändras bara var femte sekund.

Hanterare kan vara beroende av tjänster i alla omfång. Tjänster som hanterarna är beroende av tas bort när hanteraren tas bort.

Använd någon av följande metoder för att dela status per begäran med meddelandehanterare:

Använda Polly-baserade hanterare

IHttpClientFactory integreras med biblioteket Polly från tredje part. Polly är ett omfattande återhämtnings- och tillfälligt bibliotek för felhantering för .NET. Det gör att utvecklare kan uttrycka principer som Retry, Circuit Breaker, Timeout, Bulkhead Isolation och Fallback på ett flytande och trådsäkert sätt.

Tilläggsmetoder tillhandahålls för att möjliggöra användning av Polly-policies med konfigurerade HttpClient instanser. Polly-tilläggen har stöd för att lägga till Polly-baserade hanterare till klienter. Polly kräver NuGet-paketet Microsoft.Extensions.Http.Polly .

Hantera tillfälliga fel

Fel uppstår vanligtvis när externa HTTP-anrop är tillfälliga. AddTransientHttpErrorPolicy tillåter att en princip definieras för att hantera tillfälliga fel. Principer som konfigurerats med AddTransientHttpErrorPolicy hanterar följande svar:

AddTransientHttpErrorPolicy ger åtkomst till ett PolicyBuilder objekt som har konfigurerats för att hantera fel som representerar ett möjligt tillfälligt fel:

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

    // Remaining code deleted for brevity.

I föregående kod definieras en WaitAndRetryAsync princip. Misslyckade begäranden görs på nytt upp till tre gånger med en fördröjning på 600 ms mellan försöken.

Välj principer dynamiskt

Tilläggsmetoder tillhandahålls för att lägga till Polly-baserade hanterare, AddPolicyHandlertill exempel . Följande AddPolicyHandler överlagring inspekterar begäran för att avgöra vilken policy som ska tillämpas.

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

Om den utgående begäran i föregående kod är en HTTP GET tillämpas en tidsgräns på 10 sekunder. För andra HTTP-metoder används en tidsgräns på 30 sekunder.

Lägga till flera Polly-hanterare

Det är vanligt att kapsla Polly-principer:

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

I föregående exempel:

  • Två hanterare läggs till.
  • Den första hanteraren använder AddTransientHttpErrorPolicy för att lägga till en återförsöksprincip. Misslyckade begäranden görs på nytt upp till tre gånger.
  • Det andra AddTransientHttpErrorPolicy anropet lägger till en kretsbrytarstrategi. Ytterligare externa begäranden blockeras i 30 sekunder om 5 misslyckade försök görs sekventiellt. Kretsbrytarpolicys är tillståndsbaserade. Alla anrop via den här klienten delar samma kretstillstånd.

Lägga till principer från Polly-registret

En metod för att hantera principer som används regelbundet är att definiera dem en gång och registrera dem med en PolicyRegistry.

I följande kod:

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.

Mer information om IHttpClientFactory och Polly-integreringar finns i Polly-wikin.

HttpClient och livslängdshantering

En ny HttpClient instans returneras varje gång CreateClient anropas på IHttpClientFactory. En HttpMessageHandler skapas för varje namngiven klient. Fabriken hanterar livslängden på HttpMessageHandler instanserna.

IHttpClientFactory poolar de HttpMessageHandler instanser som skapats av fabriken för att minska resursförbrukningen. En HttpMessageHandler instans kan återanvändas från poolen när du skapar en ny HttpClient instans om dess livslängd inte har upphört att gälla.

Poolning av hanterare är önskvärt eftersom varje hanterare vanligtvis hanterar sina egna underliggande HTTP-anslutningar. Om du skapar fler hanterare än nödvändigt kan det leda till anslutningsfördröjningar. Vissa hanterare håller också anslutningarna öppna på obestämd tid, vilket kan hindra hanteraren från att reagera på DNS-ändringar (Domännamnssystem).

Standardhanterarlivslängden är två minuter. Standardvärdet kan åsidosättas per namngiven klient:

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

    // Remaining code deleted for brevity.

HttpClient instanser kan vanligtvis behandlas som .NET-objekt som inte kräver bortskaffande. Att kassera avbryter utgående begäranden och garanterar att den angivna HttpClient instansen inte kan användas efter att Dispose har kallats. IHttpClientFactory spårar och avyttrar resurser som används av HttpClient instanser.

Att hålla en enskild HttpClient instans vid liv under en längre tid är ett vanligt mönster som används före starten av IHttpClientFactory. Det här mönstret blir onödigt när du har migrerat till IHttpClientFactory.

Alternativ till IHttpClientFactory

Om du använder IHttpClientFactory i en DI-aktiverad app undviker du:

  • Problem med resursöverbelastning genom att poola HttpMessageHandler instanser.
  • Åtgärda inaktuella DNS-problem genom att cykla HttpMessageHandler instanser med jämna mellanrum.

Det finns alternativa sätt att lösa de föregående problemen med hjälp av en långlivad SocketsHttpHandler instans.

  • Skapa en instans av SocketsHttpHandler när appen startar och använd den under appens livslängd.
  • Konfigurera PooledConnectionLifetime till ett lämpligt värde baserat på DNS-uppdateringstider.
  • Skapa HttpClient instanser med hjälp av new HttpClient(handler, disposeHandler: false) efter behov.

De föregående metoderna löser de resurshanteringsproblem som IHttpClientFactory löser på ett liknande sätt.

  • Den SocketsHttpHandler delar anslutningar mellan HttpClient instanser. Denna delning förhindrar uttömning av socketar.
  • SocketsHttpHandler cyklar anslutningar enligt PooledConnectionLifetime för att undvika de inaktuella DNS-problemen.

Cookies

Poolinstanserna HttpMessageHandler leder till att CookieContainer objekt delas. Oväntad CookieContainer objektdelning resulterar ofta i felaktig kod. För appar som kräver cookies bör du överväga något av följande:

  • Inaktivera automatisk cookie hantering
  • Undvika IHttpClientFactory

Anropa ConfigurePrimaryHttpMessageHandler för att inaktivera automatisk cookie hantering:

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

Logging

Klienter som skapats via IHttpClientFactory registrerar loggmeddelanden för alla förfrågningar. Aktivera lämplig informationsnivå i loggningskonfigurationen för att se standardloggmeddelandena. Ytterligare loggning, till exempel loggning av begärandehuvuden, ingår endast på spårningsnivå.

Loggkategorin som används för varje klient innehåller namnet på klienten. En klient med namnet MyNamedClient loggar till exempel meddelanden med kategorin "System.Net.Http.HttpClient. MyNamedClient. LogicalHandler". Meddelanden som är suffixerade med LogicalHandler sker utanför pipelinen för begärhanteraren. På begäran loggas meddelanden innan andra hanterare i pipelinen har bearbetat den. I svaret loggas meddelanden efter att andra pipelinehanterare har tagit emot svaret.

Loggning sker också i pipelinen för begärandehanteraren. I exemplet MyNamedClient loggas dessa meddelanden med loggkategorin "System.Net.Http.HttpClient. MyNamedClient. ClientHandler". För denna begäran sker detta när alla andra hanterare har körts och omedelbart innan begäran skickas. I svaret innehåller den här loggningen tillståndet för svaret innan det skickas tillbaka via hanteringspipelinen.

Om du aktiverar loggning utanför och inuti pipelinen kan du kontrollera de ändringar som gjorts av de andra pipelinehantarna. Detta kan inkludera ändringar i begärandehuvuden eller i svarsstatuskoden.

Att inkludera namnet på klienten i loggkategorin möjliggör loggfiltrering för specifika namngivna klienter.

Konfigurera HttpMessageHandler

Det kan vara nödvändigt att styra konfigurationen av det inre HttpMessageHandler som används av en klient.

En IHttpClientBuilder returneras när du lägger till namngivna eller inskrivna klienter. Tilläggsmetoden ConfigurePrimaryHttpMessageHandler kan användas för att definiera ett ombud. Ombudet används för att skapa och konfigurera den primära HttpMessageHandler som används av klienten:

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

    // Remaining code deleted for brevity.

Använda IHttpClientFactory i en konsolapp

Lägg till följande paketreferenser i projektet i en konsolapp:

I följande exempel:

  • IHttpClientFactory är registrerad i den generiska värdens tjänstcontainer.
  • MyService skapar en klientfabriksinstans från tjänsten, som används för att skapa en HttpClient. HttpClient används för att hämta en webbsida.
  • Main skapar ett omfång för att köra tjänstens GetPage metod och skriva de första 500 tecknen i webbsidans innehåll till konsolen.
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}";
            }
        }
    }
}

Mellanprogram för rubrikspridning

Rubrikspridning är ett ASP.NET Core-mellanprogram för att sprida HTTP-huvuden från den inkommande begäran till utgående HTTP-klientbegäranden. Så här använder du sidhuvudspridning:

  • Referera till paketet Microsoft.AspNetCore.HeaderPropagation .

  • Konfigurera mellanprogrammet och HttpClient i 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();
        });
    }
    
  • Klienten innehåller de konfigurerade huvudena för utgående begäranden:

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

Ytterligare resurser

Av Kirk Larkin, Steve Gordon, Glenn Condron och Ryan Nowak.

En IHttpClientFactory kan registreras och användas för att konfigurera och skapa HttpClient instanser i en app. IHttpClientFactory erbjuder följande fördelar:

  • Tillhandahåller en central plats för att namnge och konfigurera logiska HttpClient instanser. En klient med namnet github kan till exempel registreras och konfigureras för åtkomst till GitHub. En standardklient kan registreras för allmän åtkomst.
  • Kodifierar begreppet utgående mellanprogram via delegering av hanterare i HttpClient. Tillhandahåller tillägg för Polly-baserade mellanprogram för att dra nytta av delegering av hanterare i HttpClient.
  • Hanterar pooleringen och livslängden för underliggande HttpClientMessageHandler instanser. Automatisk hantering undviker vanliga DNS-problem (Domain Name System) som uppstår när HttpClient livslängder hanteras manuellt.
  • Lägger till en konfigurerbar loggningsupplevelse (via ILogger) för alla begäranden som skickas via klienter som skapats av fabriken.

Visa eller ladda ned exempelkod (hur du laddar ned).

Exempelkoden i den här ämnesversionen använder System.Text.Json för att deserialisera JSON-innehåll som returneras i HTTP-svar. För exempel som använder Json.NET och ReadAsAsync<T>använder du versionsväljaren för att välja en 2.x-version av det här avsnittet.

Konsumtionsmönster

Det finns flera sätt IHttpClientFactory att använda i en app:

Den bästa metoden beror på appens krav.

Grundläggande användning

IHttpClientFactory kan registreras genom att anropa 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.

En IHttpClientFactory kan begäras med hjälp av beroendeinjektion (DI). Följande kod använder IHttpClientFactory för att skapa en HttpClient instans:

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

Att använda IHttpClientFactory som i föregående exempel är ett bra sätt att omstrukturera en befintlig app. Det har ingen inverkan på hur HttpClient används. På platser där HttpClient instanser skapas i en befintlig app ersätter du dessa förekomster med anrop till CreateClient.

Namngivna klienter

Namngivna klienter är ett bra val när:

  • Appen kräver många distinkta användningsområden för HttpClient.
  • Många HttpClienthar olika konfiguration.

Konfiguration för ett namngivet HttpClient kan anges under registreringen i 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");
});

I föregående kod konfigureras klienten med:

  • Basadressen https://api.github.com/.
  • Två headers behövs för att arbeta med GitHub-API:et.

CreateClient

Varje gång CreateClient anropas:

  • En ny instans av HttpClient skapas.
  • Konfigurationsåtgärden utförs.

Om du vill skapa en namngiven klient skickar du namnet till 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>();
        }
    }
}

I föregående kod behöver begäran inte ange ett värdnamn. Koden kan bara skicka sökvägen eftersom basadressen som konfigurerats för klienten används.

Inskrivna klienter

Inskrivna klienter:

  • Ge samma funktioner som namngivna klienter utan att behöva använda strängar som nycklar.
  • Ger IntelliSense och kompilatorhjälp vid användning av klienter.
  • Ange en enda plats för att konfigurera och interagera med en viss HttpClient. Till exempel kan en enskild typad klient användas:
    • För en enskild backend-endpunkt.
    • Att kapsla in all logik som hanterar slutpunkten.
  • Arbeta med DI och kan matas in där det behövs i appen.

En typad klient accepterar en HttpClient parameter i konstruktorn:

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

Om du vill se kodkommentar översatta till andra språk än engelska kan du meddela oss i det här GitHub-diskussionsproblemet.

I koden ovan:

  • Konfigurationen flyttas till den typade klienten.
  • Objektet HttpClient exponeras som en offentlig egenskap.

API-specifika metoder kan skapas som exponerar HttpClient funktioner. Metoden kapslar till exempel GetAspNetDocsIssues in kod för att hämta öppna problem.

Följande kod anropar AddHttpClientStartup.ConfigureServices för att registrera en typad klientklass:

services.AddHttpClient<GitHubService>();

Den typspecifika klienten är registrerad som övergående med DI. I föregående kod AddHttpClient registreras GitHubService som en tillfällig tjänst. Den här registreringen använder en fabriksmetod för att:

  1. Skapa en instans av HttpClient.
  2. Skapa en instans av GitHubServiceoch skicka in instansen av HttpClient till konstruktorn.

Den inskrivna klienten kan matas in och användas direkt:

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

Konfigurationen för en typad klient kan anges under registreringen i Startup.ConfigureServices istället för i den typade klientens konstruktor.

services.AddHttpClient<RepoService>(c =>
{
    c.BaseAddress = new Uri("https://api.github.com/");
    c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
    c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});

HttpClient kan kapslas in i en typad klient. I stället för att exponera den som en egenskap definierar du en metod som anropar instansen HttpClient internt:

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

I föregående kod HttpClient lagras den i ett privat fält. Åtkomst till HttpClient sker via den offentliga metoden GetRepos.

Genererade klienter

IHttpClientFactory kan användas i kombination med bibliotek från tredje part, till exempel Refit. Refit är ett REST bibliotek för .NET. Den konverterar REST API:er till livegränssnitt. En implementering av gränssnittet genereras dynamiskt av RestService, med hjälp av HttpClient för att göra externa HTTP-anrop.

Ett gränssnitt och ett svar definieras för att representera det externa API:et och dess svar:

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

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

En typad klient kan läggas till med hjälp av Refit för att generera implementeringen.

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

Det definierade gränssnittet kan användas vid behov, med implementeringen som tillhandahålls av DI och Refit:

[ApiController]
public class ValuesController : ControllerBase
{
    private readonly IHelloClient _client;

    public ValuesController(IHelloClient client)
    {
        _client = client;
    }

    [HttpGet("/")]
    public async Task<ActionResult<Reply>> Index()
    {
        return await _client.GetMessageAsync();
    }
}

Göra POST-, PUT- och DELETE-begäranden

I föregående exempel använder alla HTTP-begäranden GET HTTP-verbet. HttpClient stöder även andra HTTP-verb, inklusive:

  • POST
  • PUT
  • DELETE
  • PATCH

En fullständig lista över HTTP-verb som stöds finns i HttpMethod.

I följande exempel visas hur du gör en HTTP POST-begäran:

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

I föregående kod, CreateItemAsync metoden:

  • Serialiserar parametern TodoItem till JSON med .System.Text.Json Detta använder en instans av JsonSerializerOptions för att konfigurera serialiseringsprocessen.
  • Skapar en instans av StringContent för att paketera den serialiserade JSON:en för att skicka i HTTP-förfrågans kropp.
  • Anropar PostAsync för att skicka JSON-innehållet till den angivna URL:en. Det här är en relativ URL som läggs till i HttpClient.BaseAddress.
  • Anrop EnsureSuccessStatusCode för att utlösa ett undantag om svarsstatuskoden inte indikerar att åtgärden lyckades.

HttpClient stöder även andra typer av innehåll. Till exempel MultipartContent och StreamContent. En fullständig lista över innehåll som stöds finns i HttpContent.

I följande exempel visas en HTTP PUT-begäran:

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

Föregående kod liknar postexemplet. Metoden SaveItemAsync anropar PutAsync i stället för PostAsync.

I följande exempel visas en HTTP DELETE-begäran:

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

    httpResponse.EnsureSuccessStatusCode();
}

I föregående kod DeleteItemAsync anropar DeleteAsyncmetoden . Eftersom HTTP DELETE-begäranden vanligtvis inte innehåller någon brödtext tillhandahåller DeleteAsync metoden inte någon överlagring som accepterar en instans av HttpContent.

Mer information om hur du använder olika HTTP-verb med HttpClientfinns i HttpClient.

Mellanprogram för utgående begäran

HttpClient har konceptet att delegera hanterare som kan länkas samman för utgående HTTP-begäranden. IHttpClientFactory:

  • Förenklar definitionen av hanterare som ska användas för varje namngiven klient.
  • Understödjer registrering och kedjning av flera handlers för att bygga en middleware-pipeline för utgående begäranden. Var och en av dessa hanterare kan utföra arbete före och efter den utgående begäran. Det här mönstret:
    • Liknar pipelinen för inkommande mellanprogram i ASP.NET Core.
    • Tillhandahåller en mekanism för att hantera övergripande problem kring HTTP-begäranden, till exempel:
      • caching
      • error handling
      • serialization
      • logging

Så här skapar du en delegeringshanterare:

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

Den tidigare koden kontrollerar om X-API-KEY header finns i begäran. Om X-API-KEY saknas BadRequest returneras.

Mer än en hanterare kan läggas till i konfigurationen för en HttpClient med 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.

I föregående kod är den ValidateHeaderHandler registrerad med DI. När AddHttpMessageHandler har registrerats kan du anropa det och skicka in typen för hanteraren.

Flera hanterare kan registreras i den ordning de ska köras. Varje hanterare omsluter nästa hanterare tills den slutgiltiga HttpClientHandler kör begäran:

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

Använd DI i mellanmjukvara för utgående förfrågningar

När IHttpClientFactory skapar en ny delegeringshanterare använder den DI för att uppfylla konstruktorparametrarna för hanteraren. IHttpClientFactory skapar ett separat DI-omfång för varje hanterare, vilket kan leda till överraskande beteende när en hanterare använder en begränsad tjänst.

Tänk till exempel på följande gränssnitt och dess implementering, som representerar en uppgift som en åtgärd med en identifierare, OperationId:

public interface IOperationScoped 
{
    string OperationId { get; }
}

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

Som namnet antyder IOperationScoped registreras med DI med en begränsad livslängd:

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

Följande delegeringshanterare konsumerar och använder IOperationScoped för att ange X-OPERATION-ID huvud för den utgående begäran.

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

I HttpRequestsSample nedladdningsområdet, navigera till /Operation och ladda om sidan. Värdet för omfånget för begäran ändras för varje begäran, men värdet för hanterarens omfång ändras bara var femte sekund.

Hanterare kan vara beroende av tjänster i alla omfång. Tjänster som hanterarna är beroende av tas bort när hanteraren tas bort.

Använd någon av följande metoder för att dela status per begäran med meddelandehanterare:

Använda Polly-baserade hanterare

IHttpClientFactory integreras med biblioteket Polly från tredje part. Polly är ett omfattande återhämtnings- och tillfälligt bibliotek för felhantering för .NET. Det gör att utvecklare kan uttrycka principer som Retry, Circuit Breaker, Timeout, Bulkhead Isolation och Fallback på ett flytande och trådsäkert sätt.

Tilläggsmetoder tillhandahålls för att möjliggöra användning av Polly-policies med konfigurerade HttpClient instanser. Polly-tilläggen har stöd för att lägga till Polly-baserade hanterare till klienter. Polly kräver NuGet-paketet Microsoft.Extensions.Http.Polly .

Hantera tillfälliga fel

Fel uppstår vanligtvis när externa HTTP-anrop är tillfälliga. AddTransientHttpErrorPolicy tillåter att en princip definieras för att hantera tillfälliga fel. Principer som konfigurerats med AddTransientHttpErrorPolicy hanterar följande svar:

AddTransientHttpErrorPolicy ger åtkomst till ett PolicyBuilder objekt som har konfigurerats för att hantera fel som representerar ett möjligt tillfälligt fel:

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

    // Remaining code deleted for brevity.

I föregående kod definieras en WaitAndRetryAsync princip. Misslyckade begäranden görs på nytt upp till tre gånger med en fördröjning på 600 ms mellan försöken.

Välj principer dynamiskt

Tilläggsmetoder tillhandahålls för att lägga till Polly-baserade hanterare, AddPolicyHandlertill exempel . Följande AddPolicyHandler överlagring inspekterar begäran för att avgöra vilken policy som ska tillämpas.

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

Om den utgående begäran i föregående kod är en HTTP GET tillämpas en tidsgräns på 10 sekunder. För andra HTTP-metoder används en tidsgräns på 30 sekunder.

Lägga till flera Polly-hanterare

Det är vanligt att kapsla Polly-principer:

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

I föregående exempel:

  • Två hanterare läggs till.
  • Den första hanteraren använder AddTransientHttpErrorPolicy för att lägga till en återförsöksprincip. Misslyckade begäranden görs på nytt upp till tre gånger.
  • Det andra AddTransientHttpErrorPolicy anropet lägger till en kretsbrytarstrategi. Ytterligare externa begäranden blockeras i 30 sekunder om 5 misslyckade försök görs sekventiellt. Kretsbrytarpolicys är tillståndsbaserade. Alla anrop via den här klienten delar samma kretstillstånd.

Lägga till principer från Polly-registret

En metod för att hantera principer som används regelbundet är att definiera dem en gång och registrera dem med en PolicyRegistry.

I följande kod:

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.

Mer information om IHttpClientFactory och Polly-integreringar finns i Polly-wikin.

HttpClient och livslängdshantering

En ny HttpClient instans returneras varje gång CreateClient anropas på IHttpClientFactory. En HttpMessageHandler skapas för varje namngiven klient. Fabriken hanterar livslängden på HttpMessageHandler instanserna.

IHttpClientFactory poolar de HttpMessageHandler instanser som skapats av fabriken för att minska resursförbrukningen. En HttpMessageHandler instans kan återanvändas från poolen när du skapar en ny HttpClient instans om dess livslängd inte har upphört att gälla.

Poolning av hanterare är önskvärt eftersom varje hanterare vanligtvis hanterar sina egna underliggande HTTP-anslutningar. Om du skapar fler hanterare än nödvändigt kan det leda till anslutningsfördröjningar. Vissa hanterare håller också anslutningarna öppna på obestämd tid, vilket kan hindra hanteraren från att reagera på DNS-ändringar (Domännamnssystem).

Standardhanterarlivslängden är två minuter. Standardvärdet kan åsidosättas per namngiven klient:

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

    // Remaining code deleted for brevity.

HttpClient instanser kan vanligtvis behandlas som .NET-objekt som inte kräver bortskaffande. Att kassera avbryter utgående begäranden och garanterar att den angivna HttpClient instansen inte kan användas efter att Dispose har kallats. IHttpClientFactory spårar och avyttrar resurser som används av HttpClient instanser.

Att hålla en enskild HttpClient instans vid liv under en längre tid är ett vanligt mönster som används före starten av IHttpClientFactory. Det här mönstret blir onödigt när du har migrerat till IHttpClientFactory.

Alternativ till IHttpClientFactory

Om du använder IHttpClientFactory i en DI-aktiverad app undviker du:

  • Problem med resursöverbelastning genom att poola HttpMessageHandler instanser.
  • Åtgärda inaktuella DNS-problem genom att cykla HttpMessageHandler instanser med jämna mellanrum.

Det finns alternativa sätt att lösa de föregående problemen med hjälp av en långlivad SocketsHttpHandler instans.

  • Skapa en instans av SocketsHttpHandler när appen startar och använd den under appens livslängd.
  • Konfigurera PooledConnectionLifetime till ett lämpligt värde baserat på DNS-uppdateringstider.
  • Skapa HttpClient instanser med hjälp av new HttpClient(handler, disposeHandler: false) efter behov.

De föregående metoderna löser de resurshanteringsproblem som IHttpClientFactory löser på ett liknande sätt.

  • Den SocketsHttpHandler delar anslutningar mellan HttpClient instanser. Denna delning förhindrar uttömning av socketar.
  • SocketsHttpHandler cyklar anslutningar enligt PooledConnectionLifetime för att undvika de inaktuella DNS-problemen.

Cookies

Poolinstanserna HttpMessageHandler leder till att CookieContainer objekt delas. Oväntad CookieContainer objektdelning resulterar ofta i felaktig kod. För appar som kräver cookies bör du överväga något av följande:

  • Inaktivera automatisk cookie hantering
  • Undvika IHttpClientFactory

Anropa ConfigurePrimaryHttpMessageHandler för att inaktivera automatisk cookie hantering:

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

Logging

Klienter som skapats via IHttpClientFactory registrerar loggmeddelanden för alla förfrågningar. Aktivera lämplig informationsnivå i loggningskonfigurationen för att se standardloggmeddelandena. Ytterligare loggning, till exempel loggning av begärandehuvuden, ingår endast på spårningsnivå.

Loggkategorin som används för varje klient innehåller namnet på klienten. En klient med namnet MyNamedClient loggar till exempel meddelanden med kategorin "System.Net.Http.HttpClient. MyNamedClient. LogicalHandler". Meddelanden som är suffixerade med LogicalHandler sker utanför pipelinen för begärhanteraren. På begäran loggas meddelanden innan andra hanterare i pipelinen har bearbetat den. I svaret loggas meddelanden efter att andra pipelinehanterare har tagit emot svaret.

Loggning sker också i pipelinen för begärandehanteraren. I exemplet MyNamedClient loggas dessa meddelanden med loggkategorin "System.Net.Http.HttpClient. MyNamedClient. ClientHandler". För denna begäran sker detta när alla andra hanterare har körts och omedelbart innan begäran skickas. I svaret innehåller den här loggningen tillståndet för svaret innan det skickas tillbaka via hanteringspipelinen.

Om du aktiverar loggning utanför och inuti pipelinen kan du kontrollera de ändringar som gjorts av de andra pipelinehantarna. Detta kan inkludera ändringar i begärandehuvuden eller i svarsstatuskoden.

Att inkludera namnet på klienten i loggkategorin möjliggör loggfiltrering för specifika namngivna klienter.

Konfigurera HttpMessageHandler

Det kan vara nödvändigt att styra konfigurationen av det inre HttpMessageHandler som används av en klient.

En IHttpClientBuilder returneras när du lägger till namngivna eller inskrivna klienter. Tilläggsmetoden ConfigurePrimaryHttpMessageHandler kan användas för att definiera ett ombud. Ombudet används för att skapa och konfigurera den primära HttpMessageHandler som används av klienten:

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

    // Remaining code deleted for brevity.

Använda IHttpClientFactory i en konsolapp

Lägg till följande paketreferenser i projektet i en konsolapp:

I följande exempel:

  • IHttpClientFactory är registrerad i den generiska värdens tjänstcontainer.
  • MyService skapar en klientfabriksinstans från tjänsten, som används för att skapa en HttpClient. HttpClient används för att hämta en webbsida.
  • Main skapar ett omfång för att köra tjänstens GetPage metod och skriva de första 500 tecknen i webbsidans innehåll till konsolen.
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}";
            }
        }
    }
}

Mellanprogram för rubrikspridning

Rubrikspridning är ett ASP.NET Core-mellanprogram för att sprida HTTP-huvuden från den inkommande begäran till utgående HTTP-klientbegäranden. Så här använder du sidhuvudspridning:

  • Referera till paketet Microsoft.AspNetCore.HeaderPropagation .

  • Konfigurera mellanprogrammet och HttpClient i 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();
        });
    }
    
  • Klienten innehåller de konfigurerade huvudena för utgående begäranden:

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

Ytterligare resurser

Av Glenn Condron, Ryan Nowak och Steve Gordon

En IHttpClientFactory kan registreras och användas för att konfigurera och skapa HttpClient instanser i en app. Den erbjuder följande fördelar:

  • Tillhandahåller en central plats för att namnge och konfigurera logiska HttpClient instanser. Till exempel kan en github-klient registreras och konfigureras för åtkomst till GitHub. En standardklient kan registreras för andra ändamål.
  • Kodifierar begreppet utgående mellanprogram via delegering av hanterare i HttpClient och tillhandahåller tillägg för Polly-baserade mellanprogram för att dra nytta av det.
  • Hanterar pooler och livslängd för underliggande HttpClientMessageHandler instanser för att undvika vanliga DNS-problem som uppstår vid manuell hantering av HttpClient livslängder.
  • Lägger till en konfigurerbar loggningsupplevelse (via ILogger) för alla begäranden som skickas via klienter som skapats av fabriken.

Visa eller ladda ned exempelkod (hur du laddar ned)

Prerequisites

Projekt som riktar sig till .NET Framework kräver installation av NuGet-paketet Microsoft.Extensions.Http . Projekt som riktar in sig på .NET Core och refererar till Microsoft.AspNetCore.App metapaket innehållerMicrosoft.Extensions.Http redan paketet.

Konsumtionsmönster

Det finns flera sätt IHttpClientFactory att använda i en app:

Ingen av dem är helt överlägsen en annan. Den bästa metoden beror på appens begränsningar.

Grundläggande användning

IHttpClientFactory kan registreras genom att anropa tilläggsmetoden AddHttpClientIServiceCollection inom Startup.ConfigureServices-metoden.

services.AddHttpClient();

När kod har registrerats kan den acceptera en IHttpClientFactory var som helst där tjänster kan matas in med beroendeinjektion (DI). IHttpClientFactory Kan användas för att skapa en HttpClient instans:

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

Att använda IHttpClientFactory på det här sättet är ett bra sätt att omstrukturera en befintlig app. Det har ingen inverkan på hur HttpClient används. På platser där HttpClient instanser skapas för närvarande ersätter du dessa förekomster med ett anrop till CreateClient.

Namngivna klienter

Om en app kräver många distinkta användningsområden för HttpClient, var och en med en annan konfiguration, är ett alternativ att använda namngivna klienter. Konfiguration för ett namngivet HttpClient kan anges under registreringen i 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");
});

I föregående kod AddHttpClient anropas och anger namnet github. Den här klienten har någon standardkonfiguration – nämligen basadressen och två huvuden som krävs för att arbeta med GitHub-API:et.

Varje gång CreateClient anropas skapas en ny instans av HttpClient och konfigurationsåtgärden anropas.

Om du vill använda en namngiven klient kan en strängparameter skickas till CreateClient. Ange namnet på den klient som ska skapas:

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

I föregående kod behöver begäran inte ange ett värdnamn. Den kan bara skicka sökvägen eftersom basadressen som konfigurerats för klienten används.

Inskrivna klienter

Inskrivna klienter:

  • Ge samma funktioner som namngivna klienter utan att behöva använda strängar som nycklar.
  • Ger IntelliSense och kompilatorhjälp vid användning av klienter.
  • Ange en enda plats för att konfigurera och interagera med en viss HttpClient. Till exempel kan en enskild typad klient användas för en enskild serverdelsslutpunkt och kapsla in all logik som hanterar den slutpunkten.
  • Arbeta med DI och kan matas in där det behövs i din app.

En typad klient accepterar en HttpClient parameter i konstruktorn:

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

I föregående kod flyttas konfigurationen till den inskrivna klienten. Objektet HttpClient exponeras som en offentlig egenskap. Det är möjligt att definiera API-specifika metoder som exponerar HttpClient funktioner. Metoden GetAspNetDocsIssues kapslar in den kod som behövs för att fråga efter och parsa de senaste öppna problemen från en GitHub-lagringsplats.

Om du vill registrera en typad klient kan den generiska AddHttpClient tilläggsmetoden användas i Startup.ConfigureServicesoch ange den typerade klientklassen:

services.AddHttpClient<GitHubService>();

Den typspecifika klienten är registrerad som övergående med DI. Den inskrivna klienten kan matas in och användas direkt:

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

Om så önskas kan konfigurationen för en typad klient anges under registreringen i , i Startup.ConfigureServicesstället för i den typerade klientens konstruktor:

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

Det är möjligt att helt kapsla HttpClient in inom en typad klient. I stället för att exponera den som en egenskap kan offentliga metoder tillhandahållas som anropar HttpClient-instansen internt.

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

I föregående kod lagras den HttpClient som ett privat fält. All åtkomst för att göra externa anrop går via GetRepos metoden.

Genererade klienter

IHttpClientFactory kan användas i kombination med andra bibliotek från tredje part, till exempel Refit. Refit är ett REST bibliotek för .NET. Den konverterar REST API:er till livegränssnitt. En implementering av gränssnittet genereras dynamiskt av RestService, med hjälp av HttpClient för att göra externa HTTP-anrop.

Ett gränssnitt och ett svar definieras för att representera det externa API:et och dess svar:

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

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

En typad klient kan läggas till med hjälp av Refit för att generera implementeringen.

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

Det definierade gränssnittet kan användas vid behov, med implementeringen som tillhandahålls av DI och Refit:

[ApiController]
public class ValuesController : ControllerBase
{
    private readonly IHelloClient _client;

    public ValuesController(IHelloClient client)
    {
        _client = client;
    }

    [HttpGet("/")]
    public async Task<ActionResult<Reply>> Index()
    {
        return await _client.GetMessageAsync();
    }
}

Mellanprogram för utgående begäran

HttpClient har redan konceptet att delegera hanterare som kan länkas samman för utgående HTTP-begäranden. Gör IHttpClientFactory det enkelt att definiera vilka hanterare som ska tillämpas för varje namngiven klient. Den stödjer registrering och kedjning av flera hanterare för att bygga en mellanskiktspipeline för utgående förfrågningar. Var och en av dessa hanterare kan utföra arbete före och efter den utgående begäran. Det här mönstret liknar pipelinen för inkommande mellanprogram i ASP.NET Core. Mönstret ger en mekanism för att hantera övergripande problem kring HTTP-begäranden, inklusive cachelagring, felhantering, serialisering och loggning.

Om du vill skapa en hanterare definierar du en klass som härleds från DelegatingHandler. Åsidosätt SendAsync metoden för att köra kod innan du skickar begäran till nästa hanterare i pipelinen:

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

Föregående kod definierar en grundläggande hanterare. Den kontrollerar om ett X-API-KEY huvud har inkluderats i begäran. Om rubriken saknas kan den undvika HTTP-anropet och returnera ett lämpligt svar.

Under registreringen kan en eller flera hanterare läggas till i konfigurationen för en HttpClient. Den här uppgiften utförs via tilläggsmetoder på 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>();

I föregående kod är den ValidateHeaderHandler registrerad med DI. Hanteraren måste vara registrerad i DI som en tillfällig tjänst, aldrig som en scoped-tjänst. Om hanteraren är registrerad som en begränsad tjänst och alla tjänster som hanteraren är beroende av är disponibla:

  • Hanterarens tjänster kan tas bort innan hanteraren hamnar utanför omfånget.
  • De borttagna hanterartjänsterna gör att hanteraren misslyckas.

När registreringen är klar kan AddHttpMessageHandler anropas och hanteringstypen skickas in.

Flera hanterare kan registreras i den ordning de ska köras. Varje hanterare omsluter nästa hanterare tills den slutgiltiga HttpClientHandler kör begäran:

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

Använd någon av följande metoder för att dela status per begäran med meddelandehanterare:

  • Skicka data till hanteraren med hjälp av HttpRequestMessage.Properties.
  • Använd IHttpContextAccessor för att komma åt den aktuella begäran.
  • Skapa ett anpassat AsyncLocal lagringsobjekt för att skicka data.

Använda Polly-baserade hanterare

IHttpClientFactory integreras med ett populärt bibliotek från tredje part med namnet Polly. Polly är ett omfattande återhämtnings- och tillfälligt bibliotek för felhantering för .NET. Det gör att utvecklare kan uttrycka principer som Retry, Circuit Breaker, Timeout, Bulkhead Isolation och Fallback på ett flytande och trådsäkert sätt.

Tilläggsmetoder tillhandahålls för att möjliggöra användning av Polly-policies med konfigurerade HttpClient instanser. Polly-tilläggen:

Hantera tillfälliga fel

De vanligaste felen uppstår när externa HTTP-anrop är tillfälliga. En praktisk tilläggsmetod som heter AddTransientHttpErrorPolicy ingår som gör att en princip kan definieras för att hantera tillfälliga fel. Principer som konfigurerats med den här tilläggsmetoden hanterar HttpRequestException, HTTP 5xx-svar och HTTP 408-svar.

Tillägget AddTransientHttpErrorPolicy kan användas i Startup.ConfigureServices. Tillägget ger åtkomst till ett PolicyBuilder objekt som har konfigurerats för att hantera fel som representerar ett möjligt tillfälligt fel:

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

I föregående kod definieras en WaitAndRetryAsync princip. Misslyckade begäranden görs på nytt upp till tre gånger med en fördröjning på 600 ms mellan försöken.

Välj principer dynamiskt

Det finns ytterligare tilläggsmetoder som kan användas för att lägga till Polly-baserade hanterare. Ett sådant tillägg är AddPolicyHandler, som har flera överlagringar. Med en överlagring kan begäran inspekteras när du definierar vilken policy som ska tillämpas:

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

Om den utgående begäran i föregående kod är en HTTP GET tillämpas en tidsgräns på 10 sekunder. För andra HTTP-metoder används en tidsgräns på 30 sekunder.

Lägga till flera Polly-hanterare

Det är vanligt att kapsla Polly-principer för att ge förbättrade funktioner:

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

I föregående exempel läggs två hanterare till. Den första använder AddTransientHttpErrorPolicy tillägget för att lägga till en återförsöksprincip. Misslyckade begäranden görs på nytt upp till tre gånger. Det andra anropet till AddTransientHttpErrorPolicy lägger till en kretsbrytarprincip. Ytterligare externa begäranden blockeras i 30 sekunder om fem misslyckade försök görs sekventiellt. Kretsbrytarpolicys är tillståndsbaserade. Alla anrop via den här klienten delar samma kretstillstånd.

Lägga till principer från Polly-registret

En metod för att hantera principer som används regelbundet är att definiera dem en gång och registrera dem med en PolicyRegistry. En tilläggsmetod tillhandahålls som gör att en hanterare kan läggas till med hjälp av en princip från registret:

var registry = services.AddPolicyRegistry();

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

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

I föregående kod registreras två principer när PolicyRegistry läggs till i ServiceCollection. Om du vill använda en princip från registret AddPolicyHandlerFromRegistry används metoden och skickar namnet på principen som ska tillämpas.

Mer information om IHttpClientFactory och Polly-integreringar finns på Polly-wikin.

HttpClient och livslängdshantering

En ny HttpClient instans returneras varje gång CreateClient anropas på IHttpClientFactory. Det finns en HttpMessageHandler per namngiven klient. Fabriken hanterar livslängden på HttpMessageHandler instanserna.

IHttpClientFactory poolar de HttpMessageHandler instanser som skapats av fabriken för att minska resursförbrukningen. En HttpMessageHandler instans kan återanvändas från poolen när du skapar en ny HttpClient instans om dess livslängd inte har upphört att gälla.

Poolning av hanterare är önskvärt eftersom varje hanterare vanligtvis hanterar sina egna underliggande HTTP-anslutningar. Om du skapar fler hanterare än nödvändigt kan det leda till anslutningsfördröjningar. Vissa hanterare håller även anslutningarna öppna på obestämd tid, vilket kan hindra hanteraren från att reagera på DNS-ändringar.

Standardhanterarlivslängden är två minuter. Standardvärdet kan åsidosättas per namngiven klient. Om du vill åsidosätta det, anropar du SetHandlerLifetime på den IHttpClientBuilder som returneras när klienten skapas.

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

Det krävs inte att klienten bortskaffas. Att kassera avbryter utgående begäranden och garanterar att den angivna HttpClient instansen inte kan användas efter att Dispose har kallats. IHttpClientFactory spårar och avyttrar resurser som används av HttpClient instanser. Instanserna HttpClient kan vanligtvis behandlas som .NET-objekt som inte kräver bortskaffande.

Att hålla en enskild HttpClient instans vid liv under en längre tid är ett vanligt mönster som används före starten av IHttpClientFactory. Det här mönstret blir onödigt när du har migrerat till IHttpClientFactory.

Alternativ till IHttpClientFactory

Om du använder IHttpClientFactory i en DI-aktiverad app undviker du:

  • Problem med resursöverbelastning genom att poola HttpMessageHandler instanser.
  • Åtgärda inaktuella DNS-problem genom att cykla HttpMessageHandler instanser med jämna mellanrum.

Det finns alternativa sätt att lösa de föregående problemen med hjälp av en långlivad SocketsHttpHandler instans.

  • Skapa en instans av SocketsHttpHandler när appen startar och använd den under appens livslängd.
  • Konfigurera PooledConnectionLifetime till ett lämpligt värde baserat på DNS-uppdateringstider.
  • Skapa HttpClient instanser med hjälp av new HttpClient(handler, disposeHandler: false) efter behov.

De föregående metoderna löser de resurshanteringsproblem som IHttpClientFactory löser på ett liknande sätt.

  • Den SocketsHttpHandler delar anslutningar mellan HttpClient instanser. Denna delning förhindrar uttömning av socketar.
  • SocketsHttpHandler cyklar anslutningar enligt PooledConnectionLifetime för att undvika de inaktuella DNS-problemen.

Cookies

Poolinstanserna HttpMessageHandler leder till att CookieContainer objekt delas. Oväntad CookieContainer objektdelning resulterar ofta i felaktig kod. För appar som kräver cookies bör du överväga något av följande:

  • Inaktivera automatisk cookie hantering
  • Undvika IHttpClientFactory

Anropa ConfigurePrimaryHttpMessageHandler för att inaktivera automatisk cookie hantering:

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

Logging

Klienter som skapats via IHttpClientFactory registrerar loggmeddelanden för alla förfrågningar. Aktivera lämplig informationsnivå i loggningskonfigurationen för att se standardloggmeddelandena. Ytterligare loggning, till exempel loggning av begärandehuvuden, ingår endast på spårningsnivå.

Loggkategorin som används för varje klient innehåller namnet på klienten. En klient med namnet MyNamedClient loggar till exempel meddelanden med kategorin System.Net.Http.HttpClient.MyNamedClient.LogicalHandler. Meddelanden som är suffixerade med LogicalHandler sker utanför pipelinen för begärhanteraren. På begäran loggas meddelanden innan andra hanterare i pipelinen har bearbetat den. I svaret loggas meddelanden efter att andra pipelinehanterare har tagit emot svaret.

Loggning sker också i pipelinen för begärandehanteraren. I exemplet MyNamedClient loggas dessa meddelanden mot loggkategorin System.Net.Http.HttpClient.MyNamedClient.ClientHandler. För begäran sker detta när alla andra hanterare har körts och omedelbart innan begäran skickas ut i nätverket. I svaret innehåller den här loggningen tillståndet för svaret innan det skickas tillbaka via hanteringspipelinen.

Om du aktiverar loggning utanför och inuti pipelinen kan du kontrollera de ändringar som gjorts av de andra pipelinehantarna. Detta kan inkludera ändringar i begärandehuvuden, till exempel eller till svarsstatuskoden.

Att inkludera namnet på klienten i loggkategorin möjliggör loggfiltrering för specifika namngivna klienter där det behövs.

Konfigurera HttpMessageHandler

Det kan vara nödvändigt att styra konfigurationen av det inre HttpMessageHandler som används av en klient.

En IHttpClientBuilder returneras när du lägger till namngivna eller inskrivna klienter. Tilläggsmetoden ConfigurePrimaryHttpMessageHandler kan användas för att definiera ett ombud. Ombudet används för att skapa och konfigurera den primära HttpMessageHandler som används av klienten:

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

Använda IHttpClientFactory i en konsolapp

Lägg till följande paketreferenser i projektet i en konsolapp:

I följande exempel:

  • IHttpClientFactory är registrerad i den generiska värdens tjänstcontainer.
  • MyService skapar en klientfabriksinstans från tjänsten, som används för att skapa en HttpClient. HttpClient används för att hämta en webbsida.
  • Tjänstens GetPage metod körs för att skriva de första 500 tecknen i webbsidans innehåll till konsolen. Mer information om att anropa tjänster från Program.Mainfinns i Beroendeinmatning i ASP.NET Core.
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
class Program
{
    static async Task<int> Main(string[] args)
    {
        var builder = new HostBuilder()
            .ConfigureServices((hostContext, services) =>
            {
                services.AddHttpClient();
                services.AddTransient<IMyService, MyService>();
            }).UseConsoleLifetime();

        var host = builder.Build();

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

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

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

        return 0;
    }

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

    public class MyService : IMyService
    {
        private readonly IHttpClientFactory _clientFactory;

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

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

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

Mellanprogram för rubrikspridning

Rubrikpropagering är en community-stödd mellanprogramvara för att överföra HTTP-huvuden från den inkommande begäran till utgående HTTP-klientförfrågningar. Så här använder du sidhuvudspridning:

  • Referera till den community-port som stöds för paketet HeaderPropagation. ASP.NET Core 3.1 eller senare stöder Microsoft.AspNetCore.HeaderPropagation.

  • Konfigurera mellanprogrammet och HttpClient i 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();
    }
    
  • Klienten innehåller de konfigurerade huvudena för utgående begäranden:

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

Ytterligare resurser