Żądania HTTP przy użyciu IHttpClientFactory w usłudze ASP.NET Core

A w tym skonstutuj się: SteveGordon,Bonn Condroni Steven Nowak.

Element IHttpClientFactory może być zarejestrowany i używany do konfigurowania i tworzenia HttpClient wystąpień w aplikacji. IHttpClientFactory Oferuje następujące korzyści:

  • Zapewnia centralną lokalizację nazewnictwa i konfigurowania HttpClient wystąpień logicznych. Na przykład można zarejestrować klienta o nazwie github i skonfigurować go do uzyskiwania dostępu do GitHub. Domyślnego klienta można zarejestrować w celu uzyskania dostępu ogólnego.
  • Codifs the concept of outgoing middleware via delegating handlers in HttpClient . Udostępnia rozszerzenia dla oprogramowania pośredniczącego opartego na polly, aby korzystać z zalet delegowania programów obsługi w programie HttpClient .
  • Zarządza pulą i okresem istnienia HttpClientMessageHandler bazowych wystąpień. Automatyczne zarządzanie pozwala uniknąć typowych problemów z systemem DNS (Domain Name System), które występują podczas ręcznego zarządzania HttpClient okresami istnienia.
  • Dodaje konfigurowalne środowisko rejestrowania (za pośrednictwem ILogger ) dla wszystkich żądań wysyłanych za pośrednictwem klientów utworzonych przez fabrykę.

Wyświetlanie lub pobieranie przykładowego kodu (jak pobrać plik).

Przykładowy kod w tej wersji tematu umożliwia System.Text.Json deserializację zawartości JSON zwracanej w odpowiedziach HTTP. W przypadku przykładów, które używają i , użyj selektora wersji, Json.NET ReadAsAsync<T> aby wybrać wersję 2.x tego tematu.

Konsumpcji

Istnieje kilka IHttpClientFactory sposobów, których można użyć w aplikacji:

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

Podstawowy sposób użycia

IHttpClientFactory można zarejestrować przez wywołanie funkcji AddHttpClient :

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddHttpClient();
        // Remaining code deleted for brevity.

Żądanie IHttpClientFactory można zażądać przy użyciu wstrzykiwania zależności (DI, dependency injection). Poniższy kod używa 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 funkcji podobnej w poprzednim przykładzie jest dobrym sposobem refaktoryzacji istniejącej aplikacji. Nie ma to wpływu na HttpClient sposób ich używać. W HttpClient miejscach, w których wystąpienia są tworzone w istniejącej aplikacji, zastąp te wystąpienia wywołaniami CreateClient .

Nazwani klienci

Nazwani klienci są dobrym wyborem w przypadku:

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

Konfigurację dla HttpClient nazwanego można określić podczas rejestracji w pliku 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 przy użyciu:

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

CreateClient

Za każdym CreateClient razem jest wywoływana:

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

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

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.

Typieni klienci

Typieni klienci:

  • Zapewnij takie same możliwości jak nazwani klienci bez konieczności używania ciągów jako kluczy.
  • Zapewnia funkcje IntelliSense i pomoc kompilatora podczas korzystania z klientów.
  • Podaj pojedynczą lokalizację do skonfigurowania określonego serwera i interakcji z HttpClient nim. Można na przykład użyć jednego typiowego klienta:
    • Dla pojedynczego punktu końcowego zaplecza.
    • Hermetyzowanie całej logiki związanej z punktem końcowym.
  • Praca z wstrzykiwanym wstrzykiwanym w razie potrzeby w aplikacji.

Typowany klient akceptuje parametr HttpClient 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 przenoszony do klienta z typem.
  • Obiekt HttpClient jest ujawniony jako właściwość publiczna.

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

Poniższy kod wywołuje w AddHttpClient pliku , aby zarejestrować Startup.ConfigureServices typifikowana klasę klienta:

services.AddHttpClient<GitHubService>();

Typowany klient jest zarejestrowany jako przejściowy w di. W poprzednim kodzie rejestruje AddHttpClient się jako GitHubService usługę przejściową. Ta rejestracja używa metody fabryki w celu:

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

Typowany klient można wstrzykiwać 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ę typinego klienta można określić podczas rejestracji w programie , a nie w Startup.ConfigureServices konstruktorze klienta z typem:

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

Typ HttpClient może być hermetyzowany w obrębie typyzowanego klienta. Zamiast ujawniać 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 plik jest HttpClient przechowywany w polu prywatnym. Dostęp do HttpClient metody jest zapewniany za pomocą metody GetRepos publicznej.

Wygenerowani klienci

IHttpClientFactory Można go używać w połączeniu z bibliotekami innych firm, takimi jak Refit. Refit to biblioteka REST dla programu .NET. Konwertuje interfejsy API REST na interfejsy na żywo. Implementacja interfejsu jest generowana dynamicznie przez , RestService przy użyciu do wykonywania zewnętrznych HttpClient 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ć typizowany klient, używając funkcji Ponownego dopasowywu w celu wygenerowania implementacji:

public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpClient("hello", c =>
    {
        c.BaseAddress = new Uri("http://localhost:5000");
    })
    .AddTypedClient(c => Refit.RestService.For<IHelloClient>(c));

    services.AddControllers();
}

Zdefiniowany interfejs może być używane w razie potrzeby z implementacją zapewnianą przez di i ponownego dopasowywu:

[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 HTTP GET. HttpClient Obsługuje również inne czasowniki HTTP, w tym:

  • POST
  • PUT
  • DELETE
  • PATCH

Aby uzyskać pełną listę obsługiwanych zleceń HTTP, zobacz HttpMethod .

W poniższym przykładzie pokazano, jak wykonać żą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 metoda CreateItemAsync :

  • Serializuje parametr TodoItem do danych JSON przy użyciu funkcji System.Text.Json . Używa wystąpienia do JsonSerializerOptions skonfigurowania procesu serializacji.
  • Tworzy wystąpienie klasy StringContent , aby spakować serializowany kod JSON do wysyłania w treści żądania HTTP.
  • Wywołania PostAsync w celu wysłania zawartości JSON do określonego adresu URL. Jest to względny adres URL dodawany do adresu HttpClient.BaseAddress.
  • Wywołuje EnsureSuccessStatusCode wyjątek, jeśli kod stanu odpowiedzi nie wskazuje powodzenia.

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

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

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

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

    httpResponse.EnsureSuccessStatusCode();
}

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

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 DeleteItemAsync wywołuje metodę DeleteAsync . Ponieważ żądania HTTP DELETE zwykle nie zawierają treści, metoda nie zapewnia przeciążenia, które akceptuje DeleteAsync wystąpienie HttpContent klasy .

Aby dowiedzieć się więcej na temat używania różnych czasowników HTTP z HttpClient usługą , zobacz HttpClient .

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

HttpClient ma pojęcie delegowania programów obsługi, które mogą być połączone dla wychodzących żądań HTTP. IHttpClientFactory:

  • Upraszcza definiowanie procedur obsługi do zastosowania dla każdego nazwanego klienta.

  • Obsługuje rejestrację i tworzenie łańcuchów wielu programów obsługi w celu tworzenia potoku oprogramowania pośredniczącego żądania wychodzącego. Każdy z tych programów obsługi może wykonywać pracę przed żądaniem wychodzącym i po nim. Ten wzorzec:

    • Jest podobny do potoku przychodzącego oprogramowania pośredniczącego w ASP.NET Core.
    • Udostępnia mechanizm zarządzania redukcją problemów związanych z żądaniami HTTP, takich jak:
      • Buforowanie
      • obsługa błędów
      • Serializacji
      • rejestrowanie

Aby utworzyć program obsługi delegowania:

  • Pochodzi od DelegatingHandler .
  • Zastąp 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 zwracany jest .

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

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

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

    // Remaining code deleted for brevity.

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

Wiele programów obsługi można zarejestrować w kolejności, w których powinny zostać wykonane. Każda procedura obsługi opakowywuje następny program obsługi do momentu, gdy HttpClientHandler finalnie 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 funkcji di w pośredniczącego żądaniu wychodzącym

Podczas tworzenia nowej procedury obsługi delegowania używa funkcji DI do spełnienia parametrów IHttpClientFactory konstruktora programu obsługi. IHttpClientFactory Tworzy oddzielny zakres di dla każdego programu obsługi, co może prowadzić do zaskakującego zachowania, gdy program obsługi zużywa usługę w 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 nazwa, jest IOperationScoped rejestrowana w diuzynie przy użyciu okresu istnienia o 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 i używa polecenia IOperationScoped do ustawienia X-OPERATION-ID nagłówka 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 pliku HttpRequestsSample do pobrania], 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.

Procedury obsługi mogą zależeć od usług dowolnego zakresu. Usługi, od których zależą procedury obsługi, są usuwane, gdy procedura obsługi jest likwidowana.

Użyj jednej z następujących metod, aby udostępnić stan dla każdego żądania za pomocą programów obsługi komunikatów:

Korzystanie z programów obsługi opartych na ujmce Polly

IHttpClientFactoryIntegruje się z biblioteką innej firmy Polly. Polly to kompleksowa biblioteka odporności i obsługi błędów przejściowych dla programu .NET. Umożliwia ona deweloperom wyrażanie zasad, takich jak ponawianie, wyłącznik, limit czasu, izolacja grodziowa i rezerwowa, w sposób płynnie i bezpieczny wątkowo.

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

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

Błędy zwykle występują, gdy zewnętrzne wywołania HTTP są przejściowe. AddTransientHttpErrorPolicy umożliwia definicję zasad w celu obsługi błędów przejściowych. Zasady skonfigurowane przy AddTransientHttpErrorPolicy użyciu programu obsługują następujące odpowiedzi:

AddTransientHttpErrorPolicy zapewnia dostęp do obiektu PolicyBuilder skonfigurowanego 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ą ponoszone maksymalnie trzy razy z opóźnieniem 600 ms między próbami.

Dynamiczne wybieranie zasad

Dostępne są metody rozszerzenia służące do dodawania programów obsługi opartych na 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, stosowany jest 10-sekundowy limit czasu. W przypadku każdej innej metody HTTP jest używany limit czasu 30-sekundowy.

Dodawanie wielu programów obsługi polly

Często zagnieżdża się zasady polly:

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

W powyższym przykładzie:

  • Dodawane są dwie procedury obsługi.
  • Pierwsza procedura obsługi AddTransientHttpErrorPolicy używa polecenia w celu dodania zasad ponawiania. Żądania, które zakończyły się niepowodzeniem, są ponoszowane maksymalnie trzy razy.
  • Drugie wywołanie AddTransientHttpErrorPolicy dodaje zasady wyłącznika. Dalsze żądania zewnętrzne są blokowane na 30 sekund, jeśli 5 nieudanych prób ma miejsce 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 to zdefiniowanie ich raz i zarejestrowanie ich za pomocą obiektu 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 IHttpClientFactory na temat integracji z usługą Polly, zobacz witrynę typu wiki polly.

Zarządzanie httpClient i okres istnienia

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

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

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

Domyślny okres istnienia programu obsługi wynosi dwie minuty. Wartość domyślną można przesłonić 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 .NET, które nie wymagają usuwania. Usunięcie anuluje żądania wychodzące i gwarantuje, że danego wystąpienia nie można HttpClient użyć po wywołaniu . Dispose IHttpClientFactory śledzi i usuwa zasoby używane przez HttpClient wystąpienia.

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

Alternatywy dla IHttpClientFactory

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

  • Problemy z wyczerpaniem zasobów przez HttpMessageHandler pulę wystąpień.
  • Nieodświeżone problemy z systemem DNS HttpMessageHandler według wystąpień cyklicznych w regularnych odstępach czasu.

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

  • Utwórz wystąpienie klasy podczas uruchamiania aplikacji i używaj go SocketsHttpHandler przez całe życie aplikacji.
  • Skonfiguruj PooledConnectionLifetime odpowiednią wartość na podstawie czasu odświeżania DNS.
  • Tworzenie HttpClient wystąpień przy użyciu new HttpClient(handler, disposeHandler: false) zgodnie z potrzebami.

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

  • Połączenia SocketsHttpHandler są udziałów między HttpClient wystąpieniami. Udostępnianie zapobiega wyczerpaniu gniazd.
  • Cykle SocketsHttpHandler połączeń zgodnie z , aby uniknąć PooledConnectionLifetime nieaktualnych problemów z systemem DNS.

CookieS

Wystąpienia w HttpMessageHandler puli skutkują CookieContainer współużytkami obiektów. Nieoczekiwane udostępnianie CookieContainer obiektów często skutkuje nieprawidłowym kodem. W przypadku aplikacji, które cookie wymagają s, rozważ jedną z tych usług:

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

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

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

Rejestrowanie

Klienci utworzeni za IHttpClientFactory pośrednictwem 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 rejestruje na przykład 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 przed przetworzeniem go przez inne programy obsługi w potoku. W odpowiedzi komunikaty są rejestrowane po otrzymaniu odpowiedzi przez inne procedury 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 programów obsługi i bezpośrednio przed jego wysłaniem. W odpowiedzi to rejestrowanie zawiera stan odpowiedzi, zanim przejdzie z powrotem przez potok procedury 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 kodu stanu odpowiedzi.

Włączenie nazwy klienta w kategorii dziennika umożliwia filtrowanie dzienników dla określonych nazwanych klientów.

Konfigurowanie httpMessageHandler

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

Element IHttpClientBuilder jest zwracany podczas dodawania klientów nazwanych lub wpisanych. Metoda ConfigurePrimaryHttpMessageHandler rozszerzenia może służyć do definiowania delegata. Delegat służy do tworzenia i konfigurowania podstawowego obiektu 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 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 ogólnego hosta.
  • MyService tworzy wystąpienie fabryki klienta z usługi , które jest używane do tworzenia obiektu HttpClient . HttpClient Służy do pobierania strony internetowej.
  • Main Tworzy zakres, aby wykonać metodę usługi i zapisać pierwsze 500 znaków zawartości strony GetPage 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 ASP.NET Core pośredniczące do propagowania nagłówków HTTP z żądania przychodzącego do wychodzących żądań klienta HTTP. Aby użyć propagacji nagłówka:

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

  • Skonfiguruj oprogramowanie pośredniczące i HttpClient w programie Startup :

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers();
    
        services.AddHttpClient("MyForwardingClient").AddHeaderPropagation();
        services.AddHeaderPropagation(options =>
        {
            options.Headers.Add("X-TraceId");
        });
    }
    
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
    
        app.UseHttpsRedirection();
    
        app.UseHeaderPropagation();
    
        app.UseRouting();
    
        app.UseAuthorization();
    
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }
    
  • Klient zawiera skonfigurowane nagłówki dla żądań wychodzących:

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

Dodatkowe zasoby

A w tym skonstutuj się: SteveGordon,Bonn Condroni Steven Nowak.

Element IHttpClientFactory może być zarejestrowany i używany do konfigurowania i tworzenia HttpClient wystąpień w aplikacji. IHttpClientFactory Oferuje następujące korzyści:

  • Zapewnia centralną lokalizację nazewnictwa i konfigurowania HttpClient wystąpień logicznych. Na przykład można zarejestrować klienta o nazwie github i skonfigurować go do uzyskiwania dostępu do GitHub. Domyślnego klienta można zarejestrować w celu uzyskania dostępu ogólnego.
  • Codifs the concept of outgoing middleware via delegating handlers in HttpClient . Udostępnia rozszerzenia dla oprogramowania pośredniczącego opartego na polly, aby korzystać z zalet delegowania programów obsługi w programie HttpClient .
  • Zarządza pulą i okresem istnienia HttpClientMessageHandler bazowych wystąpień. Automatyczne zarządzanie pozwala uniknąć typowych problemów z systemem DNS (Domain Name System), które występują podczas ręcznego zarządzania HttpClient okresami istnienia.
  • Dodaje konfigurowalne środowisko rejestrowania (za pośrednictwem ILogger ) dla wszystkich żądań wysyłanych za pośrednictwem klientów utworzonych przez fabrykę.

Wyświetlanie lub pobieranie przykładowego kodu (jak pobrać plik).

Przykładowy kod w tej wersji tematu umożliwia System.Text.Json deserializację zawartości JSON zwracanej w odpowiedziach HTTP. W przypadku przykładów, które używają i , użyj selektora wersji, Json.NET ReadAsAsync<T> aby wybrać wersję 2.x tego tematu.

Konsumpcji

Istnieje kilka IHttpClientFactory sposobów, których można użyć w aplikacji:

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

Podstawowy sposób użycia

IHttpClientFactory można zarejestrować przez wywołanie funkcji AddHttpClient :

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddHttpClient();
        // Remaining code deleted for brevity.

Żądanie IHttpClientFactory można zażądać przy użyciu wstrzykiwania zależności (DI, dependency injection). Poniższy kod używa 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 funkcji podobnej w poprzednim przykładzie jest dobrym sposobem refaktoryzacji istniejącej aplikacji. Nie ma to wpływu na HttpClient sposób ich używać. W HttpClient miejscach, w których wystąpienia są tworzone w istniejącej aplikacji, zastąp te wystąpienia wywołaniami CreateClient .

Nazwani klienci

Nazwani klienci są dobrym wyborem w przypadku:

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

Konfigurację dla HttpClient nazwanego można określić podczas rejestracji w pliku 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 przy użyciu:

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

CreateClient

Za każdym CreateClient razem jest wywoływana:

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

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

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.

Typieni klienci

Typieni klienci:

  • Zapewnij te same możliwości co nazwani klienci bez konieczności używania ciągów jako kluczy.
  • Zapewnia pomoc dla funkcji IntelliSense i kompilatora podczas korzystania z klientów.
  • Podaj pojedynczą lokalizację do skonfigurowania określonego serwera i interakcji z HttpClient nim. Na przykład można użyć jednego klienta z typem:
    • Dla pojedynczego punktu końcowego zaplecza.
    • Hermetyzowanie całej logiki związanej z punktem końcowym.
  • Praca z wstrzykiwanym wstrzykiwanym di i może być wstrzykiwana tam, gdzie jest to wymagane w aplikacji.

Typowany klient akceptuje parametr HttpClient w konstruktorze:

public class GitHubService
{
    public HttpClient Client { get; }

    public GitHubService(HttpClient client)
    {
        client.BaseAddress = new Uri("https://api.github.com/");
        // GitHub API versioning
        client.DefaultRequestHeaders.Add("Accept",
            "application/vnd.github.v3+json");
        // GitHub requires a user-agent
        client.DefaultRequestHeaders.Add("User-Agent",
            "HttpClientFactory-Sample");

        Client = client;
    }

    public async Task<IEnumerable<GitHubIssue>> GetAspNetDocsIssues()
    {
        var response = await Client.GetAsync(
            "/repos/dotnet/AspNetCore.Docs/issues?state=open&sort=created&direction=desc");

        response.EnsureSuccessStatusCode();

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

Jeśli chcesz zobaczyć komentarze do kodu przetłumaczone na języki inne niż angielski, daj nam znać w tym problemie z dyskusją w serwisie GitHub.

Powyższy kod ma następujące działanie:

  • Konfiguracja jest przenoszony do klienta z typem.
  • Obiekt HttpClient jest ujawniony jako właściwość publiczna.

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

Poniższy kod wywołuje AddHttpClient klasę w celu Startup.ConfigureServices zarejestrowania typifikatora klasy klienta:

services.AddHttpClient<GitHubService>();

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

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

Wpisanego klienta można wstrzykiwać 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ę klienta typiętego można określić podczas rejestracji w , a nie w konstruktorze klienta Startup.ConfigureServices z typem:

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

Typ HttpClient może być hermetyzowany w obrębie klienta typiętego. Zamiast ujawniać 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 plik HttpClient jest przechowywany w polu prywatnym. Dostęp do HttpClient metody jest zapewniany za pomocą metody GetRepos publicznej.

Wygenerowani klienci

IHttpClientFactory Można go używać w połączeniu z bibliotekami innych firm, takimi jak Refit. Ponowne dopasowanie to biblioteka REST dla programu .NET. Konwertuje interfejsy API REST na interfejsy na żywo. Implementacja interfejsu jest generowana dynamicznie przez RestService element , przy użyciu funkcji do wykonywania zewnętrznych HttpClient 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ć typizowany klient, używając funkcji Ponownego dopasowywu w celu wygenerowania implementacji:

public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpClient("hello", c =>
    {
        c.BaseAddress = new Uri("http://localhost:5000");
    })
    .AddTypedClient(c => Refit.RestService.For<IHelloClient>(c));

    services.AddControllers();
}

Zdefiniowany interfejs może być używane w razie potrzeby z implementacją zapewnianą przez di i ponownego dopasowywuj:

[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 wykonać żą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 metoda CreateItemAsync :

  • Serializuje parametr TodoItem do JSON przy użyciu System.Text.Json . Używa to wystąpienia do JsonSerializerOptions skonfigurowania procesu serializacji.
  • Tworzy wystąpienie klasy StringContent , aby spakować serializowany kod JSON do wysyłania w treści żądania HTTP.
  • Wywołania PostAsync w celu wysłania zawartości JSON na określony adres URL. Jest to względny adres URL dodawany do adresu HttpClient.BaseAddress.
  • Wywołuje EnsureSuccessStatusCode wyjątek, jeśli kod stanu odpowiedzi nie wskazuje powodzenia.

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

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

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

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

    httpResponse.EnsureSuccessStatusCode();
}

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

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 DeleteItemAsync wywołuje metodę DeleteAsync . Ponieważ żądania HTTP DELETE zwykle nie zawierają treści, metoda nie zapewnia przeciążenia, które akceptuje DeleteAsync wystąpienie HttpContent klasy .

Aby dowiedzieć się więcej na temat używania różnych czasowników HTTP z HttpClient usługą , zobacz HttpClient .

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

HttpClient program ma pojęcie delegowania programów obsługi, które mogą być połączone dla wychodzących żądań HTTP. IHttpClientFactory:

  • Upraszcza definiowanie procedur obsługi do zastosowania dla każdego nazwanego klienta.

  • Obsługuje rejestrację i tworzenie łańcuchów wielu programów obsługi w celu tworzenia potoku oprogramowania pośredniczącego żądań wychodzących. Każdy z tych programów obsługi może wykonywać pracę przed żądaniem wychodzącym i po nim. Ten wzorzec:

    • Jest podobny do potoku przychodzącego oprogramowania pośredniczącego w ASP.NET Core.
    • Udostępnia mechanizm do zarządzania wszystkimi problemami w zakresie żądań HTTP, takimi jak:
      • Buforowanie
      • obsługa błędów
      • Serializacji
      • rejestrowanie

Aby utworzyć program obsługi delegowania:

  • Pochodzi od DelegatingHandler .
  • Zastąp 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 zwracana.

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

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

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

    // Remaining code deleted for brevity.

W poprzednim kodzie jest ValidateHeaderHandler zarejestrowany w di. Po zarejestrowaniu AddHttpMessageHandler można ją nazwać, przekazując typ dla programu obsługi.

Wiele programów obsługi można zarejestrować w kolejności, w których powinny zostać wykonane. Każda procedura obsługi opakowywuje następny program obsługi do momentu, gdy HttpClientHandler finalnie 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 funkcji DI w pośredniczącej żądaniu wychodzącym

Podczas tworzenia nowej procedury obsługi delegowania używa funkcji DI do IHttpClientFactory spełnienia parametrów konstruktora programu obsługi. IHttpClientFactoryTworzy oddzielny zakres di dla każdego programu obsługi, co może prowadzić do zaskakującego zachowania, gdy program obsługi zużywa usługę w 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 nazwa, jest IOperationScoped zarejestrowana w di-di przy użyciu okresu istnienia o 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 i używa polecenia IOperationScoped do ustawienia X-OPERATION-ID nagłówka 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 pliku HttpRequestsSample do pobraniaprzejdź do /Operation strony i odśwież ją. Wartość zakresu żądania zmienia się dla każdego żądania, ale wartość zakresu procedury obsługi zmienia się tylko co 5 sekund.

Procedury obsługi mogą zależeć od usług dowolnego zakresu. Usługi, od których zależą procedury obsługi, są usuwane podczas usuwania programu obsługi.

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

Korzystanie z programów obsługi opartych na uciece Polly

IHttpClientFactoryIntegruje się z biblioteką innej firmy Polly. Polly to kompleksowa biblioteka odporności i obsługi błędów przejściowych dla programu .NET. Umożliwia ona deweloperom wyrażanie zasad, takich jak ponawianie, wyłącznik, limit czasu, izolacja grodziowa i rezerwowa, w sposób płynnie i bezpieczny wątkowo.

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

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

Błędy zwykle występują, gdy zewnętrzne wywołania HTTP są przejściowe. AddTransientHttpErrorPolicy umożliwia definicję zasad w celu obsługi błędów przejściowych. Zasady skonfigurowane przy AddTransientHttpErrorPolicy użyciu programu obsługują następujące odpowiedzi:

AddTransientHttpErrorPolicy zapewnia dostęp do obiektu PolicyBuilder skonfigurowanego 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ą ponoszone maksymalnie trzy razy z opóźnieniem 600 ms między próbami.

Dynamiczne wybieranie zasad

Dostępne są metody rozszerzenia służące do dodawania programów obsługi opartych na 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, stosowany jest 10-sekundowy limit czasu. W przypadku każdej innej metody HTTP jest używany limit czasu 30-sekundowy.

Dodawanie wielu programów obsługi polly

Często zagnieżdża się zasady polly:

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

W powyższym przykładzie:

  • Dodawane są dwie procedury obsługi.
  • Pierwsza procedura obsługi AddTransientHttpErrorPolicy używa polecenia w celu dodania zasad ponawiania. Żądania, które zakończyły się niepowodzeniem, są ponoszowane maksymalnie trzy razy.
  • Drugie wywołanie AddTransientHttpErrorPolicy dodaje zasady wyłącznika. Dalsze żądania zewnętrzne są blokowane na 30 sekund, jeśli 5 nieudanych prób ma miejsce 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 to zdefiniowanie ich raz i zarejestrowanie ich za pomocą obiektu 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 IHttpClientFactory na temat integracji z usługą Polly, zobacz witrynę typu wiki polly.

Zarządzanie httpClient i okres istnienia

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

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

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

Domyślny okres istnienia programu obsługi wynosi dwie minuty. Wartość domyślną można przesłonić 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 .NET, które nie wymagają usuwania. Usunięcie anuluje żądania wychodzące i gwarantuje, że danego wystąpienia nie można HttpClient użyć po wywołaniu . Dispose IHttpClientFactory śledzi i usuwa zasoby używane przez HttpClient wystąpienia.

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

Alternatywy dla IHttpClientFactory

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

  • Problemy z wyczerpaniem zasobów przez HttpMessageHandler pulę wystąpień.
  • Nieodświeżone problemy z systemem DNS HttpMessageHandler według wystąpień cyklicznych w regularnych odstępach czasu.

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

  • Utwórz wystąpienie klasy podczas uruchamiania aplikacji i używaj go SocketsHttpHandler przez całe życie aplikacji.
  • Skonfiguruj PooledConnectionLifetime odpowiednią wartość na podstawie czasu odświeżania DNS.
  • Tworzenie HttpClient wystąpień przy użyciu new HttpClient(handler, disposeHandler: false) zgodnie z potrzebami.

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

  • Połączenia SocketsHttpHandler są udziałów między HttpClient wystąpieniami. Udostępnianie zapobiega wyczerpaniu gniazd.
  • Cykle SocketsHttpHandler połączeń zgodnie z , aby uniknąć PooledConnectionLifetime nieaktualnych problemów z systemem DNS.

CookieS

Wystąpienia w HttpMessageHandler puli skutkują CookieContainer współużytkami obiektów. Nieoczekiwane udostępnianie CookieContainer obiektów często skutkuje nieprawidłowym kodem. W przypadku aplikacji, które cookie wymagają s, rozważ jedną z tych usług:

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

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

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

Rejestrowanie

Klienci utworzeni za IHttpClientFactory pośrednictwem 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 rejestruje na przykład 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 przed przetworzeniem go przez inne programy obsługi w potoku. W odpowiedzi komunikaty są rejestrowane po otrzymaniu odpowiedzi przez inne procedury 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 programów obsługi i bezpośrednio przed jego wysłaniem. W odpowiedzi to rejestrowanie zawiera stan odpowiedzi, zanim przejdzie z powrotem przez potok procedury 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 kodu stanu odpowiedzi.

Włączenie nazwy klienta w kategorii dziennika umożliwia filtrowanie dzienników dla określonych nazwanych klientów.

Konfigurowanie httpMessageHandler

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

Element IHttpClientBuilder jest zwracany podczas dodawania klientów nazwanych lub wpisanych. Metoda ConfigurePrimaryHttpMessageHandler rozszerzenia może służyć do definiowania delegata. Delegat służy do tworzenia i konfigurowania podstawowego obiektu 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 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 ogólnego hosta.
  • MyService tworzy wystąpienie fabryki klienta z usługi , które jest używane do tworzenia obiektu HttpClient . HttpClient Służy do pobierania strony internetowej.
  • Main Tworzy zakres, aby wykonać metodę usługi i zapisać pierwsze 500 znaków zawartości strony GetPage 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 ASP.NET Core pośredniczące do propagowania nagłówków HTTP z żądania przychodzącego do wychodzących żądań klienta HTTP. Aby użyć propagacji nagłówka:

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

  • Skonfiguruj oprogramowanie pośredniczące i HttpClient w programie Startup :

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers();
    
        services.AddHttpClient("MyForwardingClient").AddHeaderPropagation();
        services.AddHeaderPropagation(options =>
        {
            options.Headers.Add("X-TraceId");
        });
    }
    
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
    
        app.UseHttpsRedirection();
    
        app.UseHeaderPropagation();
    
        app.UseRouting();
    
        app.UseAuthorization();
    
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }
    
  • Klient zawiera skonfigurowane nagłówki dla żądań wychodzących:

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

Dodatkowe zasoby

A więc: Gdyn Condron,Owe Nowaki Steve Gordon

Element IHttpClientFactory może być zarejestrowany i używany do konfigurowania i tworzenia HttpClient wystąpień w aplikacji. Oferuje ona następujące korzyści:

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

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

Wymagania wstępne

Projekty przeznaczone .NET Framework wymagają zainstalowania pakietu NuGet Microsoft.Extensions.Http. Projekty, które są kierowane do .NET Core i odwołują się Microsoft.AspNetCore.App metapakiet, już zawierają Microsoft.Extensions.Http pakiet.

Konsumpcji

Istnieje kilka IHttpClientFactory sposobów, których można użyć w aplikacji:

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

Podstawowy sposób użycia

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

services.AddHttpClient();

Po zarejestrowaniu kod może akceptować, gdziekolwiek można wstrzyknąć usługi za IHttpClientFactory pomocą wstrzykiwania zależności ( DI, dependency injection). Element IHttpClientFactory może służyć do tworzenia HttpClient wystąpienia:

public class BasicUsageModel : PageModel
{
    private readonly IHttpClientFactory _clientFactory;

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

    public bool GetBranchesError { get; private set; }

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

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

        var client = _clientFactory.CreateClient();

        var response = await client.SendAsync(request);

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

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

Nazwani klienci

Jeśli aplikacja wymaga wielu odrębnych zastosowań usługi , każde z HttpClient inną konfiguracją, opcją jest użycie nazwanych klientów. Konfigurację nazwy HttpClient można określić podczas rejestracji w programie Startup.ConfigureServices .

services.AddHttpClient("github", c =>
{
    c.BaseAddress = new Uri("https://api.github.com/");
    // Github API versioning
    c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
    // Github requires a user-agent
    c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});

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

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

Aby użyć nazwanego klienta, parametr ciągu może zostać przekazany CreateClient do . Określ nazwę klienta, który ma zostać utworzony:

public class NamedClientModel : PageModel
{
    private readonly IHttpClientFactory _clientFactory;

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

    public bool GetPullRequestsError { get; private set; }

    public bool HasPullRequests => PullRequests.Any();

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

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

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

        var response = await client.SendAsync(request);

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

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

Typieni klienci

Typieni klienci:

  • Zapewnij takie same możliwości jak nazwani klienci bez konieczności używania ciągów jako kluczy.
  • Zapewnia funkcje IntelliSense i pomoc kompilatora podczas korzystania z klientów.
  • Podaj pojedynczą lokalizację do skonfigurowania określonego serwera i interakcji z HttpClient nim. Na przykład jeden typowany klient może być używany dla pojedynczego punktu końcowego zaplecza i hermetyzuje całą logikę obsługi tego punktu końcowego.
  • Praca z wstrzykiwanym wstrzykiwanym w razie potrzeby w aplikacji.

Typowany klient akceptuje parametr HttpClient 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 przenoszony do klienta z typem. Obiekt HttpClient jest ujawniony jako właściwość publiczna. Można zdefiniować metody specyficzne dla interfejsu API, które uwidoczniają HttpClient funkcjonalność. Metoda hermetyzuje kod wymagany do wykonywania zapytań dotyczących najnowszych otwartych problemów z GetAspNetDocsIssues repozytorium GitHub analizowania tych problemów.

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

services.AddHttpClient<GitHubService>();

Typowany klient jest zarejestrowany jako przejściowy w di. Typowany klient można wstrzykiwać 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ę typiznego klienta można określić podczas rejestracji w programie , a nie w konstruktorze Startup.ConfigureServices klienta z typem:

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 typię w obrębie typiyzowanego klienta. Zamiast ujawniać je jako właściwość, można określić metody publiczne, które wywołują 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 plik HttpClient jest przechowywany jako pole prywatne. Cały dostęp do wywołania zewnętrznego przechodzi przez GetRepos metodę .

Wygenerowani klienci

IHttpClientFactory Można go używać w połączeniu z bibliotekami innych firm, takimi jak Refit. Refit to biblioteka REST dla programu .NET. Konwertuje interfejsy API REST na interfejsy na żywo. Implementacja interfejsu jest generowana dynamicznie przez , RestService przy użyciu do wykonywania zewnętrznych HttpClient 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ć typizowany klient, używając funkcji Ponownego dopasowywu w celu wygenerowania implementacji:

public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpClient("hello", c =>
    {
        c.BaseAddress = new Uri("http://localhost:5000");
    })
    .AddTypedClient(c => Refit.RestService.For<IHelloClient>(c));

    services.AddMvc();
}

Zdefiniowany interfejs może być używane w razie potrzeby z implementacją zapewnianą przez di i ponownego dopasowywu:

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

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

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

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

HttpClient Ma już pojęcie delegowania programów obsługi, które można ze sobą połączyć dla wychodzących żądań HTTP. Ułatwia IHttpClientFactory definiowanie programów obsługi do zastosowania dla każdego nazwanego klienta. Obsługuje rejestrację i tworzenie łańcuchów wielu programów obsługi w celu tworzenia potoku oprogramowania pośredniczącego żądań wychodzących. Każdy z tych programów obsługi może wykonywać pracę przed żądaniem wychodzącym i po nim. Ten wzorzec jest podobny do potoku przychodzącego oprogramowania pośredniczącego w ASP.NET Core. Ten wzorzec udostępnia mechanizm do zarządzania informacjami o żądaniach HTTP obejmującymi m.in. buforowanie, obsługę błędów, serializację i rejestrowanie.

Aby utworzyć program obsługi, zdefiniuj klasę pochodną klasy DelegatingHandler . Zastąp 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ą obsługę. Sprawdza, czy nagłówek X-API-KEY został uwzględniony w żądaniu. Jeśli brakuje nagłówka, można uniknąć wywołania HTTP i zwrócić odpowiednią odpowiedź.

Podczas rejestracji można dodać co najmniej jeden program obsługi do konfiguracji dla HttpClient . To zadanie jest realizowane za pomocą metod rozszerzenia w pliku 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 jest ValidateHeaderHandler zarejestrowany w di. Procedura obsługi musi być zarejestrowana w di-in jako usługa przejściowa, nigdy nie w zakresie. Jeśli program obsługi jest zarejestrowany jako usługa w zakresie i wszystkie usługi, od których zależy program obsługi, są jednorazowe:

  • Usługi programu obsługi można usunąć, zanim program obsługi wykracza poza zakres.
  • Usunięte usługi obsługi powodują niepowodzenie procedury obsługi.

Po zarejestrowaniu AddHttpMessageHandler można go wywołać, przekazując typ procedury obsługi.

Wiele programów obsługi można zarejestrować w kolejności, w których powinny zostać wykonane. Każda procedura obsługi opakowywuje następny program obsługi do momentu, gdy HttpClientHandler finalnie 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, aby udostępnić stan dla każdego żądania za pomocą programów obsługi komunikatów:

  • Przekaż dane do programu obsługi za pomocą polecenia HttpRequestMessage.Properties .
  • Użyj 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 ujmce Polly

IHttpClientFactoryIntegruje się z popularną biblioteką innych firm o nazwie Polly. Polly to kompleksowa biblioteka odporności i obsługi błędów przejściowych dla programu .NET. Umożliwia ona deweloperom wyrażanie zasad, takich jak ponawianie, wyłącznik, limit czasu, izolacja grodziowa i rezerwowa, w sposób płynnie i bezpieczny wątkowo.

Dostępne są metody rozszerzeń umożliwiające korzystanie z zasad usługi Polly ze skonfigurowanymi HttpClient wystąpieniami. Rozszerzenia polly:

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

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

Najczęściej występujące błędy występują, gdy zewnętrzne wywołania HTTP są przejściowe. Dołączona jest wygodna metoda rozszerzenia o nazwie , która AddTransientHttpErrorPolicy umożliwia definicję zasad w celu obsługi błędów przejściowych. Zasady skonfigurowane przy użyciu tej metody rozszerzenia obsługują HttpRequestException odpowiedzi HTTP 5xx i HTTP 408.

Rozszerzenia AddTransientHttpErrorPolicy można używać w programie Startup.ConfigureServices . Rozszerzenie zapewnia dostęp do obiektu PolicyBuilder skonfigurowanego 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ą ponoszone maksymalnie trzy razy z opóźnieniem 600 ms między próbami.

Dynamiczne wybieranie zasad

Istnieją dodatkowe metody rozszerzenia, które mogą służyć do dodawania programów obsługi opartych na polly. Jednym z takich rozszerzeń jest AddPolicyHandler rozszerzenie , które ma wiele przeciążeń. Jedno przeciążenie umożliwia inspekcję żą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, stosowany jest 10-sekundowy limit czasu. W przypadku każdej innej metody HTTP jest używany limit czasu 30-sekundowy.

Dodawanie wielu programów obsługi polly

Często zagnieżdża się zasady usługi 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 dodano dwa programy obsługi. Pierwszy używa AddTransientHttpErrorPolicy rozszerzenia, aby dodać zasady ponawiania. Żądania, które zakończyły się niepowodzeniem, są ponoszowane maksymalnie trzy razy. Drugie wywołanie do AddTransientHttpErrorPolicy powoduje dodanie zasad wyłącznika. Dalsze żądania zewnętrzne są blokowane na 30 sekund, jeśli po kolei wystąpi pięć nieudanych prób. 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 to zdefiniowanie ich raz i zarejestrowanie ich za pomocą obiektu PolicyRegistry . Podano metodę rozszerzenia, która umożliwia dodanie procedury 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 obiektu do obiektu ServiceCollection . Aby użyć zasad z rejestru, używana jest metoda , przekazując AddPolicyHandlerFromRegistry nazwę zasad do zastosowania.

Więcej informacji na temat integracji z usługą Polly można znaleźć na stronie IHttpClientFactory typu wiki polly.

Zarządzanie httpClient i okres istnienia

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

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

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

Domyślny okres istnienia programu obsługi wynosi dwie minuty. Wartość domyślną można przesłonić dla 1 nazwanego klienta. Aby go przesłonić, wywołaj metodę dla metody SetHandlerLifetime IHttpClientBuilder , która jest zwracana podczas tworzenia klienta:

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

Usuwanie klienta nie jest wymagane. Usunięcie anuluje żądania wychodzące i gwarantuje, że danego wystąpienia nie można HttpClient użyć po wywołaniu . Dispose IHttpClientFactory śledzi i usuwa zasoby używane przez HttpClient wystąpienia. Wystąpienia HttpClient można zwykle traktować jako obiekty .NET, które nie wymagają usuwania.

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

Alternatywy dla IHttpClientFactory

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

  • Problemy z wyczerpaniem zasobów przez HttpMessageHandler pulę wystąpień.
  • Nieodświeżone problemy z systemem DNS HttpMessageHandler według wystąpień cyklicznych w regularnych odstępach czasu.

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

  • Utwórz wystąpienie klasy podczas uruchamiania aplikacji i używaj go SocketsHttpHandler przez całe życie aplikacji.
  • Skonfiguruj PooledConnectionLifetime odpowiednią wartość na podstawie czasu odświeżania DNS.
  • Tworzenie HttpClient wystąpień przy użyciu new HttpClient(handler, disposeHandler: false) zgodnie z potrzebami.

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

  • Połączenia SocketsHttpHandler są udziałów między HttpClient wystąpieniami. Udostępnianie zapobiega wyczerpaniu gniazd.
  • Cykle SocketsHttpHandler połączeń zgodnie z , aby uniknąć PooledConnectionLifetime nieaktualnych problemów z systemem DNS.

CookieS

Wystąpienia w HttpMessageHandler puli skutkują CookieContainer współużytkami obiektów. Nieoczekiwane udostępnianie CookieContainer obiektów często skutkuje nieprawidłowym kodem. W przypadku aplikacji, które cookie wymagają s, rozważ jedną z tych usług:

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

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

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

Rejestrowanie

Klienci utworzeni za IHttpClientFactory pośrednictwem 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 rejestruje na przykład 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 przed przetworzeniem go przez inne programy obsługi w potoku. W odpowiedzi komunikaty są rejestrowane po otrzymaniu odpowiedzi przez inne procedury 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 dziennika System.Net.Http.HttpClient.MyNamedClient.ClientHandler . W przypadku żądania dzieje się tak po uruchomieniu wszystkich innych programów obsługi i bezpośrednio przed wysłaniem żądania w sieci. W odpowiedzi to rejestrowanie zawiera stan odpowiedzi, zanim przejdzie z powrotem przez potok procedury 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 kodu stanu odpowiedzi.

Włączenie nazwy klienta w kategorii dziennika umożliwia filtrowanie dzienników dla określonych nazwanych klientów, jeśli jest to konieczne.

Konfigurowanie httpMessageHandler

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

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

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

Używanie 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 z usługi , które jest używane do tworzenia obiektu HttpClient . HttpClient Służy do pobierania strony internetowej.
  • Metoda usługi jest wykonywana w celu zapisu pierwszych 500 znaków zawartości strony internetowej GetPage w konsoli. Aby uzyskać więcej informacji na temat wywoływania usług Program.Main z usługi , 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 obsługiwane przez społeczność oprogramowanie pośredniczące do propagowania nagłówków HTTP z żądania przychodzącego do wychodzących żądań klienta HTTP. Aby użyć propagacji nagłówka:

  • Odwołaj się do portu obsługiwanego przez społeczność pakietu HeaderPropagation. ASP.NET Core 3.1 i nowsze obsługują pakiet 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