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

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.
  • Codififies koncepcja 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 programie Program.cs:

var builder = WebApplication.CreateBuilder(args);

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

Żądanie IHttpClientFactory można zażądać 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 do 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 do CreateClient.

Nazwani klienci

Nazwani klienci są dobrym wyborem, gdy:

  • Aplikacja wymaga wielu odrębnych 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 z:

  • Adres podstawowy https://api.github.com/.
  • 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 klasy HttpClient .
  • Akcja konfiguracji jest wywoływana.

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

Klienci typizowane

Klienci typizowane:

  • 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 określonego elementu i interakcji z nim HttpClient. Na przykład można użyć pojedynczego klienta typizowanego:
    • W przypadku pojedynczego punktu końcowego zaplecza.
    • Aby hermetyzować całą logikę do obsługi punktu końcowego.
  • Praca z di i można wstrzyknąć 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 ma następujące działanie:

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

Można utworzyć metody specyficzne dla interfejsu API, które uwidaczniają HttpClient funkcjonalność. Na przykład GetAspNetCoreDocsBranches metoda hermetyzuje kod umożliwiający pobranie gałęzi usługi GitHub docs.

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

builder.Services.AddHttpClient<GitHubService>();

Typizowanego klienta jest rejestrowany jako przejściowy z di. W poprzednim kodzie rejestruje AddHttpClient 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 GitHubServiceklasy , przekazując w wystąpieniu obiektu do 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 platformy .NET. Konwertuje interfejsy REST API na interfejsy na żywo. Wywołanie AddRefitClient metody w celu wygenerowania dynamicznej implementacji interfejsu, który 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
  • PUT
  • DELETE
  • 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 StringContent elementu w celu spakowania serializowanego JSmodułu ON do wysyłania w treści żądania HTTP.
  • Wywołania PostAsync w celu wysł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ługiwanej 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 DeleteAsyncmetodę DeleteItemAsync . Ponieważ żądania HTTP DELETE zwykle nie zawierają treści, DeleteAsync metoda nie zapewnia przeciążenia, które akceptuje wystąpienie HttpContentklasy .

Aby dowiedzieć się więcej o korzystaniu z 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 razem dla wychodzących żądań HTTP. IHttpClientFactory:

  • Upraszcza definiowanie procedur obsługi do zastosowania dla każdego nazwanego klienta.
  • Obsługuje rejestrację i tworzenie łańcucha 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 ASP.NET Core.
    • Zapewnia 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:

  • Wyprowadzanie z klasy DelegatingHandler.
  • Zastąpij SendAsync. Wykonaj kod przed przekazaniem żądania do następnej procedury 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(
                    "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 jest zwracany.

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

builder.Services.AddTransient<ValidateHeaderHandler>();

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

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

Wiele procedur obsługi można zarejestrować w kolejności, w której powinny być wykonywane. Każda procedura obsługi opakowuje następną procedurę obsługi do momentu, aż końcowy HttpClientHandler wykona żądanie:

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

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

W poprzednim kodzie SampleHandler1 uruchamia 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. IHttpClientFactory Tworzy oddzielny zakres di dla każdej procedury 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ę o identyfikatorze : 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 rejestrowana 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 metody i używa IOperationScoped jej do ustawiania 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 plikuHttpRequestsSample przejdź do /Operation strony i odśwież stronę. Wartość zakresu żądania zmienia się dla każdego żądania, ale wartość zakresu procedury obsługi zmienia się tylko co 5 sekund.

Programy obsługi mogą zależeć od usług dowolnego zakresu. Usługi, od których zależą 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ń z procedurami obsługi komunikatów:

Korzystanie z programów 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 grodziowa 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. 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 zdefiniowano WaitAndRetryAsync 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 jest żądaniem HTTP GET, zostanie zastosowany 10-sekundowy limit czasu. W przypadku każdej innej metody HTTP jest używany limit czasu 30 sekund.

Dodawanie wielu procedur obsługi polly

Często zagnieżdżanie zasad polly:

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

W powyższym przykładzie:

  • Dodano dwie procedury obsługi.
  • Pierwsza procedura obsługi używa AddTransientHttpErrorPolicy metody w celu dodania zasad ponawiania. Żądania, które zakończyły się niepowodzeniem, są ponawiane do trzech razy.
  • Drugie AddTransientHttpErrorPolicy wywołanie dodaje zasady wyłącznika. Kolejne żą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. Na 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 ma następujące działanie:

  • 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

HttpClient Nowe wystąpienie jest zwracane za każdym razem, gdy CreateClient jest wywoływane w elemecie 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 zostać ponownie użyte z puli podczas tworzenia nowego HttpClient wystąpienia, jeśli jego okres istnienia nie wygasł.

Buforowanie procedur obsługi jest pożądane, ponieważ każda procedura obsługi zwykle zarządza własnymi podstawowymi połączeniami HTTP. Utworzenie 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 obsługi to dwie minuty. Wartość domyślną można zastąpić 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 wychodzące żądania 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 aktywności pojedynczego HttpClient wystąpienia przez długi czas jest typowym wzorcem używanym przed utworzeniem klasy IHttpClientFactory. Ten wzorzec staje się zbędny po przeprowadzeniu migracji do programu IHttpClientFactory.

Alternatywy dla IHttpClientFactory

Użycie 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 rowerowe 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 aplikacji 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 z systemem 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ń. W żądaniu komunikaty są rejestrowane, zanim wszystkie inne programy obsługi w potoku przetworzyły je. 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 dzieje się tak 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 programy obsługi potoków. 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. Metoda ConfigurePrimaryHttpMessageHandler rozszerzenia może służyć do definiowania 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 puli HttpMessageHandler powoduje CookieContainer udostępnianie obiektów. Nieprzewidziane CookieContainer udostępnianie obiektów często powoduje niepoprawny kod. W przypadku aplikacji wymagających wartości cookies należy wziąć pod uwagę jedną z następujących kwestii:

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

Wywołaj, ConfigurePrimaryHttpMessageHandler aby wyłączyć automatyczną cookie obsługę:

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 jest żądana od di, który z kolei żąda wystąpienia klasy IHttpClientFactory.
  • GitHubService używa IHttpClientFactory polecenia do utworzenia wystąpienia klasy HttpClient, 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 propagujące nagłówki HTTP z żądania przychodzącego do żądań wychodzącychHttpClient. 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, w tym dodanych nagłówków.

Dodatkowe zasoby

Przez Kirka Larkina, Steve'a Gordona, Glenna Condrona i Ryana Nowaka.

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. Domyślny klient można zarejestrować w celu uzyskania dostępu ogólnego.
  • Koduje koncepcję wychodzącego oprogramowania pośredniczącego poprzez delegowanie programów obsługi w programie HttpClient. Udostępnia rozszerzenia oprogramowania pośredniczącego opartego na usłudze Polly, które umożliwiają delegowanie 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 okresami HttpClient istnienia.
  • Dodaje konfigurowalne środowisko rejestrowania (za pośrednictwem ILogger) dla wszystkich żądań wysyłanych przez 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 Json.NET elementów i ReadAsAsync<T>użyj selektora wersji, aby wybrać wersję 2.x tego tematu.

Konsumpcji

Istnieje kilka sposobów IHttpClientFactory użycia w aplikacji:

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 może IHttpClientFactory być wymagane przy użyciu wstrzykiwania zależności (DI). Poniższy kod używa metody IHttpClientFactory 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 takich jak 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 do CreateClient.

Nazwani klienci

Nazwani klienci są dobrym wyborem, gdy:

  • Aplikacja wymaga wielu odrębnych zastosowań programu HttpClient.
  • Wiele HttpClientz nich ma inną konfigurację.

Konfigurację nazwanego 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 z:

  • 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 HttpClient .
  • Akcja konfiguracji jest wywoływana.

Aby utworzyć nazwanego klienta, przekaż jego nazwę do CreateClientelementu :

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.

Typizowane klienci

Typowi 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 określonego elementu i interakcji z nim HttpClient. Na przykład można użyć pojedynczego klienta typizowanego:
    • W przypadku pojedynczego punktu końcowego zaplecza.
    • Aby hermetyzować całą logikę do obsługi punktu końcowego.
  • Praca z di i można wstrzyknąć 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 ma następujące działanie:

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

Można utworzyć metody specyficzne dla interfejsu API, które uwidaczniają HttpClient funkcjonalność. Na przykład metoda hermetyzuje kod w GetAspNetDocsIssues celu pobrania otwartych problemów.

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

services.AddHttpClient<GitHubService>();

Typizowanego klienta jest rejestrowany jako przejściowy z di. W poprzednim kodzie rejestruje AddHttpClient 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 GitHubServiceklasy , przekazując w wystąpieniu obiektu do 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ę 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");
});

Element HttpClient można hermetyzować w ramach typizowanego klienta. Zamiast uwidaczniać je jako właściwość, zdefiniuj metodę, która wywołuje HttpClient 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 metody HttpClient jest uzyskiwany przez metodę publiczną GetRepos .

Wygenerowani klienci

IHttpClientFactory można używać w połączeniu z bibliotekami innych firm, takimi jak Refit. Refit to REST biblioteka platformy .NET. Konwertuje interfejsy REST 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 Dopasuj do generowania 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
  • PUT
  • DELETE
  • 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 StringContent elementu w celu spakowania serializowanego JSmodułu ON do wysyłania w treści żądania HTTP.
  • Wywołania PostAsync w celu wysł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ługiwanej 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 DeleteAsyncmetodę DeleteItemAsync . Ponieważ żądania HTTP DELETE zwykle nie zawierają treści, DeleteAsync metoda nie zapewnia przeciążenia, które akceptuje wystąpienie HttpContentklasy .

Aby dowiedzieć się więcej o korzystaniu z 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 razem dla wychodzących żądań HTTP. IHttpClientFactory:

  • Upraszcza definiowanie procedur obsługi do zastosowania dla każdego nazwanego klienta.
  • Obsługuje rejestrację i tworzenie łańcucha 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 ASP.NET Core.
    • Zapewnia 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:

  • Wyprowadzanie z klasy DelegatingHandler.
  • Zastąpij SendAsync. Wykonaj kod przed przekazaniem żądania do następnej procedury 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 sprawdza, czy X-API-KEY nagłówek znajduje się w żądaniu. Jeśli X-API-KEY brakuje, BadRequest jest zwracany.

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

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 ValidateHeaderHandler element jest zarejestrowany za pomocą di. Po zarejestrowaniu AddHttpMessageHandler można wywołać metodę , przekazując typ programu obsługi.

Wiele procedur obsługi można zarejestrować w kolejności, w której powinny być wykonywane. Każda procedura obsługi opakowuje następną procedurę obsługi do momentu, aż końcowy 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. IHttpClientFactory Tworzy oddzielny zakres di dla każdej procedury 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ę o identyfikatorze : 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 rejestrowana 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 metody i używa IOperationScoped jej do ustawiania 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 obszarze pobieraniaHttpRequestsSample przejdź do /Operation strony i odśwież stronę. Wartość zakresu żądania zmienia się dla każdego żądania, ale wartość zakresu procedury obsługi zmienia się tylko co 5 sekund.

Programy obsługi mogą zależeć od usług dowolnego zakresu. Usługi, od których zależą 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ń z procedurami obsługi komunikatów:

Korzystanie z programów 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 grodziowa 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. 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 zdefiniowano WaitAndRetryAsync 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 jest żądaniem HTTP GET, zostanie zastosowany 10-sekundowy limit czasu. W przypadku każdej innej metody HTTP jest używany limit czasu 30 sekund.

Dodawanie wielu procedur obsługi polly

Często zagnieżdżanie zasad polly:

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

W powyższym przykładzie:

  • Dodano dwie procedury obsługi.
  • Pierwsza procedura obsługi używa AddTransientHttpErrorPolicy metody w celu dodania zasad ponawiania. Żądania, które zakończyły się niepowodzeniem, są ponawiane do trzech razy.
  • Drugie AddTransientHttpErrorPolicy wywołanie dodaje zasady wyłącznika. Kolejne żą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

HttpClient Nowe wystąpienie jest zwracane za każdym razem, gdy CreateClient jest wywoływane w elemecie 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 zostać ponownie użyte z puli podczas tworzenia nowego HttpClient wystąpienia, jeśli jego okres istnienia nie wygasł.

Buforowanie procedur obsługi jest pożądane, ponieważ każda procedura obsługi zwykle zarządza własnymi podstawowymi połączeniami HTTP. Utworzenie 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 obsługi to dwie minuty. Wartość domyślną można zastąpić 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 wychodzące żądania 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 aktywności pojedynczego HttpClient wystąpienia przez długi czas jest typowym wzorcem używanym przed utworzeniem klasy IHttpClientFactory. Ten wzorzec staje się zbędny po przeprowadzeniu migracji do programu IHttpClientFactory.

Alternatywy dla IHttpClientFactory

Użycie 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 rowerowe 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 aplikacji SocketsHttpHandler , gdy aplikacja zostanie uruchomiona i użyj jej do końca życia aplikacji.
  • Skonfiguruj PooledConnectionLifetime do odpowiedniej wartości 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. Udostępnianie zapobiega wyczerpaniu 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 wymagających cookiewartości należy wziąć pod uwagę następujące kwestie:

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

Wywołanie ConfigurePrimaryHttpMessageHandler w 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 z sufiksem LogicalHandler występują poza potokiem obsługi żądań. W żądaniu komunikaty są rejestrowane, zanim wszystkie inne programy obsługi w potoku przetworzyły je. 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 odbywa się 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 poza potokiem 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.

Obiekt IHttpClientBuilder jest zwracany podczas dodawania nazwanych lub wpisanych klientów. Metoda ConfigurePrimaryHttpMessageHandler rozszerzenia może służyć do definiowania 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óra jest używana do utworzenia obiektu 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.
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 propagujące nagłówki 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 Kirka Larkina, Steve'a Gordona, Glenna Condrona i Ryana Nowaka.

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. Domyślny klient można zarejestrować w celu uzyskania dostępu ogólnego.
  • Koduje koncepcję wychodzącego oprogramowania pośredniczącego poprzez delegowanie programów obsługi w programie HttpClient. Udostępnia rozszerzenia oprogramowania pośredniczącego opartego na usłudze Polly, które umożliwiają delegowanie 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 okresami HttpClient istnienia.
  • Dodaje konfigurowalne środowisko rejestrowania (za pośrednictwem ILogger) dla wszystkich żądań wysyłanych przez 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 Json.NET elementów i ReadAsAsync<T>użyj selektora wersji, aby wybrać wersję 2.x tego tematu.

Konsumpcji

Istnieje kilka sposobów IHttpClientFactory użycia w aplikacji:

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 może IHttpClientFactory być wymagane przy użyciu wstrzykiwania zależności (DI). Poniższy kod używa metody IHttpClientFactory 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 takich jak 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 do CreateClient.

Nazwani klienci

Nazwani klienci są dobrym wyborem, gdy:

  • Aplikacja wymaga wielu odrębnych zastosowań programu HttpClient.
  • Wiele HttpClientz nich ma inną konfigurację.

Konfigurację nazwanego 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 z:

  • 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 HttpClient .
  • Akcja konfiguracji jest wywoływana.

Aby utworzyć nazwanego klienta, przekaż jego nazwę do CreateClientelementu :

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.

Typizowane klienci

Typowi 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 jedną lokalizację do skonfigurowania i interakcji z określonym HttpClientelementem . Na przykład można użyć pojedynczego typu klienta:
    • W przypadku pojedynczego punktu końcowego zaplecza.
    • Aby hermetyzować całą logikę dotyczącą punktu końcowego.
  • Praca z di i może być wstrzykiwana tam, gdzie jest to wymagane w aplikacji.

Typowany klient akceptuje HttpClient parametr w jego 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 ma następujące działanie:

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

Metody specyficzne dla interfejsu API można utworzyć, aby uwidocznić HttpClient funkcjonalność. 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 GitHubService, przekazując wystąpienie do 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ę typizowanego klienta można określić podczas rejestracji w programie Startup.ConfigureServices, a nie w konstruktorze typu 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 typowym kliencie. Zamiast uwidaczniać go jako właściwość, zdefiniuj metodę, która wywołuje HttpClient 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 HttpClient element jest przechowywany w polu prywatnym. Dostęp do metody HttpClient jest używany przez 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 metodę RestService, używając metody HttpClient do wykonywania zewnętrznych wywołań HTTP.

Interfejs i odpowiedź są definiowane 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 przy użyciu funkcji 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();
}

W razie potrzeby można używać zdefiniowanego interfejsu z implementacją zapewnianą 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
  • PUT
  • DELETE
  • 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 parametr JSna TodoItem WŁ. przy użyciu polecenia System.Text.Json. Używa to wystąpienia programu JsonSerializerOptions do konfigurowania procesu serializacji.
  • Tworzy wystąpienie do StringContent spakowania serializowanego JSwłączonego do wysyłania w treści żądania HTTP.
  • Wywołania PostAsync w celu wysł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 przedstawiono żą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 przedstawiono żą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 DeleteAsyncmetodę DeleteItemAsync . Ponieważ żądania HTTP DELETE zwykle nie zawierają treści, DeleteAsync metoda nie zapewnia przeciążenia, które akceptuje wystąpienie klasy HttpContent.

Aby dowiedzieć się więcej o korzystaniu z 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 do zastosowania dla każdego nazwanego klienta.
  • Obsługuje rejestrację i tworzenie łańcucha wielu procedur obsługi w celu utworzenia potoku oprogramowania pośredniczącego żądań wychodzących. Każda z tych procedur obsługi może wykonać pracę przed żądaniem wychodzącym i po nim. Ten wzorzec:
    • Jest podobny do potoku oprogramowania pośredniczącego dla ruchu przychodzącego w ASP.NET Core.
    • Zapewnia mechanizm zarządzania problemami krzyżowymi dotyczącymi żądań HTTP, takich jak:
      • Buforowanie
      • obsługa błędów
      • Serializacji
      • rejestrowanie

Aby utworzyć program obsługi delegowania:

  • Pochodzi z DelegatingHandlerelementu .
  • Zastąpij SendAsync. Wykonaj kod przed przekazaniem żądania do następnej procedury 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 sprawdza, czy X-API-KEY nagłówek znajduje się w żądaniu. Jeśli X-API-KEY brakuje, BadRequest jest zwracany.

Do konfiguracji programu można dodać więcej niż jedną procedurę HttpClient obsługi za 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 ValidateHeaderHandler element jest zarejestrowany w usłudze DI. Po zarejestrowaniu AddHttpMessageHandler można wywołać metodę , przekazując typ programu obsługi.

Wiele procedur obsługi można zarejestrować w kolejności wykonywania. Każda procedura obsługi opakowuje następną procedurę obsługi do momentu HttpClientHandler zakończenia wykonania żądania:

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 programie 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. IHttpClientFactory Tworzy oddzielny zakres di dla każdej procedury 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 rejestrowana w usłudze 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();
}

Poniższa 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 pobieraniuHttpRequestsSample przejdź do /Operation strony i odśwież stronę. Wartość zakresu żądania zmienia się dla każdego żądania, ale wartość zakresu obsługi zmienia się tylko co 5 sekund.

Programy obsługi mogą zależeć od usług z dowolnego zakresu. Usługi obsługiwane przez programy obsługi zależą od tego, są usuwane po usunięciu programu obsługi.

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

Korzystanie z programów obsługi opartych na usłudze Polly

IHttpClientFactory integruje się z biblioteką innej firmy Polly. Polly to kompleksowa biblioteka obsługi błędów przejściowych dla platformy .NET. Dzięki temu deweloperzy mogą wyrażać zasady, takie jak ponawianie, wyłącznik, limit czasu, izolacja bulkhead i rezerwowy 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. 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 nieudane 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 programów 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 jest żądaniem HTTP GET, zostanie zastosowany 10-sekundowy limit czasu. W przypadku każdej innej metody HTTP jest używany limit czasu 30 sekund.

Dodawanie wielu programów obsługi polly

Często należy zagnieżdżać zasady Polly:

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

W powyższym przykładzie:

  • Dodano dwie procedury obsługi.
  • Pierwsza procedura obsługi używa AddTransientHttpErrorPolicy polecenia do dodawania zasad ponawiania prób. Żą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 przy użyciu elementu 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 temat IHttpClientFactory integracji usługi 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 IHttpClientFactorypolecenie . 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 zostać ponownie użyte z puli podczas tworzenia nowego HttpClient wystąpienia, jeśli jego okres istnienia nie wygasł.

Buforowanie procedur 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 procedury obsługi to dwie minuty. Wartość domyślna może być zastępowana według 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 polecenia Dispose. IHttpClientFactory śledzi i usuwa zasoby używane przez HttpClient wystąpienia.

Utrzymywanie pojedynczego HttpClient wystąpienia przy życiu przez długi czas jest typowym wzorcem używanym przed utworzeniem elementu 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 rowerowe 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 na całe życie 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 z systemem DNS.

CookieS

Wystąpienia w puli HttpMessageHandler powoduje CookieContainer udostępnianie obiektów. Nieprzewidziane CookieContainer udostępnianie obiektów często powoduje niepoprawny kod. W przypadku aplikacji wymagających wartości cookies należy wziąć pod uwagę jedną z następujących kwestii:

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

Wywołaj, ConfigurePrimaryHttpMessageHandler aby wyłączyć automatyczną cookie obsługę:

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ń. W żądaniu komunikaty są rejestrowane, zanim wszystkie inne programy obsługi w potoku przetworzyły je. 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 dzieje się tak 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 programy obsługi potoków. 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. Metoda ConfigurePrimaryHttpMessageHandler rozszerzenia może służyć do definiowania 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óra jest używana 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 propagujące nagłówki 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 ona następujące korzyści:

  • Zapewnia centralną lokalizację nazewnictwa i konfigurowania wystąpień logicznych HttpClient. Na przykład można zarejestrować i skonfigurować klienta github w celu uzyskania dostępu do usługi GitHub. Domyślny klient może być zarejestrowany w innych celach.
  • Koduje koncepcję wychodzącego oprogramowania pośredniczącego poprzez delegowanie programów obsługi w systemie HttpClient i udostępnia rozszerzenia oprogramowania pośredniczącego opartego na polly, aby skorzystać z tego.
  • 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 okresami HttpClient istnienia.
  • Dodaje konfigurowalne środowisko rejestrowania (za pośrednictwem ILogger) dla wszystkich żądań wysyłanych przez klientów utworzonych przez fabrykę.

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

Wymagania wstępne

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

Konsumpcji

Istnieje kilka sposobów IHttpClientFactory użycia w aplikacji:

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

Podstawowy sposób użycia

Można IHttpClientFactory 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 w dowolnym miejscu, które można wstrzyknąć za pomocą wstrzykiwania 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 wpływu na sposób HttpClient użycia. W miejscach, w których HttpClient wystąpienia są obecnie tworzone, zastąp te wystąpienia wywołaniem .CreateClient

Nazwani klienci

Jeśli aplikacja wymaga wielu odrębnych zastosowań programu HttpClient, z których każda ma inną konfigurację, należy użyć nazwanych klientów. Konfigurację nazwanego 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ą — czyli podstawowy adres i dwa nagłówki wymagane do pracy z interfejsem API usługi GitHub.

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

Aby korzystać z nazwanego klienta, parametr ciągu można przekazać do CreateClient. Określ nazwę klienta do utworzenia:

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 przejść tylko ścieżkę, ponieważ używany jest adres podstawowy skonfigurowany dla klienta.

Klienci typizowane

Klienci typizowane:

  • 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 określonego elementu i interakcji z nim HttpClient. Na przykład jeden typ klienta może być używany dla pojedynczego punktu końcowego zaplecza i hermetyzować całą logikę zajmującą się tym punktem końcowym.
  • Współpracuj z di i można wstrzyknąć 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 udostępniany 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ń i analizowania najnowszych otwartych problemów z repozytorium GitHub.

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

services.AddHttpClient<GitHubService>();

Typizowanego klienta jest rejestrowany 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 w ramach typizowanego klienta. Zamiast uwidaczniać je jako właściwość, można podać metody publiczne, które wywołają HttpClient 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 platformy .NET. Konwertuje interfejsy REST 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 Dopasuj do generowania 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 Już ma koncepcję delegowania procedur obsługi, które mogą być połączone razem dla wychodzących żądań HTTP. Dzięki IHttpClientFactory temu można łatwo zdefiniować programy obsługi, które mają być stosowane dla każdego nazwanego klienta. Obsługuje rejestrację i tworzenie łańcucha 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 ASP.NET Core. Wzorzec zapewnia 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ę pochodną klasy .DelegatingHandler Zastąpij metodę SendAsync , aby wykonać kod przed przekazaniem żądania do następnej procedury 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 do konfiguracji programu można dodać co najmniej jedną procedurę obsługi 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 ValidateHeaderHandler element jest zarejestrowany za pomocą di. Program obsługi musi być zarejestrowany w di jako usługa przejściowa, nigdy o określonym zakresie. Jeśli program obsługi jest zarejestrowany jako usługa o określonym zakresie, a wszystkie usługi, od których zależy program obsługi, są możliwe do likwidacji:

  • Usługi programu obsługi mogą zostać usunięte 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 procedury obsługi.

Wiele procedur obsługi można zarejestrować w kolejności, w której powinny być wykonywane. Każda procedura obsługi opakowuje następną procedurę obsługi do momentu, aż końcowy 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ń z procedurami 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 programów 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 grodziowa 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 przy użyciu 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 zdefiniowano WaitAndRetryAsync 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 rozszerzenia, których można 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 jest żądaniem HTTP GET, zostanie zastosowany 10-sekundowy limit czasu. W przypadku każdej innej metody HTTP jest używany limit czasu 30 sekund.

Dodawanie wielu procedur 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 są dodawane dwa programy obsługi. Pierwszy używa AddTransientHttpErrorPolicy rozszerzenia do dodawania zasad ponawiania. Żądania, które zakończyły się niepowodzeniem, są ponawiane do trzech razy. Drugie wywołanie metody powoduje AddTransientHttpErrorPolicy dodanie zasad wyłącznika. Kolejne żą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 dwie 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

HttpClient Nowe wystąpienie jest zwracane za każdym razem, gdy CreateClient jest wywoływane w elemecie 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 zostać ponownie użyte z puli podczas tworzenia nowego HttpClient wystąpienia, jeśli jego okres istnienia nie wygasł.

Buforowanie procedur obsługi jest pożądane, ponieważ każda procedura obsługi zwykle zarządza własnymi podstawowymi połączeniami HTTP. Utworzenie 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ż otwarte połączenia na czas nieokreślony, co może uniemożliwić programowi obsługi reagowanie na zmiany DNS.

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

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

Usuwanie klienta nie jest wymagane. Usuwanie anuluje wychodzące żądania 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. Wystąpienia HttpClient mogą być zwykle traktowane jako obiekty platformy .NET, które nie wymagają usuwania.

Utrzymywanie aktywności pojedynczego HttpClient wystąpienia przez długi czas jest typowym wzorcem używanym przed utworzeniem klasy IHttpClientFactory. Ten wzorzec staje się zbędny po przeprowadzeniu migracji do programu IHttpClientFactory.

Alternatywy dla IHttpClientFactory

Użycie 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 rowerowe 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 aplikacji SocketsHttpHandler , gdy aplikacja zostanie uruchomiona i użyj jej do końca życia aplikacji.
  • Skonfiguruj PooledConnectionLifetime do odpowiedniej wartości 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. Udostępnianie zapobiega wyczerpaniu 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 wymagających cookiewartości należy wziąć pod uwagę następujące kwestie:

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

Wywołanie ConfigurePrimaryHttpMessageHandler w 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 z sufiksem LogicalHandler występują poza potokiem obsługi żądań. W żądaniu komunikaty są rejestrowane, zanim wszystkie inne programy obsługi w potoku przetworzyły je. 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 w kategorii System.Net.Http.HttpClient.MyNamedClient.ClientHandlerdziennika . W przypadku żądania odbywa się to po uruchomieniu wszystkich innych procedur 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 poza potokiem 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, jeśli jest to konieczne.

Konfigurowanie programu HttpMessageHandler

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

Obiekt IHttpClientBuilder jest zwracany podczas dodawania nazwanych lub wpisanych klientów. Metoda ConfigurePrimaryHttpMessageHandler rozszerzenia może służyć do definiowania 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óra jest używana do utworzenia obiektu HttpClient. HttpClient służy do pobierania strony internetowej.
  • Metoda usługi jest wykonywana GetPage 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 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ływanie się do obsługiwanego przez społeczność portu headerPropagation pakietu. program ASP.NET Core 3.1 lub nowszy obsługuje wersję 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