Wysyłaj żądania HTTP przy użyciu elementu IHttpClientFactory w ASP.NET Core

Przez Kirk Larkin, Steve Gordon, Glenn Condron i Ryan Nowak.

Element IHttpClientFactory można zarejestrować i użyć do konfigurowania i tworzenia HttpClient wystąpień w aplikacji. IHttpClientFactory oferuje następujące korzyści:

  • Zapewnia centralną lokalizację nazewnictwa i konfigurowania wystąpień logicznych HttpClient. Na przykład klient o nazwie github może zostać zarejestrowany i skonfigurowany do uzyskiwania dostępu do usługi GitHub. W celu uzyskania dostępu ogólnego można zarejestrować domyślnego klienta.
  • Koduje koncepcję wychodzącego oprogramowania pośredniczącego za pośrednictwem delegowania programów obsługi w programie HttpClient. Udostępnia rozszerzenia oprogramowania pośredniczącego opartego na usłudze Polly w celu korzystania z delegowania programów obsługi w programie HttpClient.
  • Zarządza buforowaniem i okresem istnienia wystąpień bazowych HttpClientMessageHandler . Automatyczne zarządzanie pozwala uniknąć typowych problemów z systemem DNS (system nazw domen), które występują podczas ręcznego zarządzania HttpClient okresami istnienia.
  • Dodaje konfigurowalne środowisko rejestrowania (za pośrednictwem ILogger) dla wszystkich żądań wysyłanych za pośrednictwem klientów utworzonych przez fabrykę.

Przykładowy kod w tej wersji tematu używa System.Text.Json do deserializacji JSzawartości ON zwróconej w odpowiedziach HTTP. W przypadku przykładów korzystających z elementów Json.NET i ReadAsAsync<T>użyj selektora wersji, aby wybrać wersję 2.x tego tematu.

Konsumpcji

W aplikacji można użyć kilku sposobów IHttpClientFactory :

Najlepsze podejście zależy od wymagań aplikacji.

Podstawowy sposób użycia

Zarejestruj się IHttpClientFactory , wywołując polecenie AddHttpClient w pliku Program.cs:

var builder = WebApplication.CreateBuilder(args);

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

Żądanie IHttpClientFactory może być wymagane przy użyciu wstrzykiwania zależności (DI). Poniższy kod używa IHttpClientFactory metody do utworzenia HttpClient wystąpienia:

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

Użycie IHttpClientFactory metody podobnej w poprzednim przykładzie jest dobrym sposobem refaktoryzacji istniejącej aplikacji. Nie ma to wpływu na sposób HttpClient użycia. W miejscach, w których HttpClient wystąpienia są tworzone w istniejącej aplikacji, zastąp te wystąpienia wywołaniami metody CreateClient.

Nazwani klienci

Nazwani klienci są dobrym wyborem w następujących przypadkach:

  • Aplikacja wymaga wielu różnych zastosowań programu HttpClient.
  • Wiele HttpClientz nich ma inną konfigurację.

Określ konfigurację dla nazwy HttpClient podczas rejestracji w pliku 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");
});

W poprzednim kodzie klient jest skonfigurowany za pomocą polecenia :

  • Adres https://api.github.com/podstawowy .
  • Dwa nagłówki wymagane do pracy z interfejsem API usługi GitHub.

CreateClient

Za każdym razem CreateClient jest wywoływana:

  • Zostanie utworzone nowe wystąpienie programu HttpClient .
  • Akcja konfiguracji jest wywoływana.

Aby utworzyć nazwanego klienta, przekaż jego nazwę do 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);
        }
    }
}

W poprzednim kodzie żądanie nie musi określać nazwy hosta. Kod może przekazać tylko ścieżkę, ponieważ używany jest adres podstawowy skonfigurowany dla klienta.

Wpisanych klientów

Typizowane klienci:

  • Zapewnij te same możliwości co nazwani klienci bez konieczności używania ciągów jako kluczy.
  • Zapewnia funkcję IntelliSense i pomoc kompilatora podczas korzystania z klientów.
  • Podaj pojedynczą lokalizację do skonfigurowania i interakcji z określonym HttpClientelementem . Na przykład można użyć jednego typu klienta:
    • W przypadku pojedynczego punktu końcowego zaplecza.
    • Aby hermetyzować całą logikę do obsługi punktu końcowego.
  • Praca z di i może być wstrzykiwana tam, gdzie jest to wymagane w aplikacji.

Typowany klient akceptuje HttpClient parametr w konstruktorze:

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

Powyższy kod:

  • Konfiguracja jest przenoszona do typizowanego klienta.
  • Podane HttpClient wystąpienie jest przechowywane jako pole prywatne.

Metody specyficzne dla interfejsu API można utworzyć, aby uwidaczniać HttpClient funkcje. Na przykład GetAspNetCoreDocsBranches metoda hermetyzuje kod w celu pobrania gałęzi usługi GitHub witryny Docs.

Następujące wywołania AddHttpClient kodu w programie w Program.cs celu zarejestrowania wpisanej GitHubService klasy klienta:

builder.Services.AddHttpClient<GitHubService>();

Typowany klient jest zarejestrowany jako przejściowy z di. W poprzednim kodzie AddHttpClient rejestruje się GitHubService jako usługa przejściowa. Ta rejestracja używa metody fabryki do:

  1. Utwórz wystąpienie elementu HttpClient.
  2. Utwórz wystąpienie klasy , przekazując wystąpienie GitHubServicedo jego konstruktora HttpClient .

Typizowanego klienta można wstrzyknąć i używać bezpośrednio:

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

Konfigurację typizowanego klienta można również określić podczas jego rejestracji w Program.csprogramie , a nie w konstruktorze typizowanego klienta:

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

    // ...
});

Wygenerowani klienci

IHttpClientFactory można używać w połączeniu z bibliotekami innych firm, takimi jak Refit. Refit to REST biblioteka dla platformy .NET. Konwertuje REST interfejsy API na interfejsy na żywo. Wywołaj metodę AddRefitClient , aby wygenerować dynamiczną implementację interfejsu, która używa HttpClient metody do wykonywania zewnętrznych wywołań HTTP.

Interfejs niestandardowy reprezentuje zewnętrzny interfejs API:

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

Wywołaj metodę AddRefitClient w celu wygenerowania implementacji dynamicznej, a następnie wywołaj metodę ConfigureHttpClient w celu skonfigurowania bazowego HttpClientelementu :

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

Użyj di, aby uzyskać dostęp do dynamicznej implementacji programu 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)
        {
            // ...
        }
    }
}

Żądania POST, PUT i DELETE

W poprzednich przykładach wszystkie żądania HTTP używają czasownika GET HTTP. HttpClient Obsługuje również inne czasowniki HTTP, w tym:

  • POST
  • ODŁÓŻ
  • USUŃ
  • PATCH

Aby uzyskać pełną listę obsługiwanych czasowników HTTP, zobacz HttpMethod.

W poniższym przykładzie pokazano, jak utworzyć żądanie HTTP POST:

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

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

    httpResponseMessage.EnsureSuccessStatusCode();
}

W poprzednim kodzie CreateItemAsync metoda:

  • Serializuje TodoItem parametr na JSWŁ. przy użyciu polecenia System.Text.Json.
  • Tworzy wystąpienie programu StringContent , aby spakować serializowaną JSwartość ON do wysyłania w treści żądania HTTP.
  • Wywołuje metodę PostAsync wysyłania JSzawartości ON do określonego adresu URL. Jest to względny adres URL, który jest dodawany do obiektu HttpClient.BaseAddress.
  • Wywołania EnsureSuccessStatusCode w celu zgłoszenia wyjątku, jeśli kod stanu odpowiedzi nie wskazuje powodzenia.

HttpClient obsługuje również inne typy zawartości. Na przykład MultipartContent i StreamContent. Aby uzyskać pełną listę obsługiwanych zawartości, zobacz HttpContent.

W poniższym przykładzie pokazano żądanie HTTP PUT:

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

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

    httpResponseMessage.EnsureSuccessStatusCode();
}

Powyższy kod jest podobny do przykładu POST. Metoda SaveItemAsync wywołuje PutAsync metodę PostAsynczamiast .

W poniższym przykładzie pokazano żądanie HTTP DELETE:

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

    httpResponseMessage.EnsureSuccessStatusCode();
}

W poprzednim kodzie metoda wywołuje metodę DeleteItemAsyncDeleteAsync. Ponieważ żądania HTTP DELETE zwykle nie zawierają treści, DeleteAsync metoda nie udostępnia przeciążenia, które akceptuje wystąpienie HttpContentklasy .

Aby dowiedzieć się więcej na temat używania różnych czasowników HTTP w programie HttpClient, zobacz HttpClient.

Oprogramowanie pośredniczące żądań wychodzących

HttpClient ma koncepcję delegowania procedur obsługi, które mogą być połączone ze sobą dla wychodzących żądań HTTP. IHttpClientFactory:

  • Upraszcza definiowanie procedur obsługi, które mają być stosowane dla każdego nazwanego klienta.
  • Obsługuje rejestrację i łączenie wielu procedur obsługi w celu utworzenia potoku oprogramowania pośredniczącego żądań wychodzących. Każdy z tych programów obsługi może wykonać pracę przed i po żądaniu wychodzącym. Ten wzorzec:
    • Jest podobny do potoku oprogramowania pośredniczącego dla ruchu przychodzącego w programie ASP.NET Core.
    • Udostępnia mechanizm zarządzania problemami krzyżowymi dotyczącymi żądań HTTP, takich jak:
      • Buforowanie
      • obsługa błędów
      • Serializacji
      • rejestrowanie

Aby utworzyć procedurę obsługi delegowania:

  • Pochodzą z klasy DelegatingHandler.
  • Zastąpij .SendAsync Przed przekazaniem żądania do następnego programu obsługi w potoku wykonaj kod:
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);
    }
}

Powyższy kod sprawdza, czy X-API-KEY nagłówek znajduje się w żądaniu. Jeśli X-API-KEY brakuje, BadRequest zostanie zwrócona wartość .

Do konfiguracji programu można dodać więcej niż jedną procedurę obsługi za HttpClient pomocą polecenia Microsoft.Extensions.DependencyInjection.HttpClientBuilderExtensions.AddHttpMessageHandler:

builder.Services.AddTransient<ValidateHeaderHandler>();

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

W poprzednim kodzie element ValidateHeaderHandler jest zarejestrowany w di. Po zarejestrowaniu AddHttpMessageHandler można wywołać metodę , przekazując typ programu obsługi.

W kolejności wykonywania można zarejestrować wiele procedur obsługi. Każda procedura obsługi opakowuje następną procedurę obsługi do momentu, aż finale HttpClientHandler wykona żądanie:

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

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

W poprzednim kodzie SampleHandler1 uruchomi się najpierw przed SampleHandler2.

Używanie di w rozwiązaniu pośredniczącym żądań wychodzących

Podczas IHttpClientFactory tworzenia nowej procedury obsługi delegowania używa di do spełnienia parametrów konstruktora programu obsługi. IHttpClientFactorytworzy oddzielnyzakres di dla każdego programu obsługi, co może prowadzić do zaskakującego zachowania, gdy program obsługi korzysta z usługi o określonym zakresie.

Rozważmy na przykład następujący interfejs i jego implementację, która reprezentuje zadanie jako operację z identyfikatorem: OperationId

public interface IOperationScoped
{
    string OperationId { get; }
}

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

Jak sugeruje jego nazwa, IOperationScoped jest zarejestrowany w di przy użyciu okresu istnienia o określonym zakresie :

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

Następująca procedura obsługi delegowania używa polecenia i używa IOperationScoped go do ustawienia nagłówka X-OPERATION-ID dla żądania wychodzącego:

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

W pobranym pliku HttpRequestsSample przejdź do /Operation strony i odśwież stronę. Wartość zakresu żądania zmienia się dla każdego żądania, ale wartość zakresu programu obsługi zmienia się tylko co 5 sekund.

Programy obsługi mogą zależeć od usług dowolnego zakresu. Usługi, od których są zależne programy obsługi, są usuwane po usunięciu programu obsługi.

Użyj jednej z następujących metod udostępniania stanu poszczególnych żądań za pomocą programów obsługi komunikatów:

Korzystanie z procedur obsługi opartych na usłudze Polly

IHttpClientFactory program integruje się z biblioteką innej firmy Polly. Polly to kompleksowa odporność i biblioteka obsługi błędów przejściowych dla platformy .NET. Umożliwia deweloperom wyrażanie zasad, takich jak ponawianie prób, wyłącznik, przekroczenie limitu czasu, izolacja bulkhead i powrót w sposób płynny i bezpieczny wątkowo.

Dostępne są metody rozszerzenia umożliwiające korzystanie z zasad Polly ze skonfigurowanymi HttpClient wystąpieniami. Rozszerzenia Polly obsługują dodawanie programów obsługi opartych na usłudze Polly do klientów. Usługa Polly wymaga pakietu NuGet Microsoft.Extensions.Http.Polly .

Obsługa błędów przejściowych

Błędy zwykle występują, gdy zewnętrzne wywołania HTTP są przejściowe. AddTransientHttpErrorPolicy umożliwia zdefiniowanie zasad w celu obsługi błędów przejściowych. Zasady skonfigurowane z obsługą AddTransientHttpErrorPolicy następujących odpowiedzi:

AddTransientHttpErrorPolicy zapewnia dostęp do obiektu skonfigurowanego PolicyBuilder do obsługi błędów reprezentujących możliwy błąd przejściowy:

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

W poprzednim kodzie WaitAndRetryAsync zdefiniowano zasady. Żądania, które zakończyły się niepowodzeniem, są ponawiane do trzech razy z opóźnieniem 600 ms między próbami.

Dynamiczne wybieranie zasad

Metody rozszerzeń są udostępniane w celu dodania procedur obsługi opartych na usłudze Polly, na przykład AddPolicyHandler. Następujące AddPolicyHandler przeciążenie sprawdza żądanie, aby zdecydować, które zasady mają być stosowane:

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

W poprzednim kodzie, jeśli żądanie wychodzące to HTTP GET, zostanie zastosowany limit czasu 10-sekundowy. W przypadku każdej innej metody HTTP jest używany 30-sekundowy limit czasu.

Dodawanie wielu programów obsługi polly

Typowe jest zagnieżdżanie zasad polly:

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

W poprzednim przykładzie:

  • Dodano dwa programy obsługi.
  • Pierwsza procedura obsługi używa AddTransientHttpErrorPolicy metody do dodawania zasad ponawiania. Żądania, które zakończyły się niepowodzeniem, są ponawiane do trzech razy.
  • Drugie AddTransientHttpErrorPolicy wywołanie dodaje zasady wyłącznika. Dalsze żądania zewnętrzne są blokowane przez 30 sekund, jeśli 5 nieudanych prób nastąpi sekwencyjnie. Zasady wyłącznika są stanowe. Wszystkie wywołania za pośrednictwem tego klienta mają ten sam stan obwodu.

Dodawanie zasad z rejestru Polly

Podejście do zarządzania regularnie używanymi zasadami polega na zdefiniowaniu ich raz i zarejestrowaniu ich w obiekcie PolicyRegistry. Przykład:

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

Powyższy kod:

  • Dwie zasady Regular i Long, są dodawane do rejestru Polly.
  • AddPolicyHandlerFromRegistry Konfiguruje poszczególnych nazwanych klientów tak, aby używali tych zasad z rejestru Polly.

Aby uzyskać więcej informacji na IHttpClientFactory temat integracji z usługą Polly, zobacz witrynę typu wiki Polly.

Zarządzanie klientem HttpClient i okresem istnienia

Nowe HttpClient wystąpienie jest zwracane za każdym razem, gdy CreateClient jest wywoływane w obiekcie IHttpClientFactory. Element HttpMessageHandler jest tworzony na nazwanego klienta. Fabryka zarządza okresami HttpMessageHandler istnienia wystąpień.

IHttpClientFactoryHttpMessageHandler pule wystąpień utworzonych przez fabrykę w celu zmniejszenia zużycia zasobów. Wystąpienie HttpMessageHandler może być ponownie używane z puli podczas tworzenia nowego HttpClient wystąpienia, jeśli jego okres istnienia nie wygasł.

Buforowanie programów obsługi jest pożądane, ponieważ każda procedura obsługi zwykle zarządza własnymi podstawowymi połączeniami HTTP. Tworzenie większej liczby procedur obsługi niż jest to konieczne może spowodować opóźnienia połączeń. Niektóre programy obsługi utrzymują również połączenia otwarte na czas nieokreślony, co może uniemożliwić programowi obsługi reagowanie na zmiany systemu DNS (systemu nazw domen).

Domyślny okres istnienia programu obsługi to dwie minuty. Wartość domyślna może zostać zastąpiona na podstawie nazwanego klienta:

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

HttpClient wystąpienia mogą być zwykle traktowane jako obiekty platformy .NET, które nie wymagają usuwania. Usuwanie anuluje żądania wychodzące i gwarantuje, że dane HttpClient wystąpienie nie może być używane po wywołaniu metody Dispose. IHttpClientFactory śledzi i usuwa zasoby używane przez HttpClient wystąpienia.

Utrzymywanie pojedynczego HttpClient wystąpienia przy życiu przez długi czas trwania jest typowym wzorcem używanym przed utworzeniem klasy IHttpClientFactory. Ten wzorzec staje się niepotrzebny po przeprowadzeniu migracji do IHttpClientFactoryprogramu .

Alternatywy dla IHttpClientFactory

Używanie IHttpClientFactory w aplikacji z włączoną obsługą di pozwala uniknąć:

  • Problemy z wyczerpaniem zasobów przez wystąpienia puli HttpMessageHandler .
  • Nieaktualne problemy z systemem DNS przez wystąpienia rowerów HttpMessageHandler w regularnych odstępach czasu.

Istnieją alternatywne sposoby rozwiązywania powyższych problemów przy użyciu długotrwałego SocketsHttpHandler wystąpienia.

  • Utwórz wystąpienie, SocketsHttpHandler gdy aplikacja zostanie uruchomiona i użyj jej do końca życia aplikacji.
  • Skonfiguruj PooledConnectionLifetime odpowiednią wartość na podstawie czasów odświeżania DNS.
  • Utwórz HttpClient wystąpienia przy użyciu zgodnie z new HttpClient(handler, disposeHandler: false) potrzebami.

Powyższe podejścia rozwiązują problemy z zarządzaniem zasobami, które IHttpClientFactory są rozwiązywane w podobny sposób.

  • Udziały SocketsHttpHandler połączeń między HttpClient wystąpieniami. To udostępnianie uniemożliwia wyczerpanie gniazd.
  • Połączenia SocketsHttpHandler cykli zgodnie z, aby PooledConnectionLifetime uniknąć nieaktualnych problemów DNS.

Rejestrowanie

Klienci utworzeni za pośrednictwem IHttpClientFactory komunikatów dziennika rekordów dla wszystkich żądań. Włącz odpowiedni poziom informacji w konfiguracji rejestrowania, aby wyświetlić domyślne komunikaty dziennika. Dodatkowe rejestrowanie, takie jak rejestrowanie nagłówków żądań, jest uwzględniane tylko na poziomie śledzenia.

Kategoria dziennika używana dla każdego klienta zawiera nazwę klienta. Klient o nazwie MyNamedClient, na przykład, rejestruje komunikaty z kategorią "System.Net.Http.HttpClient.MyNamedClient. Logiczna procedura obsługi". Komunikaty sufiksowane za pomocą programu LogicalHandler występują poza potokiem obsługi żądań. Na żądanie komunikaty są rejestrowane, zanim wszystkie inne programy obsługi w potoku go przetworzyły. W odpowiedzi komunikaty są rejestrowane po otrzymaniu odpowiedzi przez inne programy obsługi potoku.

Rejestrowanie odbywa się również wewnątrz potoku procedury obsługi żądań. W przykładzie MyNamedClient te komunikaty są rejestrowane z kategorią dziennika "System.Net.Http.HttpClient.MyNamedClient. ClientHandler". W przypadku żądania następuje to po uruchomieniu wszystkich innych procedur obsługi i bezpośrednio przed wysłaniem żądania. W odpowiedzi to rejestrowanie zawiera stan odpowiedzi, zanim przejdzie z powrotem przez potok obsługi.

Włączenie rejestrowania na zewnątrz i wewnątrz potoku umożliwia inspekcję zmian wprowadzonych przez inne procedury obsługi potoku. Może to obejmować zmiany nagłówków żądań lub kod stanu odpowiedzi.

Uwzględnienie nazwy klienta w kategorii dziennika umożliwia filtrowanie dzienników dla określonych nazwanych klientów.

Konfigurowanie programu HttpMessageHandler

Może być konieczne kontrolowanie konfiguracji wewnętrznej HttpMessageHandler używanej przez klienta.

Element IHttpClientBuilder jest zwracany podczas dodawania nazwanych lub wpisanych klientów. Metodę ConfigurePrimaryHttpMessageHandler rozszerzenia można użyć do zdefiniowania delegata. Delegat służy do tworzenia i konfigurowania podstawowego HttpMessageHandler używanego przez tego klienta:

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

CookieS

Wystąpienia w HttpMessageHandler puli powoduje CookieContainer współużytkowanie obiektów. Nieprzewidziane CookieContainer udostępnianie obiektów często powoduje niepoprawny kod. W przypadku aplikacji, które wymagają cookies, należy wziąć pod uwagę jedną z następujących kwestii:

  • Wyłączanie automatycznej cookie obsługi
  • Unikanie IHttpClientFactory

Wywołanie w ConfigurePrimaryHttpMessageHandler celu wyłączenia automatycznej cookie obsługi:

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

Używanie elementu IHttpClientFactory w aplikacji konsolowej

W aplikacji konsolowej dodaj następujące odwołania do pakietu do projektu:

W poniższym przykładzie:

  • IHttpClientFactory i GitHubService są zarejestrowane w kontenerze usługi hosta ogólnego.
  • GitHubService żądanie z di, który z kolei żąda wystąpienia wystąpienia IHttpClientFactory.
  • GitHubService używa IHttpClientFactory metody do utworzenia HttpClientwystąpienia klasy , którego używa do pobierania gałęzi usługi GitHub w witrynie Docs.
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);

Oprogramowanie pośredniczące propagacji nagłówka

Propagacja nagłówka to oprogramowanie pośredniczące ASP.NET Core do propagowania nagłówków HTTP z żądania przychodzącego do żądań wychodzących HttpClient . Aby użyć propagacji nagłówka:

  • Zainstaluj pakiet Microsoft.AspNetCore.HeaderPropagation.

  • Skonfiguruj potok oprogramowania pośredniczącego HttpClient i w programie 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();
    
  • Wysyłaj żądania wychodzące przy użyciu skonfigurowanego HttpClient wystąpienia, które zawiera dodane nagłówki.

Dodatkowe zasoby

Przez Kirk Larkin, Steve Gordon, Glenn Condron i Ryan Nowak.

Element IHttpClientFactory można zarejestrować i użyć do konfigurowania i tworzenia HttpClient wystąpień w aplikacji. IHttpClientFactory oferuje następujące korzyści:

  • Zapewnia centralną lokalizację nazewnictwa i konfigurowania wystąpień logicznych HttpClient. Na przykład klient o nazwie github może zostać zarejestrowany i skonfigurowany do uzyskiwania dostępu do usługi GitHub. W celu uzyskania dostępu ogólnego można zarejestrować domyślnego klienta.
  • Koduje koncepcję wychodzącego oprogramowania pośredniczącego za pośrednictwem delegowania programów obsługi w programie HttpClient. Udostępnia rozszerzenia oprogramowania pośredniczącego opartego na usłudze Polly w celu korzystania z delegowania programów obsługi w programie HttpClient.
  • Zarządza buforowaniem i okresem istnienia wystąpień bazowych HttpClientMessageHandler . Automatyczne zarządzanie pozwala uniknąć typowych problemów z systemem DNS (system nazw domen), które występują podczas ręcznego zarządzania HttpClient okresami istnienia.
  • Dodaje konfigurowalne środowisko rejestrowania (za pośrednictwem ILogger) dla wszystkich żądań wysyłanych za pośrednictwem klientów utworzonych przez fabrykę.

Wyświetl lub pobierz przykładowy kod (jak pobrać).

Przykładowy kod w tej wersji tematu używa System.Text.Json do deserializacji JSzawartości ON zwróconej w odpowiedziach HTTP. W przypadku przykładów korzystających z elementów Json.NET i ReadAsAsync<T>użyj selektora wersji, aby wybrać wersję 2.x tego tematu.

Konsumpcji

W aplikacji można użyć kilku sposobów IHttpClientFactory :

Najlepsze podejście zależy od wymagań aplikacji.

Podstawowy sposób użycia

IHttpClientFactory można zarejestrować, wywołując polecenie 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.

Żądanie IHttpClientFactory może być wymagane przy użyciu wstrzykiwania zależności (DI). Poniższy kod używa IHttpClientFactory metody do utworzenia HttpClient wystąpienia:

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

Użycie IHttpClientFactory metody podobnej w poprzednim przykładzie jest dobrym sposobem refaktoryzacji istniejącej aplikacji. Nie ma to wpływu na sposób HttpClient użycia. W miejscach, w których HttpClient wystąpienia są tworzone w istniejącej aplikacji, zastąp te wystąpienia wywołaniami metody CreateClient.

Nazwani klienci

Nazwani klienci są dobrym wyborem w następujących przypadkach:

  • Aplikacja wymaga wielu różnych zastosowań programu HttpClient.
  • Wiele HttpClientz nich ma inną konfigurację.

Konfigurację nazwy HttpClient można określić podczas rejestracji w programie 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");
});

W poprzednim kodzie klient jest skonfigurowany za pomocą polecenia :

  • Adres https://api.github.com/podstawowy .
  • Dwa nagłówki wymagane do pracy z interfejsem API usługi GitHub.

CreateClient

Za każdym razem CreateClient jest wywoływana:

  • Zostanie utworzone nowe wystąpienie programu HttpClient .
  • Akcja konfiguracji jest wywoływana.

Aby utworzyć nazwanego klienta, przekaż jego nazwę do 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>();
        }
    }
}

W poprzednim kodzie żądanie nie musi określać nazwy hosta. Kod może przekazać tylko ścieżkę, ponieważ używany jest adres podstawowy skonfigurowany dla klienta.

Wpisanych klientów

Typizowane klienci:

  • Zapewnij te same możliwości co nazwani klienci bez konieczności używania ciągów jako kluczy.
  • Zapewnia funkcję IntelliSense i pomoc kompilatora podczas korzystania z klientów.
  • Podaj pojedynczą lokalizację do skonfigurowania i interakcji z określonym HttpClientelementem . Na przykład można użyć jednego typu klienta:
    • W przypadku pojedynczego punktu końcowego zaplecza.
    • Aby hermetyzować całą logikę do obsługi punktu końcowego.
  • Praca z di i może być wstrzykiwana tam, gdzie jest to wymagane w aplikacji.

Typowany klient akceptuje HttpClient parametr w konstruktorze:

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

Powyższy kod:

  • Konfiguracja jest przenoszona do typizowanego klienta.
  • Obiekt HttpClient jest uwidaczniony jako właściwość publiczna.

Metody specyficzne dla interfejsu API można utworzyć, aby uwidaczniać HttpClient funkcje. Na przykład GetAspNetDocsIssues metoda hermetyzuje kod w celu pobrania otwartych problemów.

Następujące wywołania AddHttpClient kodu w programie w Startup.ConfigureServices celu zarejestrowania wpisanej klasy klienta:

services.AddHttpClient<GitHubService>();

Typowany klient jest zarejestrowany jako przejściowy z di. W poprzednim kodzie AddHttpClient rejestruje się GitHubService jako usługa przejściowa. Ta rejestracja używa metody fabryki do:

  1. Utwórz wystąpienie elementu HttpClient.
  2. Utwórz wystąpienie klasy , przekazując wystąpienie GitHubServicedo jego konstruktora HttpClient .

Typizowanego klienta można wstrzyknąć i używać bezpośrednio:

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

Konfigurację typowego klienta można określić podczas rejestracji w Startup.ConfigureServicesprogramie, a nie w konstruktorze typizowanego klienta:

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

Element HttpClient można hermetyzować w obrębie typizowanego klienta. Zamiast ujawniać go jako właściwość, zdefiniuj metodę, która HttpClient wywołuje wystąpienie wewnętrznie:

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

W poprzednim kodzie element HttpClient jest przechowywany w polu prywatnym. Dostęp do elementu HttpClient jest metodą publiczną GetRepos .

Wygenerowani klienci

IHttpClientFactory można używać w połączeniu z bibliotekami innych firm, takimi jak Refit. Refit to REST biblioteka dla platformy .NET. Konwertuje REST interfejsy API na interfejsy na żywo. Implementacja interfejsu jest generowana dynamicznie przez RestServiceelement , za pomocą polecenia HttpClient w celu wykonywania zewnętrznych wywołań HTTP.

Interfejs i odpowiedź są zdefiniowane w celu reprezentowania zewnętrznego interfejsu API i jego odpowiedzi:

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

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

Można dodać typizowanego klienta, używając polecenia Refit w celu wygenerowania implementacji:

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

Zdefiniowany interfejs może być używany w razie potrzeby z implementacją dostarczaną przez di i 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();
    }
}

Żądania POST, PUT i DELETE

W poprzednich przykładach wszystkie żądania HTTP używają czasownika GET HTTP. HttpClient Obsługuje również inne czasowniki HTTP, w tym:

  • POST
  • ODŁÓŻ
  • USUŃ
  • PATCH

Aby uzyskać pełną listę obsługiwanych czasowników HTTP, zobacz HttpMethod.

W poniższym przykładzie pokazano, jak utworzyć żądanie HTTP POST:

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

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

    httpResponse.EnsureSuccessStatusCode();
}

W poprzednim kodzie CreateItemAsync metoda:

  • Serializuje TodoItem parametr na JSWŁ. przy użyciu polecenia System.Text.Json. Używa to wystąpienia JsonSerializerOptions klasy , aby skonfigurować proces serializacji.
  • Tworzy wystąpienie programu StringContent , aby spakować serializowaną JSwartość ON do wysyłania w treści żądania HTTP.
  • Wywołuje metodę PostAsync wysyłania JSzawartości ON do określonego adresu URL. Jest to względny adres URL, który jest dodawany do obiektu HttpClient.BaseAddress.
  • Wywołania EnsureSuccessStatusCode w celu zgłoszenia wyjątku, jeśli kod stanu odpowiedzi nie wskazuje powodzenia.

HttpClient obsługuje również inne typy zawartości. Na przykład MultipartContent i StreamContent. Aby uzyskać pełną listę obsługiwanych zawartości, zobacz HttpContent.

W poniższym przykładzie pokazano żądanie HTTP PUT:

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

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

    httpResponse.EnsureSuccessStatusCode();
}

Powyższy kod jest bardzo podobny do przykładu POST. Metoda SaveItemAsync wywołuje PutAsync metodę PostAsynczamiast .

W poniższym przykładzie pokazano żądanie HTTP DELETE:

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

    httpResponse.EnsureSuccessStatusCode();
}

W poprzednim kodzie metoda wywołuje metodę DeleteItemAsyncDeleteAsync. Ponieważ żądania HTTP DELETE zwykle nie zawierają treści, DeleteAsync metoda nie udostępnia przeciążenia, które akceptuje wystąpienie HttpContentklasy .

Aby dowiedzieć się więcej na temat używania różnych czasowników HTTP w programie HttpClient, zobacz HttpClient.

Oprogramowanie pośredniczące żądań wychodzących

HttpClient ma koncepcję delegowania procedur obsługi, które mogą być połączone ze sobą dla wychodzących żądań HTTP. IHttpClientFactory:

  • Upraszcza definiowanie procedur obsługi, które mają być stosowane dla każdego nazwanego klienta.
  • Obsługuje rejestrację i łączenie wielu procedur obsługi w celu utworzenia potoku oprogramowania pośredniczącego żądań wychodzących. Każdy z tych programów obsługi może wykonać pracę przed i po żądaniu wychodzącym. Ten wzorzec:
    • Jest podobny do potoku oprogramowania pośredniczącego dla ruchu przychodzącego w programie ASP.NET Core.
    • Udostępnia mechanizm zarządzania problemami krzyżowymi dotyczącymi żądań HTTP, takich jak:
      • Buforowanie
      • obsługa błędów
      • Serializacji
      • rejestrowanie

Aby utworzyć procedurę obsługi delegowania:

  • Pochodzą z klasy DelegatingHandler.
  • Zastąpij .SendAsync Przed przekazaniem żądania do następnego programu obsługi w potoku wykonaj kod:
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);
    }
}

Powyższy kod sprawdza, czy X-API-KEY nagłówek znajduje się w żądaniu. Jeśli X-API-KEY brakuje, BadRequest zostanie zwrócona wartość .

Do konfiguracji programu można dodać więcej niż jedną procedurę obsługi za HttpClient pomocą polecenia 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.

W poprzednim kodzie element ValidateHeaderHandler jest zarejestrowany w di. Po zarejestrowaniu AddHttpMessageHandler można wywołać metodę , przekazując typ programu obsługi.

W kolejności wykonywania można zarejestrować wiele procedur obsługi. Każda procedura obsługi opakowuje następną procedurę obsługi do momentu, aż finale HttpClientHandler wykona żądanie:

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

Używanie di w rozwiązaniu pośredniczącym żądań wychodzących

Podczas IHttpClientFactory tworzenia nowej procedury obsługi delegowania używa di do spełnienia parametrów konstruktora programu obsługi. IHttpClientFactorytworzy oddzielnyzakres di dla każdego programu obsługi, co może prowadzić do zaskakującego zachowania, gdy program obsługi korzysta z usługi o określonym zakresie.

Rozważmy na przykład następujący interfejs i jego implementację, która reprezentuje zadanie jako operację z identyfikatorem: OperationId

public interface IOperationScoped 
{
    string OperationId { get; }
}

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

Jak sugeruje jego nazwa, IOperationScoped jest zarejestrowany w di przy użyciu okresu istnienia o określonym zakresie :

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

Następująca procedura obsługi delegowania używa polecenia i używa IOperationScoped go do ustawienia nagłówka X-OPERATION-ID dla żądania wychodzącego:

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

W pobranym pliku HttpRequestsSample przejdź do /Operation strony i odśwież stronę. Wartość zakresu żądania zmienia się dla każdego żądania, ale wartość zakresu programu obsługi zmienia się tylko co 5 sekund.

Programy obsługi mogą zależeć od usług dowolnego zakresu. Usługi, od których są zależne programy obsługi, są usuwane po usunięciu programu obsługi.

Użyj jednej z następujących metod udostępniania stanu poszczególnych żądań za pomocą programów obsługi komunikatów:

Korzystanie z procedur obsługi opartych na usłudze Polly

IHttpClientFactory program integruje się z biblioteką innej firmy Polly. Polly to kompleksowa odporność i biblioteka obsługi błędów przejściowych dla platformy .NET. Umożliwia deweloperom wyrażanie zasad, takich jak ponawianie prób, wyłącznik, przekroczenie limitu czasu, izolacja bulkhead i powrót w sposób płynny i bezpieczny wątkowo.

Dostępne są metody rozszerzenia umożliwiające korzystanie z zasad Polly ze skonfigurowanymi HttpClient wystąpieniami. Rozszerzenia Polly obsługują dodawanie programów obsługi opartych na usłudze Polly do klientów. Usługa Polly wymaga pakietu NuGet Microsoft.Extensions.Http.Polly .

Obsługa błędów przejściowych

Błędy zwykle występują, gdy zewnętrzne wywołania HTTP są przejściowe. AddTransientHttpErrorPolicy umożliwia zdefiniowanie zasad w celu obsługi błędów przejściowych. Zasady skonfigurowane z obsługą AddTransientHttpErrorPolicy następujących odpowiedzi:

AddTransientHttpErrorPolicy zapewnia dostęp do obiektu skonfigurowanego PolicyBuilder do obsługi błędów reprezentujących możliwy błąd przejściowy:

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

    // Remaining code deleted for brevity.

W poprzednim kodzie WaitAndRetryAsync zdefiniowano zasady. Żądania, które zakończyły się niepowodzeniem, są ponawiane do trzech razy z opóźnieniem 600 ms między próbami.

Dynamiczne wybieranie zasad

Metody rozszerzeń są udostępniane w celu dodania procedur obsługi opartych na usłudze Polly, na przykład AddPolicyHandler. Następujące AddPolicyHandler przeciążenie sprawdza żądanie, aby zdecydować, które zasady mają być stosowane:

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

W poprzednim kodzie, jeśli żądanie wychodzące to HTTP GET, zostanie zastosowany limit czasu 10-sekundowy. W przypadku każdej innej metody HTTP jest używany 30-sekundowy limit czasu.

Dodawanie wielu programów obsługi polly

Typowe jest zagnieżdżanie zasad polly:

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

W poprzednim przykładzie:

  • Dodano dwa programy obsługi.
  • Pierwsza procedura obsługi używa AddTransientHttpErrorPolicy metody do dodawania zasad ponawiania. Żądania, które zakończyły się niepowodzeniem, są ponawiane do trzech razy.
  • Drugie AddTransientHttpErrorPolicy wywołanie dodaje zasady wyłącznika. Dalsze żądania zewnętrzne są blokowane przez 30 sekund, jeśli 5 nieudanych prób nastąpi sekwencyjnie. Zasady wyłącznika są stanowe. Wszystkie wywołania za pośrednictwem tego klienta mają ten sam stan obwodu.

Dodawanie zasad z rejestru Polly

Podejście do zarządzania regularnie używanymi zasadami polega na zdefiniowaniu ich raz i zarejestrowaniu ich w obiekcie PolicyRegistry.

W poniższym kodzie:

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.

Aby uzyskać więcej informacji na IHttpClientFactory temat integracji z usługą Polly, zobacz witrynę typu wiki Polly.

Zarządzanie klientem HttpClient i okresem istnienia

Nowe HttpClient wystąpienie jest zwracane za każdym razem, gdy CreateClient jest wywoływane w obiekcie IHttpClientFactory. Element HttpMessageHandler jest tworzony na nazwanego klienta. Fabryka zarządza okresami HttpMessageHandler istnienia wystąpień.

IHttpClientFactoryHttpMessageHandler pule wystąpień utworzonych przez fabrykę w celu zmniejszenia zużycia zasobów. Wystąpienie HttpMessageHandler może być ponownie używane z puli podczas tworzenia nowego HttpClient wystąpienia, jeśli jego okres istnienia nie wygasł.

Buforowanie programów obsługi jest pożądane, ponieważ każda procedura obsługi zwykle zarządza własnymi podstawowymi połączeniami HTTP. Tworzenie większej liczby procedur obsługi niż jest to konieczne może spowodować opóźnienia połączeń. Niektóre programy obsługi utrzymują również połączenia otwarte na czas nieokreślony, co może uniemożliwić programowi obsługi reagowanie na zmiany systemu DNS (systemu nazw domen).

Domyślny okres istnienia programu obsługi to dwie minuty. Wartość domyślna może zostać zastąpiona na podstawie nazwanego klienta:

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

    // Remaining code deleted for brevity.

HttpClient wystąpienia mogą być zwykle traktowane jako obiekty platformy .NET, które nie wymagają usuwania. Usuwanie anuluje żądania wychodzące i gwarantuje, że dane HttpClient wystąpienie nie może być używane po wywołaniu metody Dispose. IHttpClientFactory śledzi i usuwa zasoby używane przez HttpClient wystąpienia.

Utrzymywanie pojedynczego HttpClient wystąpienia przy życiu przez długi czas trwania jest typowym wzorcem używanym przed utworzeniem klasy IHttpClientFactory. Ten wzorzec staje się niepotrzebny po przeprowadzeniu migracji do IHttpClientFactoryprogramu .

Alternatywy dla IHttpClientFactory

Używanie IHttpClientFactory w aplikacji z włączoną obsługą di pozwala uniknąć:

  • Problemy z wyczerpaniem zasobów przez wystąpienia puli HttpMessageHandler .
  • Nieaktualne problemy z systemem DNS przez wystąpienia rowerów HttpMessageHandler w regularnych odstępach czasu.

Istnieją alternatywne sposoby rozwiązywania powyższych problemów przy użyciu długotrwałego SocketsHttpHandler wystąpienia.

  • Utwórz wystąpienie, SocketsHttpHandler gdy aplikacja zostanie uruchomiona i użyj jej do końca życia aplikacji.
  • Skonfiguruj PooledConnectionLifetime odpowiednią wartość na podstawie czasów odświeżania DNS.
  • Utwórz HttpClient wystąpienia przy użyciu zgodnie z new HttpClient(handler, disposeHandler: false) potrzebami.

Powyższe podejścia rozwiązują problemy z zarządzaniem zasobami, które IHttpClientFactory są rozwiązywane w podobny sposób.

  • Udziały SocketsHttpHandler połączeń między HttpClient wystąpieniami. To udostępnianie uniemożliwia wyczerpanie gniazd.
  • Połączenia SocketsHttpHandler cykli zgodnie z, aby PooledConnectionLifetime uniknąć nieaktualnych problemów DNS.

CookieS

Wystąpienia w HttpMessageHandler puli powoduje CookieContainer współużytkowanie obiektów. Nieprzewidziane CookieContainer udostępnianie obiektów często powoduje niepoprawny kod. W przypadku aplikacji, które wymagają cookies, należy wziąć pod uwagę jedną z następujących kwestii:

  • Wyłączanie automatycznej cookie obsługi
  • Unikanie IHttpClientFactory

Wywołanie w ConfigurePrimaryHttpMessageHandler celu wyłączenia automatycznej cookie obsługi:

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

Rejestrowanie

Klienci utworzeni za pośrednictwem IHttpClientFactory komunikatów dziennika rekordów dla wszystkich żądań. Włącz odpowiedni poziom informacji w konfiguracji rejestrowania, aby wyświetlić domyślne komunikaty dziennika. Dodatkowe rejestrowanie, takie jak rejestrowanie nagłówków żądań, jest uwzględniane tylko na poziomie śledzenia.

Kategoria dziennika używana dla każdego klienta zawiera nazwę klienta. Klient o nazwie MyNamedClient, na przykład, rejestruje komunikaty z kategorią "System.Net.Http.HttpClient.MyNamedClient. Logiczna procedura obsługi". Komunikaty sufiksowane za pomocą programu LogicalHandler występują poza potokiem obsługi żądań. Na żądanie komunikaty są rejestrowane, zanim wszystkie inne programy obsługi w potoku go przetworzyły. W odpowiedzi komunikaty są rejestrowane po otrzymaniu odpowiedzi przez inne programy obsługi potoku.

Rejestrowanie odbywa się również wewnątrz potoku procedury obsługi żądań. W przykładzie MyNamedClient te komunikaty są rejestrowane z kategorią dziennika "System.Net.Http.HttpClient.MyNamedClient. ClientHandler". W przypadku żądania następuje to po uruchomieniu wszystkich innych procedur obsługi i bezpośrednio przed wysłaniem żądania. W odpowiedzi to rejestrowanie zawiera stan odpowiedzi, zanim przejdzie z powrotem przez potok obsługi.

Włączenie rejestrowania na zewnątrz i wewnątrz potoku umożliwia inspekcję zmian wprowadzonych przez inne procedury obsługi potoku. Może to obejmować zmiany nagłówków żądań lub kod stanu odpowiedzi.

Uwzględnienie nazwy klienta w kategorii dziennika umożliwia filtrowanie dzienników dla określonych nazwanych klientów.

Konfigurowanie programu HttpMessageHandler

Może być konieczne kontrolowanie konfiguracji wewnętrznej HttpMessageHandler używanej przez klienta.

Element IHttpClientBuilder jest zwracany podczas dodawania nazwanych lub wpisanych klientów. Metodę ConfigurePrimaryHttpMessageHandler rozszerzenia można użyć do zdefiniowania delegata. Delegat służy do tworzenia i konfigurowania podstawowego HttpMessageHandler używanego przez tego klienta:

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

    // Remaining code deleted for brevity.

Używanie elementu IHttpClientFactory w aplikacji konsolowej

W aplikacji konsolowej dodaj następujące odwołania do pakietu do projektu:

W poniższym przykładzie:

  • IHttpClientFactory jest zarejestrowany w kontenerze usługi hosta ogólnego.
  • MyService Tworzy wystąpienie fabryki klienta na podstawie usługi, które jest używane do utworzenia elementu HttpClient. HttpClient służy do pobierania strony internetowej.
  • Main Tworzy zakres umożliwiający wykonanie metody usługi GetPage i zapisanie pierwszych 500 znaków zawartości strony internetowej w konsoli programu .
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}";
            }
        }
    }
}

Oprogramowanie pośredniczące propagacji nagłówka

Propagacja nagłówka to oprogramowanie pośredniczące ASP.NET Core do propagowania nagłówków HTTP z żądania przychodzącego do wychodzących żądań klienta HTTP. Aby użyć propagacji nagłówka:

  • Odwołaj się do pakietu Microsoft.AspNetCore.HeaderPropagation .

  • Skonfiguruj oprogramowanie pośredniczące i HttpClient w programie 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();
        });
    }
    
  • Klient zawiera skonfigurowane nagłówki dla żądań wychodzących:

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

Dodatkowe zasoby

Przez Kirk Larkin, Steve Gordon, Glenn Condron i Ryan Nowak.

Element IHttpClientFactory można zarejestrować i użyć do konfigurowania i tworzenia HttpClient wystąpień w aplikacji. IHttpClientFactory oferuje następujące korzyści:

  • Zapewnia centralną lokalizację nazewnictwa i konfigurowania wystąpień logicznych HttpClient. Na przykład klient o nazwie github może zostać zarejestrowany i skonfigurowany do uzyskiwania dostępu do usługi GitHub. W celu uzyskania dostępu ogólnego można zarejestrować domyślnego klienta.
  • Koduje koncepcję wychodzącego oprogramowania pośredniczącego za pośrednictwem delegowania programów obsługi w programie HttpClient. Udostępnia rozszerzenia oprogramowania pośredniczącego opartego na usłudze Polly w celu korzystania z delegowania programów obsługi w programie HttpClient.
  • Zarządza buforowaniem i okresem istnienia wystąpień bazowych HttpClientMessageHandler . Automatyczne zarządzanie pozwala uniknąć typowych problemów z systemem DNS (system nazw domen), które występują podczas ręcznego zarządzania HttpClient okresami istnienia.
  • Dodaje konfigurowalne środowisko rejestrowania (za pośrednictwem ILogger) dla wszystkich żądań wysyłanych za pośrednictwem klientów utworzonych przez fabrykę.

Wyświetl lub pobierz przykładowy kod (jak pobrać).

Przykładowy kod w tej wersji tematu używa System.Text.Json do deserializacji JSzawartości ON zwróconej w odpowiedziach HTTP. W przypadku przykładów korzystających z elementów Json.NET i ReadAsAsync<T>użyj selektora wersji, aby wybrać wersję 2.x tego tematu.

Konsumpcji

W aplikacji można użyć kilku sposobów IHttpClientFactory :

Najlepsze podejście zależy od wymagań aplikacji.

Podstawowy sposób użycia

IHttpClientFactory można zarejestrować, wywołując polecenie 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.

Żądanie IHttpClientFactory może być wymagane przy użyciu wstrzykiwania zależności (DI). Poniższy kod używa IHttpClientFactory metody do utworzenia HttpClient wystąpienia:

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

Użycie IHttpClientFactory metody podobnej w poprzednim przykładzie jest dobrym sposobem refaktoryzacji istniejącej aplikacji. Nie ma to wpływu na sposób HttpClient użycia. W miejscach, w których HttpClient wystąpienia są tworzone w istniejącej aplikacji, zastąp te wystąpienia wywołaniami metody CreateClient.

Nazwani klienci

Nazwani klienci są dobrym wyborem w następujących przypadkach:

  • Aplikacja wymaga wielu różnych zastosowań programu HttpClient.
  • Wiele HttpClientz nich ma inną konfigurację.

Konfigurację nazwy HttpClient można określić podczas rejestracji w programie 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");
});

W poprzednim kodzie klient jest skonfigurowany za pomocą polecenia :

  • Adres https://api.github.com/podstawowy .
  • Dwa nagłówki wymagane do pracy z interfejsem API usługi GitHub.

CreateClient

Za każdym razem CreateClient jest wywoływana:

  • Zostanie utworzone nowe wystąpienie programu HttpClient .
  • Akcja konfiguracji jest wywoływana.

Aby utworzyć nazwanego klienta, przekaż jego nazwę do 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>();
        }
    }
}

W poprzednim kodzie żądanie nie musi określać nazwy hosta. Kod może przekazać tylko ścieżkę, ponieważ używany jest adres podstawowy skonfigurowany dla klienta.

Wpisanych klientów

Typizowane klienci:

  • Zapewnij te same możliwości co nazwani klienci bez konieczności używania ciągów jako kluczy.
  • Zapewnia funkcję IntelliSense i pomoc kompilatora podczas korzystania z klientów.
  • Podaj pojedynczą lokalizację do skonfigurowania i interakcji z określonym HttpClientelementem . Na przykład można użyć jednego typu klienta:
    • W przypadku pojedynczego punktu końcowego zaplecza.
    • Aby hermetyzować całą logikę do obsługi punktu końcowego.
  • Praca z di i może być wstrzykiwana tam, gdzie jest to wymagane w aplikacji.

Typowany klient akceptuje HttpClient parametr w konstruktorze:

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

Jeśli chcesz zobaczyć komentarze kodu przetłumaczone na języki inne niż angielski, poinformuj nas o tym w tym problemie z dyskusją w usłudze GitHub.

Powyższy kod:

  • Konfiguracja jest przenoszona do typizowanego klienta.
  • Obiekt HttpClient jest uwidaczniony jako właściwość publiczna.

Metody specyficzne dla interfejsu API można utworzyć, aby uwidaczniać HttpClient funkcje. Na przykład GetAspNetDocsIssues metoda hermetyzuje kod w celu pobrania otwartych problemów.

Następujące wywołania AddHttpClient kodu w programie w Startup.ConfigureServices celu zarejestrowania wpisanej klasy klienta:

services.AddHttpClient<GitHubService>();

Typowany klient jest zarejestrowany jako przejściowy z di. W poprzednim kodzie AddHttpClient rejestruje się GitHubService jako usługa przejściowa. Ta rejestracja używa metody fabryki do:

  1. Utwórz wystąpienie elementu HttpClient.
  2. Utwórz wystąpienie klasy , przekazując wystąpienie GitHubServicedo jego konstruktora HttpClient .

Typizowanego klienta można wstrzyknąć i używać bezpośrednio:

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

Konfigurację typowego klienta można określić podczas rejestracji w Startup.ConfigureServicesprogramie, a nie w konstruktorze typizowanego klienta:

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

Element HttpClient można hermetyzować w obrębie typizowanego klienta. Zamiast ujawniać go jako właściwość, zdefiniuj metodę, która HttpClient wywołuje wystąpienie wewnętrznie:

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

W poprzednim kodzie element HttpClient jest przechowywany w polu prywatnym. Dostęp do elementu HttpClient jest metodą publiczną GetRepos .

Wygenerowani klienci

IHttpClientFactory można używać w połączeniu z bibliotekami innych firm, takimi jak Refit. Refit to REST biblioteka dla platformy .NET. Konwertuje REST interfejsy API na interfejsy na żywo. Implementacja interfejsu jest generowana dynamicznie przez RestServiceelement , za pomocą polecenia HttpClient w celu wykonywania zewnętrznych wywołań HTTP.

Interfejs i odpowiedź są zdefiniowane w celu reprezentowania zewnętrznego interfejsu API i jego odpowiedzi:

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

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

Można dodać typizowanego klienta, używając polecenia Refit w celu wygenerowania implementacji:

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

Zdefiniowany interfejs może być używany w razie potrzeby z implementacją dostarczaną przez di i 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();
    }
}

Żądania POST, PUT i DELETE

W poprzednich przykładach wszystkie żądania HTTP używają czasownika GET HTTP. HttpClient Obsługuje również inne czasowniki HTTP, w tym:

  • POST
  • ODŁÓŻ
  • USUŃ
  • PATCH

Aby uzyskać pełną listę obsługiwanych czasowników HTTP, zobacz HttpMethod.

W poniższym przykładzie pokazano, jak utworzyć żądanie HTTP POST:

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

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

    httpResponse.EnsureSuccessStatusCode();
}

W poprzednim kodzie CreateItemAsync metoda:

  • Serializuje TodoItem parametr na JSWŁ. przy użyciu polecenia System.Text.Json. Używa to wystąpienia JsonSerializerOptions klasy , aby skonfigurować proces serializacji.
  • Tworzy wystąpienie programu StringContent , aby spakować serializowaną JSwartość ON do wysyłania w treści żądania HTTP.
  • Wywołuje metodę PostAsync wysyłania JSzawartości ON do określonego adresu URL. Jest to względny adres URL, który jest dodawany do obiektu HttpClient.BaseAddress.
  • Wywołania EnsureSuccessStatusCode w celu zgłoszenia wyjątku, jeśli kod stanu odpowiedzi nie wskazuje powodzenia.

HttpClient obsługuje również inne typy zawartości. Na przykład MultipartContent i StreamContent. Aby uzyskać pełną listę obsługiwanych zawartości, zobacz HttpContent.

W poniższym przykładzie pokazano żądanie HTTP PUT:

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

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

    httpResponse.EnsureSuccessStatusCode();
}

Powyższy kod jest bardzo podobny do przykładu POST. Metoda SaveItemAsync wywołuje PutAsync metodę PostAsynczamiast .

W poniższym przykładzie pokazano żądanie HTTP DELETE:

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

    httpResponse.EnsureSuccessStatusCode();
}

W poprzednim kodzie metoda wywołuje metodę DeleteItemAsyncDeleteAsync. Ponieważ żądania HTTP DELETE zwykle nie zawierają treści, DeleteAsync metoda nie udostępnia przeciążenia, które akceptuje wystąpienie HttpContentklasy .

Aby dowiedzieć się więcej na temat używania różnych czasowników HTTP w programie HttpClient, zobacz HttpClient.

Oprogramowanie pośredniczące żądań wychodzących

HttpClient ma koncepcję delegowania procedur obsługi, które mogą być połączone ze sobą dla wychodzących żądań HTTP. IHttpClientFactory:

  • Upraszcza definiowanie procedur obsługi, które mają być stosowane dla każdego nazwanego klienta.
  • Obsługuje rejestrację i łączenie wielu procedur obsługi w celu utworzenia potoku oprogramowania pośredniczącego żądań wychodzących. Każdy z tych programów obsługi może wykonać pracę przed i po żądaniu wychodzącym. Ten wzorzec:
    • Jest podobny do potoku oprogramowania pośredniczącego dla ruchu przychodzącego w programie ASP.NET Core.
    • Udostępnia mechanizm zarządzania problemami krzyżowymi dotyczącymi żądań HTTP, takich jak:
      • Buforowanie
      • obsługa błędów
      • Serializacji
      • rejestrowanie

Aby utworzyć procedurę obsługi delegowania:

  • Pochodzą z klasy DelegatingHandler.
  • Zastąpij .SendAsync Przed przekazaniem żądania do następnego programu obsługi w potoku wykonaj kod:
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);
    }
}

Powyższy kod sprawdza, czy X-API-KEY nagłówek znajduje się w żądaniu. Jeśli X-API-KEY brakuje, BadRequest zostanie zwrócona wartość .

Do konfiguracji programu można dodać więcej niż jedną procedurę obsługi za HttpClient pomocą polecenia 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.

W poprzednim kodzie element ValidateHeaderHandler jest zarejestrowany w di. Po zarejestrowaniu AddHttpMessageHandler można wywołać metodę , przekazując typ programu obsługi.

W kolejności wykonywania można zarejestrować wiele procedur obsługi. Każda procedura obsługi opakowuje następną procedurę obsługi do momentu, aż finale HttpClientHandler wykona żądanie:

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

Używanie di w rozwiązaniu pośredniczącym żądań wychodzących

Podczas IHttpClientFactory tworzenia nowej procedury obsługi delegowania używa di do spełnienia parametrów konstruktora programu obsługi. IHttpClientFactorytworzy oddzielnyzakres di dla każdego programu obsługi, co może prowadzić do zaskakującego zachowania, gdy program obsługi korzysta z usługi o określonym zakresie.

Rozważmy na przykład następujący interfejs i jego implementację, która reprezentuje zadanie jako operację z identyfikatorem: OperationId

public interface IOperationScoped 
{
    string OperationId { get; }
}

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

Jak sugeruje jego nazwa, IOperationScoped jest zarejestrowany w di przy użyciu okresu istnienia o określonym zakresie :

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

Następująca procedura obsługi delegowania używa polecenia i używa IOperationScoped go do ustawienia nagłówka X-OPERATION-ID dla żądania wychodzącego:

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

W pobranym pliku HttpRequestsSample przejdź do /Operation strony i odśwież stronę. Wartość zakresu żądania zmienia się dla każdego żądania, ale wartość zakresu programu obsługi zmienia się tylko co 5 sekund.

Programy obsługi mogą zależeć od usług dowolnego zakresu. Usługi, od których są zależne programy obsługi, są usuwane po usunięciu programu obsługi.

Użyj jednej z następujących metod udostępniania stanu poszczególnych żądań za pomocą programów obsługi komunikatów:

Korzystanie z procedur obsługi opartych na usłudze Polly

IHttpClientFactory program integruje się z biblioteką innej firmy Polly. Polly to kompleksowa odporność i biblioteka obsługi błędów przejściowych dla platformy .NET. Umożliwia deweloperom wyrażanie zasad, takich jak ponawianie prób, wyłącznik, przekroczenie limitu czasu, izolacja bulkhead i powrót w sposób płynny i bezpieczny wątkowo.

Dostępne są metody rozszerzenia umożliwiające korzystanie z zasad Polly ze skonfigurowanymi HttpClient wystąpieniami. Rozszerzenia Polly obsługują dodawanie programów obsługi opartych na usłudze Polly do klientów. Usługa Polly wymaga pakietu NuGet Microsoft.Extensions.Http.Polly .

Obsługa błędów przejściowych

Błędy zwykle występują, gdy zewnętrzne wywołania HTTP są przejściowe. AddTransientHttpErrorPolicy umożliwia zdefiniowanie zasad w celu obsługi błędów przejściowych. Zasady skonfigurowane z obsługą AddTransientHttpErrorPolicy następujących odpowiedzi:

AddTransientHttpErrorPolicy zapewnia dostęp do obiektu skonfigurowanego PolicyBuilder do obsługi błędów reprezentujących możliwy błąd przejściowy:

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

    // Remaining code deleted for brevity.

W poprzednim kodzie WaitAndRetryAsync zdefiniowano zasady. Żądania, które zakończyły się niepowodzeniem, są ponawiane do trzech razy z opóźnieniem 600 ms między próbami.

Dynamiczne wybieranie zasad

Metody rozszerzeń są udostępniane w celu dodania procedur obsługi opartych na usłudze Polly, na przykład AddPolicyHandler. Następujące AddPolicyHandler przeciążenie sprawdza żądanie, aby zdecydować, które zasady mają być stosowane:

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

W poprzednim kodzie, jeśli żądanie wychodzące to HTTP GET, zostanie zastosowany limit czasu 10-sekundowy. W przypadku każdej innej metody HTTP jest używany 30-sekundowy limit czasu.

Dodawanie wielu programów obsługi polly

Typowe jest zagnieżdżanie zasad polly:

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

W poprzednim przykładzie:

  • Dodano dwa programy obsługi.
  • Pierwsza procedura obsługi używa AddTransientHttpErrorPolicy metody do dodawania zasad ponawiania. Żądania, które zakończyły się niepowodzeniem, są ponawiane do trzech razy.
  • Drugie AddTransientHttpErrorPolicy wywołanie dodaje zasady wyłącznika. Dalsze żądania zewnętrzne są blokowane przez 30 sekund, jeśli 5 nieudanych prób nastąpi sekwencyjnie. Zasady wyłącznika są stanowe. Wszystkie wywołania za pośrednictwem tego klienta mają ten sam stan obwodu.

Dodawanie zasad z rejestru Polly

Podejście do zarządzania regularnie używanymi zasadami polega na zdefiniowaniu ich raz i zarejestrowaniu ich w obiekcie PolicyRegistry.

W poniższym kodzie:

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.

Aby uzyskać więcej informacji na IHttpClientFactory temat integracji z usługą Polly, zobacz witrynę typu wiki Polly.

Zarządzanie klientem HttpClient i okresem istnienia

Nowe HttpClient wystąpienie jest zwracane za każdym razem, gdy CreateClient jest wywoływane w obiekcie IHttpClientFactory. Element HttpMessageHandler jest tworzony na nazwanego klienta. Fabryka zarządza okresami HttpMessageHandler istnienia wystąpień.

IHttpClientFactoryHttpMessageHandler pule wystąpień utworzonych przez fabrykę w celu zmniejszenia zużycia zasobów. Wystąpienie HttpMessageHandler może być ponownie używane z puli podczas tworzenia nowego HttpClient wystąpienia, jeśli jego okres istnienia nie wygasł.

Buforowanie programów obsługi jest pożądane, ponieważ każda procedura obsługi zwykle zarządza własnymi podstawowymi połączeniami HTTP. Tworzenie większej liczby procedur obsługi niż jest to konieczne może spowodować opóźnienia połączeń. Niektóre programy obsługi utrzymują również połączenia otwarte na czas nieokreślony, co może uniemożliwić programowi obsługi reagowanie na zmiany systemu DNS (systemu nazw domen).

Domyślny okres istnienia programu obsługi to dwie minuty. Wartość domyślna może zostać zastąpiona na podstawie nazwanego klienta:

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

    // Remaining code deleted for brevity.

HttpClient wystąpienia mogą być zwykle traktowane jako obiekty platformy .NET, które nie wymagają usuwania. Usuwanie anuluje żądania wychodzące i gwarantuje, że dane HttpClient wystąpienie nie może być używane po wywołaniu metody Dispose. IHttpClientFactory śledzi i usuwa zasoby używane przez HttpClient wystąpienia.

Utrzymywanie pojedynczego HttpClient wystąpienia przy życiu przez długi czas trwania jest typowym wzorcem używanym przed utworzeniem klasy IHttpClientFactory. Ten wzorzec staje się niepotrzebny po przeprowadzeniu migracji do IHttpClientFactoryprogramu .

Alternatywy dla IHttpClientFactory

Używanie IHttpClientFactory w aplikacji z włączoną obsługą di pozwala uniknąć:

  • Problemy z wyczerpaniem zasobów przez wystąpienia puli HttpMessageHandler .
  • Nieaktualne problemy z systemem DNS przez wystąpienia rowerów HttpMessageHandler w regularnych odstępach czasu.

Istnieją alternatywne sposoby rozwiązywania powyższych problemów przy użyciu długotrwałego SocketsHttpHandler wystąpienia.

  • Utwórz wystąpienie, SocketsHttpHandler gdy aplikacja zostanie uruchomiona i użyj jej do końca życia aplikacji.
  • Skonfiguruj PooledConnectionLifetime odpowiednią wartość na podstawie czasów odświeżania DNS.
  • Utwórz HttpClient wystąpienia przy użyciu zgodnie z new HttpClient(handler, disposeHandler: false) potrzebami.

Powyższe podejścia rozwiązują problemy z zarządzaniem zasobami, które IHttpClientFactory są rozwiązywane w podobny sposób.

  • Udziały SocketsHttpHandler połączeń między HttpClient wystąpieniami. To udostępnianie uniemożliwia wyczerpanie gniazd.
  • Połączenia SocketsHttpHandler cykli zgodnie z, aby PooledConnectionLifetime uniknąć nieaktualnych problemów DNS.

CookieS

Wystąpienia w HttpMessageHandler puli powoduje CookieContainer współużytkowanie obiektów. Nieprzewidziane CookieContainer udostępnianie obiektów często powoduje niepoprawny kod. W przypadku aplikacji, które wymagają cookies, należy wziąć pod uwagę jedną z następujących kwestii:

  • Wyłączanie automatycznej cookie obsługi
  • Unikanie IHttpClientFactory

Wywołanie w ConfigurePrimaryHttpMessageHandler celu wyłączenia automatycznej cookie obsługi:

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

Rejestrowanie

Klienci utworzeni za pośrednictwem IHttpClientFactory komunikatów dziennika rekordów dla wszystkich żądań. Włącz odpowiedni poziom informacji w konfiguracji rejestrowania, aby wyświetlić domyślne komunikaty dziennika. Dodatkowe rejestrowanie, takie jak rejestrowanie nagłówków żądań, jest uwzględniane tylko na poziomie śledzenia.

Kategoria dziennika używana dla każdego klienta zawiera nazwę klienta. Klient o nazwie MyNamedClient, na przykład, rejestruje komunikaty z kategorią "System.Net.Http.HttpClient.MyNamedClient. Logiczna procedura obsługi". Komunikaty sufiksowane za pomocą programu LogicalHandler występują poza potokiem obsługi żądań. Na żądanie komunikaty są rejestrowane, zanim wszystkie inne programy obsługi w potoku go przetworzyły. W odpowiedzi komunikaty są rejestrowane po otrzymaniu odpowiedzi przez inne programy obsługi potoku.

Rejestrowanie odbywa się również wewnątrz potoku procedury obsługi żądań. W przykładzie MyNamedClient te komunikaty są rejestrowane z kategorią dziennika "System.Net.Http.HttpClient.MyNamedClient. ClientHandler". W przypadku żądania następuje to po uruchomieniu wszystkich innych procedur obsługi i bezpośrednio przed wysłaniem żądania. W odpowiedzi to rejestrowanie zawiera stan odpowiedzi, zanim przejdzie z powrotem przez potok obsługi.

Włączenie rejestrowania na zewnątrz i wewnątrz potoku umożliwia inspekcję zmian wprowadzonych przez inne procedury obsługi potoku. Może to obejmować zmiany nagłówków żądań lub kod stanu odpowiedzi.

Uwzględnienie nazwy klienta w kategorii dziennika umożliwia filtrowanie dzienników dla określonych nazwanych klientów.

Konfigurowanie programu HttpMessageHandler

Może być konieczne kontrolowanie konfiguracji wewnętrznej HttpMessageHandler używanej przez klienta.

Element IHttpClientBuilder jest zwracany podczas dodawania nazwanych lub wpisanych klientów. Metodę ConfigurePrimaryHttpMessageHandler rozszerzenia można użyć do zdefiniowania delegata. Delegat służy do tworzenia i konfigurowania podstawowego HttpMessageHandler używanego przez tego klienta:

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

    // Remaining code deleted for brevity.

Używanie elementu IHttpClientFactory w aplikacji konsolowej

W aplikacji konsolowej dodaj następujące odwołania do pakietu do projektu:

W poniższym przykładzie:

  • IHttpClientFactory jest zarejestrowany w kontenerze usługi hosta ogólnego.
  • MyService Tworzy wystąpienie fabryki klienta na podstawie usługi, które jest używane do utworzenia elementu HttpClient. HttpClient służy do pobierania strony internetowej.
  • Main Tworzy zakres umożliwiający wykonanie metody usługi GetPage i zapisanie pierwszych 500 znaków zawartości strony internetowej w konsoli programu .
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}";
            }
        }
    }
}

Oprogramowanie pośredniczące propagacji nagłówka

Propagacja nagłówka to oprogramowanie pośredniczące ASP.NET Core do propagowania nagłówków HTTP z żądania przychodzącego do wychodzących żądań klienta HTTP. Aby użyć propagacji nagłówka:

  • Odwołaj się do pakietu Microsoft.AspNetCore.HeaderPropagation .

  • Skonfiguruj oprogramowanie pośredniczące i HttpClient w programie 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();
        });
    }
    
  • Klient zawiera skonfigurowane nagłówki dla żądań wychodzących:

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

Dodatkowe zasoby

Przez Glenn Condron, Ryan Nowak i Steve Gordon

Element IHttpClientFactory można zarejestrować i użyć do konfigurowania i tworzenia HttpClient wystąpień w aplikacji. Oferuje następujące korzyści:

  • Zapewnia centralną lokalizację nazewnictwa i konfigurowania wystąpień logicznych HttpClient. Na przykład można zarejestrować i skonfigurować klienta usługi GitHub w celu uzyskania dostępu do usługi GitHub. Do innych celów można zarejestrować domyślnego klienta.
  • Codifies pojęcie wychodzącego oprogramowania pośredniczącego za pośrednictwem delegowania programów obsługi w HttpClient systemie i zapewnia rozszerzenia oprogramowania pośredniczącego opartego na polly, aby z tego skorzystać.
  • Zarządza buforowaniem i okresem istnienia wystąpień bazowych HttpClientMessageHandler , aby uniknąć typowych problemów z systemem DNS występujących podczas ręcznego zarządzania HttpClient okresami istnienia.
  • Dodaje konfigurowalne środowisko rejestrowania (za pośrednictwem ILogger) dla wszystkich żądań wysyłanych za pośrednictwem klientów utworzonych przez fabrykę.

Wyświetl lub pobierz przykładowy kod (jak pobrać)

Wymagania wstępne

Projekty przeznaczone dla platformy .NET Framework wymagają zainstalowania pakietu NuGet Microsoft.Extensions.Http . Projekty przeznaczone dla platformy .NET Core i odwołujące się do Microsoft.AspNetCore.App metapakiet zawierają Microsoft.Extensions.Http już pakiet.

Konsumpcji

W aplikacji można użyć kilku sposobów IHttpClientFactory :

Żaden z nich nie jest ściśle lepszy od innego. Najlepsze podejście zależy od ograniczeń aplikacji.

Podstawowy sposób użycia

IHttpClientFactory Można je zarejestrować, wywołując metodę AddHttpClient rozszerzenia w metodzie IServiceCollectionStartup.ConfigureServices , wewnątrz metody .

services.AddHttpClient();

Po zarejestrowaniu kod może akceptować IHttpClientFactory usługi z dowolnego miejsca, które można wstrzykiwać zależności (DI). Można IHttpClientFactory go użyć do utworzenia HttpClient wystąpienia:

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

Użycie IHttpClientFactory w ten sposób jest dobrym sposobem refaktoryzacji istniejącej aplikacji. Nie ma to wpływu na sposób, w jaki HttpClient jest używany. W miejscach, w których HttpClient wystąpienia są obecnie tworzone, zastąp te wystąpienia wywołaniem metody CreateClient.

Nazwani klienci

Jeśli aplikacja wymaga wielu różnych zastosowań HttpClientprogramu , z których każda ma inną konfigurację, należy użyć nazwanych klientów. Konfigurację nazwy HttpClient można określić podczas rejestracji w programie 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");
});

W poprzednim kodzie AddHttpClient jest wywoływana nazwa github. Ten klient ma zastosowaną konfigurację domyślną — a mianowicie adres podstawowy i dwa nagłówki wymagane do pracy z interfejsem API usługi GitHub.

Za każdym razem CreateClient jest wywoływane nowe wystąpienie HttpClient jest tworzone, a akcja konfiguracji jest wywoływana.

Aby korzystać z nazwanego klienta, parametr ciągu można przekazać do CreateClient. Określ nazwę klienta, który ma zostać utworzony:

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

W poprzednim kodzie żądanie nie musi określać nazwy hosta. Może ona przekazać tylko ścieżkę, ponieważ używany jest adres podstawowy skonfigurowany dla klienta.

Wpisanych klientów

Typizowane klienci:

  • Zapewnij te same możliwości co nazwani klienci bez konieczności używania ciągów jako kluczy.
  • Zapewnia funkcję IntelliSense i pomoc kompilatora podczas korzystania z klientów.
  • Podaj pojedynczą lokalizację do skonfigurowania i interakcji z określonym HttpClientelementem . Na przykład pojedynczy klient typu może być używany dla pojedynczego punktu końcowego zaplecza i hermetyzować całą logikę zajmującą się tym punktem końcowym.
  • Praca z di i może być wstrzykiwana tam, gdzie jest to wymagane w aplikacji.

Typowany klient akceptuje HttpClient parametr w konstruktorze:

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

W poprzednim kodzie konfiguracja jest przenoszona do typizowanego klienta. Obiekt HttpClient jest uwidaczniony jako właściwość publiczna. Istnieje możliwość zdefiniowania metod specyficznych dla interfejsu API, które uwidaczniają HttpClient funkcjonalność. Metoda GetAspNetDocsIssues hermetyzuje kod potrzebny do wykonywania zapytań dotyczących i analizowania najnowszych otwartych problemów z repozytorium GitHub.

Aby zarejestrować typizowanego klienta, można użyć metody rozszerzenia ogólnego AddHttpClient w programie Startup.ConfigureServices, określając typową klasę klienta:

services.AddHttpClient<GitHubService>();

Typowany klient jest zarejestrowany jako przejściowy z di. Typizowanego klienta można wstrzyknąć i używać bezpośrednio:

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

Jeśli jest to preferowane, konfigurację typizowanego klienta można określić podczas rejestracji w Startup.ConfigureServicesprogramie, a nie w konstruktorze typizowanego klienta:

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

Można całkowicie hermetyzować HttpClient wewnątrz typizowanego klienta. Zamiast uwidaczniać go jako właściwość, metody publiczne można podać, które HttpClient wywołają wystąpienie wewnętrznie.

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

W poprzednim kodzie element HttpClient jest przechowywany jako pole prywatne. Cały dostęp do wywołań zewnętrznych przechodzi przez metodę GetRepos .

Wygenerowani klienci

IHttpClientFactory można używać w połączeniu z innymi bibliotekami innych firm, takimi jak Refit. Refit to REST biblioteka dla platformy .NET. Konwertuje REST interfejsy API na interfejsy na żywo. Implementacja interfejsu jest generowana dynamicznie przez RestServiceelement , za pomocą polecenia HttpClient w celu wykonywania zewnętrznych wywołań HTTP.

Interfejs i odpowiedź są zdefiniowane w celu reprezentowania zewnętrznego interfejsu API i jego odpowiedzi:

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

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

Można dodać typizowanego klienta, używając polecenia Refit w celu wygenerowania implementacji:

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

Zdefiniowany interfejs może być używany w razie potrzeby z implementacją dostarczaną przez di i 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();
    }
}

Oprogramowanie pośredniczące żądań wychodzących

HttpClient Ma już koncepcję delegowania procedur obsługi, które mogą być połączone ze sobą dla wychodzących żądań HTTP. Ułatwia IHttpClientFactory zdefiniowanie procedur obsługi, które mają być stosowane dla każdego nazwanego klienta. Obsługuje rejestrację i łączenie wielu procedur obsługi w celu utworzenia potoku oprogramowania pośredniczącego żądań wychodzących. Każdy z tych programów obsługi może wykonać pracę przed i po żądaniu wychodzącym. Ten wzorzec jest podobny do przychodzącego potoku oprogramowania pośredniczącego w ASP.NET Core. Wzorzec udostępnia mechanizm zarządzania problemami krzyżowymi dotyczącymi żądań HTTP, w tym buforowania, obsługi błędów, serializacji i rejestrowania.

Aby utworzyć procedurę obsługi, zdefiniuj klasę wyprowadzoną z DelegatingHandlerklasy . Zastąpij metodę SendAsync , aby wykonać kod przed przekazaniem żądania do następnego programu obsługi w potoku:

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

Powyższy kod definiuje podstawową procedurę obsługi. Sprawdza, czy X-API-KEY nagłówek został uwzględniony w żądaniu. Jeśli brakuje nagłówka, może uniknąć wywołania HTTP i zwrócić odpowiednią odpowiedź.

Podczas rejestracji można dodać co najmniej jeden program obsługi do konfiguracji dla elementu HttpClient. To zadanie jest wykonywane za pomocą metod rozszerzeń w obiekcie 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>();

W poprzednim kodzie element ValidateHeaderHandler jest zarejestrowany w di. Program obsługi musi być zarejestrowany w usłudze DI jako usługa przejściowy, nigdy nie ma zakresu. Jeśli program obsługi jest zarejestrowany jako usługa o określonym zakresie i wszystkie usługi, od których zależy program obsługi, są jednorazowe:

  • Usługi programu obsługi można usunąć przed wyjściem programu obsługi z zakresu.
  • Usunięte usługi obsługi powodują niepowodzenie programu obsługi.

Po zarejestrowaniu AddHttpMessageHandler można wywołać metodę , przekazując typ programu obsługi.

W kolejności wykonywania można zarejestrować wiele procedur obsługi. Każda procedura obsługi opakowuje następną procedurę obsługi do momentu, aż finale HttpClientHandler wykona żądanie:

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

Użyj jednej z następujących metod udostępniania stanu poszczególnych żądań za pomocą programów obsługi komunikatów:

  • Przekazywanie danych do programu obsługi przy użyciu polecenia HttpRequestMessage.Properties.
  • Użyj polecenia IHttpContextAccessor , aby uzyskać dostęp do bieżącego żądania.
  • Utwórz niestandardowy AsyncLocal obiekt magazynu w celu przekazania danych.

Korzystanie z procedur obsługi opartych na usłudze Polly

IHttpClientFactory integruje się z popularną biblioteką innej firmy o nazwie Polly. Polly to kompleksowa odporność i biblioteka obsługi błędów przejściowych dla platformy .NET. Umożliwia deweloperom wyrażanie zasad, takich jak ponawianie prób, wyłącznik, przekroczenie limitu czasu, izolacja bulkhead i powrót w sposób płynny i bezpieczny wątkowo.

Dostępne są metody rozszerzenia umożliwiające korzystanie z zasad Polly ze skonfigurowanymi HttpClient wystąpieniami. Rozszerzenia Polly:

  • Obsługa dodawania programów obsługi opartych na usłudze Polly do klientów.
  • Można go użyć po zainstalowaniu pakietu NuGet Microsoft.Extensions.Http.Polly . Pakiet nie jest uwzględniony w strukturze udostępnionej ASP.NET Core.

Obsługa błędów przejściowych

Najczęstsze błędy występują, gdy zewnętrzne wywołania HTTP są przejściowe. Dołączono wygodną metodę AddTransientHttpErrorPolicy rozszerzenia, która umożliwia zdefiniowanie zasad w celu obsługi błędów przejściowych. Zasady skonfigurowane za pomocą tej metody rozszerzenia obsługują HttpRequestExceptionodpowiedzi HTTP 5xx i odpowiedzi HTTP 408.

Rozszerzenie AddTransientHttpErrorPolicy może być używane w programie Startup.ConfigureServices. Rozszerzenie zapewnia dostęp do obiektu skonfigurowanego PolicyBuilder do obsługi błędów reprezentujących możliwy błąd przejściowy:

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

W poprzednim kodzie WaitAndRetryAsync zdefiniowano zasady. Żądania, które zakończyły się niepowodzeniem, są ponawiane do trzech razy z opóźnieniem 600 ms między próbami.

Dynamiczne wybieranie zasad

Istnieją dodatkowe metody rozszerzeń, które mogą służyć do dodawania procedur obsługi opartych na usłudze Polly. Jednym z takich rozszerzeń jest AddPolicyHandler, które ma wiele przeciążeń. Jedno przeciążenie umożliwia sprawdzenie żądania podczas definiowania zasad do zastosowania:

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

W poprzednim kodzie, jeśli żądanie wychodzące to HTTP GET, zostanie zastosowany limit czasu 10-sekundowy. W przypadku każdej innej metody HTTP jest używany 30-sekundowy limit czasu.

Dodawanie wielu programów obsługi polly

Typowe jest zagnieżdżanie zasad polly w celu zapewnienia rozszerzonych funkcji:

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

W poprzednim przykładzie dodawane są dwa programy obsługi. Pierwszy używa AddTransientHttpErrorPolicy rozszerzenia do dodawania zasad ponawiania prób. Żądania, które zakończyły się niepowodzeniem, są ponawiane do trzech razy. Drugie wywołanie w celu AddTransientHttpErrorPolicy dodawania zasad wyłącznika. Dalsze żądania zewnętrzne są blokowane przez 30 sekund, jeśli pięć nieudanych prób nastąpi sekwencyjnie. Zasady wyłącznika są stanowe. Wszystkie wywołania za pośrednictwem tego klienta mają ten sam stan obwodu.

Dodawanie zasad z rejestru Polly

Podejście do zarządzania regularnie używanymi zasadami polega na zdefiniowaniu ich raz i zarejestrowaniu ich w obiekcie PolicyRegistry. Udostępniono metodę rozszerzenia, która umożliwia dodanie programu obsługi przy użyciu zasad z rejestru:

var registry = services.AddPolicyRegistry();

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

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

W poprzednim kodzie dwa zasady są rejestrowane po PolicyRegistry dodaniu elementu do elementu ServiceCollection. Aby użyć zasad z rejestru, AddPolicyHandlerFromRegistry używana jest metoda przekazująca nazwę zasad do zastosowania.

Więcej informacji na temat IHttpClientFactory integracji usługi Polly można znaleźć na stronie wiki Polly.

Zarządzanie klientem HttpClient i okresem istnienia

Nowe HttpClient wystąpienie jest zwracane za każdym razem, gdy CreateClient jest wywoływane w obiekcie IHttpClientFactory. Istnieje nazwany HttpMessageHandler klient. Fabryka zarządza okresami HttpMessageHandler istnienia wystąpień.

IHttpClientFactoryHttpMessageHandler pule wystąpień utworzonych przez fabrykę w celu zmniejszenia zużycia zasobów. Wystąpienie HttpMessageHandler może być ponownie używane z puli podczas tworzenia nowego HttpClient wystąpienia, jeśli jego okres istnienia nie wygasł.

Buforowanie programów obsługi jest pożądane, ponieważ każda procedura obsługi zwykle zarządza własnymi podstawowymi połączeniami HTTP. Tworzenie większej liczby procedur obsługi niż jest to konieczne może spowodować opóźnienia połączeń. Niektóre programy obsługi utrzymują również połączenia otwarte na czas nieokreślony, co może uniemożliwić programowi obsługi reagowanie na zmiany DNS.

Domyślny okres istnienia programu obsługi to dwie minuty. Wartość domyślna może zostać zastąpiona na podstawie nazwanego klienta. Aby go zastąpić, wywołaj SetHandlerLifetimeIHttpClientBuilder metodę , która jest zwracana podczas tworzenia klienta:

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

Usuwanie klienta nie jest wymagane. Usuwanie anuluje żądania wychodzące i gwarantuje, że dane HttpClient wystąpienie nie może być używane po wywołaniu metody Dispose. IHttpClientFactory śledzi i usuwa zasoby używane przez HttpClient wystąpienia. HttpClient Wystąpienia mogą być zwykle traktowane jako obiekty platformy .NET, które nie wymagają usuwania.

Utrzymywanie pojedynczego HttpClient wystąpienia przy życiu przez długi czas trwania jest typowym wzorcem używanym przed utworzeniem klasy IHttpClientFactory. Ten wzorzec staje się niepotrzebny po przeprowadzeniu migracji do IHttpClientFactoryprogramu .

Alternatywy dla IHttpClientFactory

Używanie IHttpClientFactory w aplikacji z włączoną obsługą di pozwala uniknąć:

  • Problemy z wyczerpaniem zasobów przez wystąpienia puli HttpMessageHandler .
  • Nieaktualne problemy z systemem DNS przez wystąpienia rowerów HttpMessageHandler w regularnych odstępach czasu.

Istnieją alternatywne sposoby rozwiązywania powyższych problemów przy użyciu długotrwałego SocketsHttpHandler wystąpienia.

  • Utwórz wystąpienie, SocketsHttpHandler gdy aplikacja zostanie uruchomiona i użyj jej do końca życia aplikacji.
  • Skonfiguruj PooledConnectionLifetime odpowiednią wartość na podstawie czasów odświeżania DNS.
  • Utwórz HttpClient wystąpienia przy użyciu zgodnie z new HttpClient(handler, disposeHandler: false) potrzebami.

Powyższe podejścia rozwiązują problemy z zarządzaniem zasobami, które IHttpClientFactory są rozwiązywane w podobny sposób.

  • Udziały SocketsHttpHandler połączeń między HttpClient wystąpieniami. To udostępnianie uniemożliwia wyczerpanie gniazd.
  • Połączenia SocketsHttpHandler cykli zgodnie z, aby PooledConnectionLifetime uniknąć nieaktualnych problemów DNS.

CookieS

Wystąpienia w HttpMessageHandler puli powoduje CookieContainer współużytkowanie obiektów. Nieprzewidziane CookieContainer udostępnianie obiektów często powoduje niepoprawny kod. W przypadku aplikacji, które wymagają cookies, należy wziąć pod uwagę jedną z następujących kwestii:

  • Wyłączanie automatycznej cookie obsługi
  • Unikanie IHttpClientFactory

Wywołanie w ConfigurePrimaryHttpMessageHandler celu wyłączenia automatycznej cookie obsługi:

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

Rejestrowanie

Klienci utworzeni za pośrednictwem IHttpClientFactory komunikatów dziennika rekordów dla wszystkich żądań. Włącz odpowiedni poziom informacji w konfiguracji rejestrowania, aby wyświetlić domyślne komunikaty dziennika. Dodatkowe rejestrowanie, takie jak rejestrowanie nagłówków żądań, jest uwzględniane tylko na poziomie śledzenia.

Kategoria dziennika używana dla każdego klienta zawiera nazwę klienta. Klient o nazwie MyNamedClient, na przykład rejestruje komunikaty z kategorią System.Net.Http.HttpClient.MyNamedClient.LogicalHandler. Komunikaty sufiksowane za pomocą programu LogicalHandler występują poza potokiem obsługi żądań. Na żądanie komunikaty są rejestrowane, zanim wszystkie inne programy obsługi w potoku go przetworzyły. W odpowiedzi komunikaty są rejestrowane po otrzymaniu odpowiedzi przez inne programy obsługi potoku.

Rejestrowanie odbywa się również wewnątrz potoku procedury obsługi żądań. W przykładzie MyNamedClient te komunikaty są rejestrowane względem kategorii System.Net.Http.HttpClient.MyNamedClient.ClientHandlerdziennika . W przypadku żądania dzieje się tak po uruchomieniu wszystkich innych programów obsługi i bezpośrednio przed wysłaniem żądania w sieci. W odpowiedzi to rejestrowanie zawiera stan odpowiedzi, zanim przejdzie z powrotem przez potok obsługi.

Włączenie rejestrowania na zewnątrz i wewnątrz potoku umożliwia inspekcję zmian wprowadzonych przez inne procedury obsługi potoku. Może to obejmować zmiany nagłówków żądań, na przykład lub kod stanu odpowiedzi.

Uwzględnienie nazwy klienta w kategorii dziennika umożliwia filtrowanie dzienników dla określonych nazwanych klientów w razie potrzeby.

Konfigurowanie programu HttpMessageHandler

Może być konieczne kontrolowanie konfiguracji wewnętrznej HttpMessageHandler używanej przez klienta.

Element IHttpClientBuilder jest zwracany podczas dodawania nazwanych lub wpisanych klientów. Metodę ConfigurePrimaryHttpMessageHandler rozszerzenia można użyć do zdefiniowania delegata. Delegat służy do tworzenia i konfigurowania podstawowego HttpMessageHandler używanego przez tego klienta:

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

Używanie elementu IHttpClientFactory w aplikacji konsolowej

W aplikacji konsolowej dodaj następujące odwołania do pakietu do projektu:

W poniższym przykładzie:

  • IHttpClientFactory jest zarejestrowany w kontenerze usługi hosta ogólnego.
  • MyService Tworzy wystąpienie fabryki klienta na podstawie usługi, które jest używane do utworzenia elementu HttpClient. HttpClient służy do pobierania strony internetowej.
  • Metoda usługi GetPage jest wykonywana w celu zapisania pierwszych 500 znaków zawartości strony internetowej w konsoli. Aby uzyskać więcej informacji na temat wywoływania usług z Program.Mainprogramu , zobacz Wstrzykiwanie zależności w programie 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}";
            }
        }
    }
}

Oprogramowanie pośredniczące propagacji nagłówka

Propagacja nagłówka to oprogramowanie pośredniczące obsługiwane przez społeczność, które propaguje nagłówki HTTP z żądania przychodzącego do wychodzących żądań klienta HTTP. Aby użyć propagacji nagłówka:

  • Odwołuj się do portu obsługiwanego przez społeczność nagłówkapropagacji pakietu. program ASP.NET Core 3.1 lub nowszy obsługuje element Microsoft.AspNetCore.HeaderPropagation.

  • Skonfiguruj oprogramowanie pośredniczące i HttpClient w programie 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();
    }
    
  • Klient zawiera skonfigurowane nagłówki dla żądań wychodzących:

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

Dodatkowe zasoby