Wysyłaj żądania HTTP przy użyciu elementu IHttpClientFactory w ASP.NET Core
Kirk Larkin, Steve Gordon, Glenn Condron i Ryan Nowak.
Element IHttpClientFactory można zarejestrować i użyć do konfigurowania i tworzenia HttpClient wystąpień w aplikacji. IHttpClientFactory
oferuje następujące korzyści:
- Zapewnia centralną lokalizację nazewnictwa i konfigurowania wystąpień logicznych
HttpClient
. Na przykład klient o nazwie github może zostać zarejestrowany i skonfigurowany do uzyskiwania dostępu do usługi GitHub. W celu uzyskania dostępu ogólnego można zarejestrować domyślnego klienta. - Codififies koncepcja wychodzącego oprogramowania pośredniczącego za pośrednictwem delegowania programów obsługi w programie
HttpClient
. Udostępnia rozszerzenia oprogramowania pośredniczącego opartego na usłudze Polly w celu korzystania z delegowania programów obsługi w programieHttpClient
. - Zarządza buforowaniem i okresem istnienia wystąpień bazowych
HttpClientMessageHandler
. Automatyczne zarządzanie pozwala uniknąć typowych problemów z systemem DNS (system nazw domen), które występują podczas ręcznego zarządzaniaHttpClient
okresami istnienia. - Dodaje konfigurowalne środowisko rejestrowania (za pośrednictwem
ILogger
) dla wszystkich żądań wysyłanych za pośrednictwem klientów utworzonych przez fabrykę.
Przykładowy kod w tej wersji tematu używa System.Text.Json do deserializacji JSzawartości ON zwróconej w odpowiedziach HTTP. W przypadku przykładów korzystających z elementów Json.NET
i ReadAsAsync<T>
użyj selektora wersji, aby wybrać wersję 2.x tego tematu.
Konsumpcji
W aplikacji można użyć kilku sposobów IHttpClientFactory
:
Najlepsze podejście zależy od wymagań aplikacji.
Podstawowy sposób użycia
Zarejestruj się IHttpClientFactory
, wywołując polecenie AddHttpClient
w programie Program.cs
:
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddHttpClient();
Żądanie IHttpClientFactory
można zażądać przy użyciu wstrzykiwania zależności (DI). Poniższy kod używa IHttpClientFactory
metody do utworzenia HttpClient
wystąpienia:
public class BasicModel : PageModel
{
private readonly IHttpClientFactory _httpClientFactory;
public BasicModel(IHttpClientFactory httpClientFactory) =>
_httpClientFactory = httpClientFactory;
public IEnumerable<GitHubBranch>? GitHubBranches { get; set; }
public async Task OnGet()
{
var httpRequestMessage = new HttpRequestMessage(
HttpMethod.Get,
"https://api.github.com/repos/dotnet/AspNetCore.Docs/branches")
{
Headers =
{
{ HeaderNames.Accept, "application/vnd.github.v3+json" },
{ HeaderNames.UserAgent, "HttpRequestsSample" }
}
};
var httpClient = _httpClientFactory.CreateClient();
var httpResponseMessage = await httpClient.SendAsync(httpRequestMessage);
if (httpResponseMessage.IsSuccessStatusCode)
{
using var contentStream =
await httpResponseMessage.Content.ReadAsStreamAsync();
GitHubBranches = await JsonSerializer.DeserializeAsync
<IEnumerable<GitHubBranch>>(contentStream);
}
}
}
Użycie IHttpClientFactory
metody podobnej do w poprzednim przykładzie jest dobrym sposobem refaktoryzacji istniejącej aplikacji. Nie ma to wpływu na sposób HttpClient
użycia. W miejscach, w których HttpClient
wystąpienia są tworzone w istniejącej aplikacji, zastąp te wystąpienia wywołaniami do CreateClient.
Nazwani klienci
Nazwani klienci są dobrym wyborem, gdy:
- Aplikacja wymaga wielu odrębnych zastosowań programu
HttpClient
. - Wiele
HttpClient
z nich ma inną konfigurację.
Określ konfigurację dla nazwy HttpClient
podczas rejestracji w pliku Program.cs
:
builder.Services.AddHttpClient("GitHub", httpClient =>
{
httpClient.BaseAddress = new Uri("https://api.github.com/");
// using Microsoft.Net.Http.Headers;
// The GitHub API requires two headers.
httpClient.DefaultRequestHeaders.Add(
HeaderNames.Accept, "application/vnd.github.v3+json");
httpClient.DefaultRequestHeaders.Add(
HeaderNames.UserAgent, "HttpRequestsSample");
});
W poprzednim kodzie klient jest skonfigurowany z:
- Adres podstawowy
https://api.github.com/
. - Dwa nagłówki wymagane do pracy z interfejsem API usługi GitHub.
CreateClient
Za każdym razem CreateClient jest wywoływana:
- Zostanie utworzone nowe wystąpienie klasy
HttpClient
. - Akcja konfiguracji jest wywoływana.
Aby utworzyć nazwanego klienta, przekaż jego nazwę do elementu CreateClient
:
public class NamedClientModel : PageModel
{
private readonly IHttpClientFactory _httpClientFactory;
public NamedClientModel(IHttpClientFactory httpClientFactory) =>
_httpClientFactory = httpClientFactory;
public IEnumerable<GitHubBranch>? GitHubBranches { get; set; }
public async Task OnGet()
{
var httpClient = _httpClientFactory.CreateClient("GitHub");
var httpResponseMessage = await httpClient.GetAsync(
"repos/dotnet/AspNetCore.Docs/branches");
if (httpResponseMessage.IsSuccessStatusCode)
{
using var contentStream =
await httpResponseMessage.Content.ReadAsStreamAsync();
GitHubBranches = await JsonSerializer.DeserializeAsync
<IEnumerable<GitHubBranch>>(contentStream);
}
}
}
W poprzednim kodzie żądanie nie musi określać nazwy hosta. Kod może przekazać tylko ścieżkę, ponieważ używany jest adres podstawowy skonfigurowany dla klienta.
Klienci typizowane
Klienci typizowane:
- Zapewnij te same możliwości, co nazwani klienci bez konieczności używania ciągów jako kluczy.
- Zapewnia funkcję IntelliSense i pomoc kompilatora podczas korzystania z klientów.
- Podaj pojedynczą lokalizację do skonfigurowania określonego elementu i interakcji z nim
HttpClient
. Na przykład można użyć pojedynczego klienta typizowanego:- W przypadku pojedynczego punktu końcowego zaplecza.
- Aby hermetyzować całą logikę do obsługi punktu końcowego.
- Praca z di i można wstrzyknąć tam, gdzie jest to wymagane w aplikacji.
Typowany klient akceptuje HttpClient
parametr w konstruktorze:
public class GitHubService
{
private readonly HttpClient _httpClient;
public GitHubService(HttpClient httpClient)
{
_httpClient = httpClient;
_httpClient.BaseAddress = new Uri("https://api.github.com/");
// using Microsoft.Net.Http.Headers;
// The GitHub API requires two headers.
_httpClient.DefaultRequestHeaders.Add(
HeaderNames.Accept, "application/vnd.github.v3+json");
_httpClient.DefaultRequestHeaders.Add(
HeaderNames.UserAgent, "HttpRequestsSample");
}
public async Task<IEnumerable<GitHubBranch>?> GetAspNetCoreDocsBranchesAsync() =>
await _httpClient.GetFromJsonAsync<IEnumerable<GitHubBranch>>(
"repos/dotnet/AspNetCore.Docs/branches");
}
Powyższy kod ma następujące działanie:
- Konfiguracja jest przenoszona do typizowanego klienta.
- Podane
HttpClient
wystąpienie jest przechowywane jako pole prywatne.
Można utworzyć metody specyficzne dla interfejsu API, które uwidaczniają HttpClient
funkcjonalność. Na przykład GetAspNetCoreDocsBranches
metoda hermetyzuje kod umożliwiający pobranie gałęzi usługi GitHub docs.
Następujące wywołania AddHttpClient kodu w programie w Program.cs
celu zarejestrowania wpisanej GitHubService
klasy klienta:
builder.Services.AddHttpClient<GitHubService>();
Typizowanego klienta jest rejestrowany jako przejściowy z di. W poprzednim kodzie rejestruje AddHttpClient
się GitHubService
jako usługa przejściowa. Ta rejestracja używa metody fabryki do:
- Utwórz wystąpienie elementu
HttpClient
. - Utwórz wystąpienie
GitHubService
klasy , przekazując w wystąpieniu obiektu do jego konstruktoraHttpClient
.
Typizowanego klienta można wstrzyknąć i używać bezpośrednio:
public class TypedClientModel : PageModel
{
private readonly GitHubService _gitHubService;
public TypedClientModel(GitHubService gitHubService) =>
_gitHubService = gitHubService;
public IEnumerable<GitHubBranch>? GitHubBranches { get; set; }
public async Task OnGet()
{
try
{
GitHubBranches = await _gitHubService.GetAspNetCoreDocsBranchesAsync();
}
catch (HttpRequestException)
{
// ...
}
}
}
Konfigurację typizowanego klienta można również określić podczas jego rejestracji w Program.cs
programie , a nie w konstruktorze typizowanego klienta:
builder.Services.AddHttpClient<GitHubService>(httpClient =>
{
httpClient.BaseAddress = new Uri("https://api.github.com/");
// ...
});
Wygenerowani klienci
IHttpClientFactory
można używać w połączeniu z bibliotekami innych firm, takimi jak Refit. Refit to REST biblioteka platformy .NET. Konwertuje interfejsy REST API na interfejsy na żywo. Wywołanie AddRefitClient
metody w celu wygenerowania dynamicznej implementacji interfejsu, który używa HttpClient
metody do wykonywania zewnętrznych wywołań HTTP.
Interfejs niestandardowy reprezentuje zewnętrzny interfejs API:
public interface IGitHubClient
{
[Get("/repos/dotnet/AspNetCore.Docs/branches")]
Task<IEnumerable<GitHubBranch>> GetAspNetCoreDocsBranchesAsync();
}
Wywołaj metodę AddRefitClient
w celu wygenerowania implementacji dynamicznej, a następnie wywołaj metodę ConfigureHttpClient
w celu skonfigurowania bazowego HttpClient
elementu :
builder.Services.AddRefitClient<IGitHubClient>()
.ConfigureHttpClient(httpClient =>
{
httpClient.BaseAddress = new Uri("https://api.github.com/");
// using Microsoft.Net.Http.Headers;
// The GitHub API requires two headers.
httpClient.DefaultRequestHeaders.Add(
HeaderNames.Accept, "application/vnd.github.v3+json");
httpClient.DefaultRequestHeaders.Add(
HeaderNames.UserAgent, "HttpRequestsSample");
});
Użyj di, aby uzyskać dostęp do dynamicznej implementacji programu IGitHubClient
:
public class RefitModel : PageModel
{
private readonly IGitHubClient _gitHubClient;
public RefitModel(IGitHubClient gitHubClient) =>
_gitHubClient = gitHubClient;
public IEnumerable<GitHubBranch>? GitHubBranches { get; set; }
public async Task OnGet()
{
try
{
GitHubBranches = await _gitHubClient.GetAspNetCoreDocsBranchesAsync();
}
catch (ApiException)
{
// ...
}
}
}
Żądania POST, PUT i DELETE
W poprzednich przykładach wszystkie żądania HTTP używają czasownika GET HTTP. HttpClient
Obsługuje również inne czasowniki HTTP, w tym:
- POST
- PUT
- DELETE
- PATCH
Aby uzyskać pełną listę obsługiwanych czasowników HTTP, zobacz HttpMethod.
W poniższym przykładzie pokazano, jak utworzyć żądanie HTTP POST:
public async Task CreateItemAsync(TodoItem todoItem)
{
var todoItemJson = new StringContent(
JsonSerializer.Serialize(todoItem),
Encoding.UTF8,
Application.Json); // using static System.Net.Mime.MediaTypeNames;
using var httpResponseMessage =
await _httpClient.PostAsync("/api/TodoItems", todoItemJson);
httpResponseMessage.EnsureSuccessStatusCode();
}
W poprzednim kodzie CreateItemAsync
metoda :
- Serializuje
TodoItem
parametr na JSWŁ. przy użyciu poleceniaSystem.Text.Json
. - Tworzy wystąpienie StringContent elementu w celu spakowania serializowanego JSmodułu ON do wysyłania w treści żądania HTTP.
- Wywołania PostAsync w celu wysłania JSzawartości ON do określonego adresu URL. Jest to względny adres URL, który jest dodawany do obiektu HttpClient.BaseAddress.
- Wywołania EnsureSuccessStatusCode w celu zgłoszenia wyjątku, jeśli kod stanu odpowiedzi nie wskazuje powodzenia.
HttpClient
Obsługuje również inne typy zawartości. Na przykład MultipartContent i StreamContent. Aby uzyskać pełną listę obsługiwanej zawartości, zobacz HttpContent.
W poniższym przykładzie pokazano żądanie HTTP PUT:
public async Task SaveItemAsync(TodoItem todoItem)
{
var todoItemJson = new StringContent(
JsonSerializer.Serialize(todoItem),
Encoding.UTF8,
Application.Json);
using var httpResponseMessage =
await _httpClient.PutAsync($"/api/TodoItems/{todoItem.Id}", todoItemJson);
httpResponseMessage.EnsureSuccessStatusCode();
}
Powyższy kod jest podobny do przykładu POST. Metoda SaveItemAsync
wywołuje PutAsync metodę PostAsync
zamiast .
W poniższym przykładzie pokazano żądanie HTTP DELETE:
public async Task DeleteItemAsync(long itemId)
{
using var httpResponseMessage =
await _httpClient.DeleteAsync($"/api/TodoItems/{itemId}");
httpResponseMessage.EnsureSuccessStatusCode();
}
W poprzednim kodzie metoda wywołuje DeleteAsyncmetodę DeleteItemAsync
. Ponieważ żądania HTTP DELETE zwykle nie zawierają treści, DeleteAsync
metoda nie zapewnia przeciążenia, które akceptuje wystąpienie HttpContent
klasy .
Aby dowiedzieć się więcej o korzystaniu z różnych czasowników HTTP w programie HttpClient
, zobacz HttpClient.
Oprogramowanie pośredniczące żądań wychodzących
HttpClient
Ma koncepcję delegowania procedur obsługi, które mogą być połączone razem dla wychodzących żądań HTTP. IHttpClientFactory
:
- Upraszcza definiowanie procedur obsługi do zastosowania dla każdego nazwanego klienta.
- Obsługuje rejestrację i tworzenie łańcucha wielu procedur obsługi w celu utworzenia potoku oprogramowania pośredniczącego żądań wychodzących. Każdy z tych programów obsługi może wykonać pracę przed i po żądaniu wychodzącym. Ten wzorzec:
- Jest podobny do potoku oprogramowania pośredniczącego dla ruchu przychodzącego w ASP.NET Core.
- Zapewnia mechanizm zarządzania problemami krzyżowymi dotyczącymi żądań HTTP, takich jak:
- Buforowanie
- obsługa błędów
- Serializacji
- rejestrowanie
Aby utworzyć procedurę obsługi delegowania:
- Wyprowadzanie z klasy DelegatingHandler.
- Zastąpij SendAsync. Wykonaj kod przed przekazaniem żądania do następnej procedury obsługi w potoku:
public class ValidateHeaderHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
if (!request.Headers.Contains("X-API-KEY"))
{
return new HttpResponseMessage(HttpStatusCode.BadRequest)
{
Content = new StringContent(
"The API key header X-API-KEY is required.")
};
}
return await base.SendAsync(request, cancellationToken);
}
}
Powyższy kod sprawdza, czy X-API-KEY
nagłówek znajduje się w żądaniu. Jeśli X-API-KEY
brakuje, BadRequest jest zwracany.
Do konfiguracji programu można dodać więcej niż jedną procedurę obsługi za HttpClient
pomocą Microsoft.Extensions.DependencyInjection.HttpClientBuilderExtensions.AddHttpMessageHandlerpolecenia :
builder.Services.AddTransient<ValidateHeaderHandler>();
builder.Services.AddHttpClient("HttpMessageHandler")
.AddHttpMessageHandler<ValidateHeaderHandler>();
W poprzednim kodzie ValidateHeaderHandler
element jest zarejestrowany za pomocą di. Po zarejestrowaniu AddHttpMessageHandler można wywołać metodę , przekazując typ programu obsługi.
Wiele procedur obsługi można zarejestrować w kolejności, w której powinny być wykonywane. Każda procedura obsługi opakowuje następną procedurę obsługi do momentu, aż końcowy HttpClientHandler
wykona żądanie:
builder.Services.AddTransient<SampleHandler1>();
builder.Services.AddTransient<SampleHandler2>();
builder.Services.AddHttpClient("MultipleHttpMessageHandlers")
.AddHttpMessageHandler<SampleHandler1>()
.AddHttpMessageHandler<SampleHandler2>();
W poprzednim kodzie SampleHandler1
uruchamia się najpierw przed SampleHandler2
.
Używanie di w rozwiązaniu pośredniczącym żądań wychodzących
Podczas IHttpClientFactory
tworzenia nowej procedury obsługi delegowania używa di do spełnienia parametrów konstruktora programu obsługi. IHttpClientFactory
Tworzy oddzielny zakres di dla każdej procedury obsługi, co może prowadzić do zaskakującego zachowania, gdy program obsługi korzysta z usługi o określonym zakresie .
Rozważmy na przykład następujący interfejs i jego implementację, która reprezentuje zadanie jako operację o identyfikatorze : OperationId
public interface IOperationScoped
{
string OperationId { get; }
}
public class OperationScoped : IOperationScoped
{
public string OperationId { get; } = Guid.NewGuid().ToString()[^4..];
}
Jak sugeruje jego nazwa, IOperationScoped
jest rejestrowana w di przy użyciu okresu istnienia o określonym zakresie :
builder.Services.AddScoped<IOperationScoped, OperationScoped>();
Następująca procedura obsługi delegowania używa metody i używa IOperationScoped
jej do ustawiania nagłówka X-OPERATION-ID
dla żądania wychodzącego:
public class OperationHandler : DelegatingHandler
{
private readonly IOperationScoped _operationScoped;
public OperationHandler(IOperationScoped operationScoped) =>
_operationScoped = operationScoped;
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Headers.Add("X-OPERATION-ID", _operationScoped.OperationId);
return await base.SendAsync(request, cancellationToken);
}
}
W pobranym plikuHttpRequestsSample
przejdź do /Operation
strony i odśwież stronę. Wartość zakresu żądania zmienia się dla każdego żądania, ale wartość zakresu procedury obsługi zmienia się tylko co 5 sekund.
Programy obsługi mogą zależeć od usług dowolnego zakresu. Usługi, od których zależą programy obsługi, są usuwane po usunięciu programu obsługi.
Użyj jednej z następujących metod udostępniania stanu poszczególnych żądań z procedurami obsługi komunikatów:
- Przekazywanie danych do programu obsługi przy użyciu polecenia HttpRequestMessage.Options.
- Użyj polecenia IHttpContextAccessor , aby uzyskać dostęp do bieżącego żądania.
- Utwórz niestandardowy AsyncLocal<T> obiekt magazynu w celu przekazania danych.
Korzystanie z programów obsługi opartych na usłudze Polly
IHttpClientFactory
program integruje się z biblioteką innej firmy Polly. Polly to kompleksowa odporność i biblioteka obsługi błędów przejściowych dla platformy .NET. Umożliwia deweloperom wyrażanie zasad, takich jak ponawianie prób, wyłącznik, przekroczenie limitu czasu, izolacja grodziowa i powrót w sposób płynny i bezpieczny wątkowo.
Dostępne są metody rozszerzenia umożliwiające korzystanie z zasad polly ze skonfigurowanymi HttpClient
wystąpieniami. Rozszerzenia Polly obsługują dodawanie programów obsługi opartych na usłudze Polly do klientów. Polly wymaga pakietu NuGet Microsoft.Extensions.Http.Polly .
Obsługa błędów przejściowych
Błędy zwykle występują, gdy zewnętrzne wywołania HTTP są przejściowe. AddTransientHttpErrorPolicy umożliwia zdefiniowanie zasad w celu obsługi błędów przejściowych. Zasady skonfigurowane z obsługą AddTransientHttpErrorPolicy
następujących odpowiedzi:
- HttpRequestException
- HTTP 5xx
- HTTP 408
AddTransientHttpErrorPolicy
zapewnia dostęp do obiektu skonfigurowanego PolicyBuilder
do obsługi błędów reprezentujących możliwy błąd przejściowy:
builder.Services.AddHttpClient("PollyWaitAndRetry")
.AddTransientHttpErrorPolicy(policyBuilder =>
policyBuilder.WaitAndRetryAsync(
3, retryNumber => TimeSpan.FromMilliseconds(600)));
W poprzednim kodzie zdefiniowano WaitAndRetryAsync
zasady. Żądania, które zakończyły się niepowodzeniem, są ponawiane do trzech razy z opóźnieniem 600 ms między próbami.
Dynamiczne wybieranie zasad
Metody rozszerzeń są udostępniane w celu dodania procedur obsługi opartych na usłudze Polly, na przykład AddPolicyHandler. Następujące AddPolicyHandler
przeciążenie sprawdza żądanie, aby zdecydować, które zasady mają być stosowane:
var timeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(10));
var longTimeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(30));
builder.Services.AddHttpClient("PollyDynamic")
.AddPolicyHandler(httpRequestMessage =>
httpRequestMessage.Method == HttpMethod.Get ? timeoutPolicy : longTimeoutPolicy);
W poprzednim kodzie, jeśli żądanie wychodzące jest żądaniem HTTP GET, zostanie zastosowany 10-sekundowy limit czasu. W przypadku każdej innej metody HTTP jest używany limit czasu 30 sekund.
Dodawanie wielu procedur obsługi polly
Często zagnieżdżanie zasad polly:
builder.Services.AddHttpClient("PollyMultiple")
.AddTransientHttpErrorPolicy(policyBuilder =>
policyBuilder.RetryAsync(3))
.AddTransientHttpErrorPolicy(policyBuilder =>
policyBuilder.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));
W powyższym przykładzie:
- Dodano dwie procedury obsługi.
- Pierwsza procedura obsługi używa AddTransientHttpErrorPolicy metody w celu dodania zasad ponawiania. Żądania, które zakończyły się niepowodzeniem, są ponawiane do trzech razy.
- Drugie
AddTransientHttpErrorPolicy
wywołanie dodaje zasady wyłącznika. Kolejne żądania zewnętrzne są blokowane przez 30 sekund, jeśli 5 nieudanych prób nastąpi sekwencyjnie. Zasady wyłącznika są stanowe. Wszystkie wywołania za pośrednictwem tego klienta mają ten sam stan obwodu.
Dodawanie zasad z rejestru Polly
Podejście do zarządzania regularnie używanymi zasadami polega na zdefiniowaniu ich raz i zarejestrowaniu ich w obiekcie PolicyRegistry
. Na przykład:
var timeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(10));
var longTimeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(30));
var policyRegistry = builder.Services.AddPolicyRegistry();
policyRegistry.Add("Regular", timeoutPolicy);
policyRegistry.Add("Long", longTimeoutPolicy);
builder.Services.AddHttpClient("PollyRegistryRegular")
.AddPolicyHandlerFromRegistry("Regular");
builder.Services.AddHttpClient("PollyRegistryLong")
.AddPolicyHandlerFromRegistry("Long");
Powyższy kod ma następujące działanie:
- Dwie zasady
Regular
iLong
, są dodawane do rejestru Polly. - AddPolicyHandlerFromRegistry Konfiguruje poszczególnych nazwanych klientów tak, aby używali tych zasad z rejestru Polly.
Aby uzyskać więcej informacji na IHttpClientFactory
temat integracji z usługą Polly, zobacz witrynę typu wiki polly.
Zarządzanie klientem HttpClient i okresem istnienia
HttpClient
Nowe wystąpienie jest zwracane za każdym razem, gdy CreateClient
jest wywoływane w elemecie IHttpClientFactory
. Element HttpMessageHandler jest tworzony na nazwanego klienta. Fabryka zarządza okresami HttpMessageHandler
istnienia wystąpień.
IHttpClientFactory
HttpMessageHandler
pule wystąpień utworzonych przez fabrykę w celu zmniejszenia zużycia zasobów. Wystąpienie HttpMessageHandler
może zostać ponownie użyte z puli podczas tworzenia nowego HttpClient
wystąpienia, jeśli jego okres istnienia nie wygasł.
Buforowanie procedur obsługi jest pożądane, ponieważ każda procedura obsługi zwykle zarządza własnymi podstawowymi połączeniami HTTP. Utworzenie większej liczby procedur obsługi niż jest to konieczne może spowodować opóźnienia połączeń. Niektóre programy obsługi utrzymują również połączenia otwarte na czas nieokreślony, co może uniemożliwić programowi obsługi reagowanie na zmiany systemu DNS (systemu nazw domen).
Domyślny okres obsługi to dwie minuty. Wartość domyślną można zastąpić na podstawie nazwanego klienta:
builder.Services.AddHttpClient("HandlerLifetime")
.SetHandlerLifetime(TimeSpan.FromMinutes(5));
HttpClient
wystąpienia mogą być zwykle traktowane jako obiekty platformy .NET , które nie wymagają usuwania. Usuwanie anuluje wychodzące żądania i gwarantuje, że dane HttpClient
wystąpienie nie może być używane po wywołaniu metody Dispose. IHttpClientFactory
śledzi i usuwa zasoby używane przez HttpClient
wystąpienia.
Utrzymywanie aktywności pojedynczego HttpClient
wystąpienia przez długi czas jest typowym wzorcem używanym przed utworzeniem klasy IHttpClientFactory
. Ten wzorzec staje się zbędny po przeprowadzeniu migracji do programu IHttpClientFactory
.
Alternatywy dla IHttpClientFactory
Użycie IHttpClientFactory
w aplikacji z włączoną obsługą di pozwala uniknąć:
- Problemy z wyczerpaniem zasobów przez wystąpienia puli
HttpMessageHandler
. - Nieaktualne problemy z systemem DNS przez wystąpienia rowerowe
HttpMessageHandler
w regularnych odstępach czasu.
Istnieją alternatywne sposoby rozwiązywania powyższych problemów przy użyciu długotrwałego SocketsHttpHandler wystąpienia.
- Utwórz wystąpienie aplikacji
SocketsHttpHandler
, gdy aplikacja zostanie uruchomiona i użyj jej do końca życia aplikacji. - Skonfiguruj PooledConnectionLifetime odpowiednią wartość na podstawie czasów odświeżania DNS.
- Utwórz
HttpClient
wystąpienia przy użyciu zgodnie znew HttpClient(handler, disposeHandler: false)
potrzebami.
Powyższe podejścia rozwiązują problemy z zarządzaniem zasobami, które IHttpClientFactory
są rozwiązywane w podobny sposób.
- Udziały
SocketsHttpHandler
połączeń międzyHttpClient
wystąpieniami. To udostępnianie uniemożliwia wyczerpanie gniazd. - Połączenia
SocketsHttpHandler
cykli zgodnie z, abyPooledConnectionLifetime
uniknąć nieaktualnych problemów z systemem DNS.
Rejestrowanie
Klienci utworzeni za pośrednictwem IHttpClientFactory
komunikatów dziennika rekordów dla wszystkich żądań. Włącz odpowiedni poziom informacji w konfiguracji rejestrowania, aby wyświetlić domyślne komunikaty dziennika. Dodatkowe rejestrowanie, takie jak rejestrowanie nagłówków żądań, jest uwzględniane tylko na poziomie śledzenia.
Kategoria dziennika używana dla każdego klienta zawiera nazwę klienta. Klient o nazwie MyNamedClient, na przykład, rejestruje komunikaty z kategorią "System.Net.Http.HttpClient. MyNamedClient. Logiczna procedura obsługi". Komunikaty sufiksowane za pomocą programu LogicalHandler występują poza potokiem obsługi żądań. W żądaniu komunikaty są rejestrowane, zanim wszystkie inne programy obsługi w potoku przetworzyły je. W odpowiedzi komunikaty są rejestrowane po otrzymaniu odpowiedzi przez inne programy obsługi potoku.
Rejestrowanie odbywa się również wewnątrz potoku procedury obsługi żądań. W przykładzie MyNamedClient te komunikaty są rejestrowane z kategorią dziennika "System.Net.Http.HttpClient. MyNamedClient. ClientHandler". W przypadku żądania dzieje się tak po uruchomieniu wszystkich innych procedur obsługi i bezpośrednio przed wysłaniem żądania. W odpowiedzi to rejestrowanie zawiera stan odpowiedzi, zanim przejdzie z powrotem przez potok obsługi.
Włączenie rejestrowania na zewnątrz i wewnątrz potoku umożliwia inspekcję zmian wprowadzonych przez inne programy obsługi potoków. Może to obejmować zmiany nagłówków żądań lub kod stanu odpowiedzi.
Uwzględnienie nazwy klienta w kategorii dziennika umożliwia filtrowanie dzienników dla określonych nazwanych klientów.
Konfigurowanie programu HttpMessageHandler
Może być konieczne kontrolowanie konfiguracji wewnętrznej HttpMessageHandler
używanej przez klienta.
Element IHttpClientBuilder
jest zwracany podczas dodawania nazwanych lub wpisanych klientów. Metoda ConfigurePrimaryHttpMessageHandler rozszerzenia może służyć do definiowania delegata. Delegat służy do tworzenia i konfigurowania podstawowego HttpMessageHandler
używanego przez tego klienta:
builder.Services.AddHttpClient("ConfiguredHttpMessageHandler")
.ConfigurePrimaryHttpMessageHandler(() =>
new HttpClientHandler
{
AllowAutoRedirect = true,
UseDefaultCredentials = true
});
CookieS
Wystąpienia w puli HttpMessageHandler
powoduje CookieContainer
udostępnianie obiektów. Nieprzewidziane CookieContainer
udostępnianie obiektów często powoduje niepoprawny kod. W przypadku aplikacji wymagających wartości cookies należy wziąć pod uwagę jedną z następujących kwestii:
- Wyłączanie automatycznej cookie obsługi
- Unikanie
IHttpClientFactory
Wywołaj, ConfigurePrimaryHttpMessageHandler aby wyłączyć automatyczną cookie obsługę:
builder.Services.AddHttpClient("NoAutomaticCookies")
.ConfigurePrimaryHttpMessageHandler(() =>
new HttpClientHandler
{
UseCookies = false
});
Używanie elementu IHttpClientFactory w aplikacji konsolowej
W aplikacji konsolowej dodaj następujące odwołania do pakietu do projektu:
W poniższym przykładzie:
- IHttpClientFactory i
GitHubService
są zarejestrowane w kontenerze usługi hosta ogólnego . GitHubService
jest żądana od di, który z kolei żąda wystąpienia klasyIHttpClientFactory
.GitHubService
używaIHttpClientFactory
polecenia do utworzenia wystąpienia klasyHttpClient
, którego używa do pobierania gałęzi usługi GitHub w witrynie Docs.
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
var host = new HostBuilder()
.ConfigureServices(services =>
{
services.AddHttpClient();
services.AddTransient<GitHubService>();
})
.Build();
try
{
var gitHubService = host.Services.GetRequiredService<GitHubService>();
var gitHubBranches = await gitHubService.GetAspNetCoreDocsBranchesAsync();
Console.WriteLine($"{gitHubBranches?.Count() ?? 0} GitHub Branches");
if (gitHubBranches is not null)
{
foreach (var gitHubBranch in gitHubBranches)
{
Console.WriteLine($"- {gitHubBranch.Name}");
}
}
}
catch (Exception ex)
{
host.Services.GetRequiredService<ILogger<Program>>()
.LogError(ex, "Unable to load branches from GitHub.");
}
public class GitHubService
{
private readonly IHttpClientFactory _httpClientFactory;
public GitHubService(IHttpClientFactory httpClientFactory) =>
_httpClientFactory = httpClientFactory;
public async Task<IEnumerable<GitHubBranch>?> GetAspNetCoreDocsBranchesAsync()
{
var httpRequestMessage = new HttpRequestMessage(
HttpMethod.Get,
"https://api.github.com/repos/dotnet/AspNetCore.Docs/branches")
{
Headers =
{
{ "Accept", "application/vnd.github.v3+json" },
{ "User-Agent", "HttpRequestsConsoleSample" }
}
};
var httpClient = _httpClientFactory.CreateClient();
var httpResponseMessage = await httpClient.SendAsync(httpRequestMessage);
httpResponseMessage.EnsureSuccessStatusCode();
using var contentStream =
await httpResponseMessage.Content.ReadAsStreamAsync();
return await JsonSerializer.DeserializeAsync
<IEnumerable<GitHubBranch>>(contentStream);
}
}
public record GitHubBranch(
[property: JsonPropertyName("name")] string Name);
Oprogramowanie pośredniczące propagacji nagłówka
Propagacja nagłówka to oprogramowanie pośredniczące ASP.NET Core propagujące nagłówki HTTP z żądania przychodzącego do żądań wychodzącychHttpClient
. Aby użyć propagacji nagłówka:
Zainstaluj pakiet Microsoft.AspNetCore.HeaderPropagation .
Skonfiguruj potok oprogramowania pośredniczącego
HttpClient
i w programieProgram.cs
:// Add services to the container. builder.Services.AddControllers(); builder.Services.AddHttpClient("PropagateHeaders") .AddHeaderPropagation(); builder.Services.AddHeaderPropagation(options => { options.Headers.Add("X-TraceId"); }); var app = builder.Build(); // Configure the HTTP request pipeline. app.UseHttpsRedirection(); app.UseHeaderPropagation(); app.MapControllers();
Wysyłaj żądania wychodzące przy użyciu skonfigurowanego
HttpClient
wystąpienia, w tym dodanych nagłówków.
Dodatkowe zasoby
- Wyświetl lub pobierz przykładowy kod (jak pobrać)
- Używanie elementu HttpClientFactory do implementowania odpornych na błędy żądań HTTP
- Implementowanie ponownych prób wywołań HTTP z wycofywaniem wykładniczym przy użyciu zasad HttpClientFactory i Polly
- Implementowanie wzorca wyłącznika
- Jak serializować i deserializować JSWŁ. na platformie .NET
Przez Kirka Larkina, Steve'a Gordona, Glenna Condrona i Ryana Nowaka.
Element IHttpClientFactory można zarejestrować i użyć do konfigurowania i tworzenia HttpClient wystąpień w aplikacji. IHttpClientFactory
oferuje następujące korzyści:
- Zapewnia centralną lokalizację nazewnictwa i konfigurowania wystąpień logicznych
HttpClient
. Na przykład klient o nazwie github może zostać zarejestrowany i skonfigurowany do uzyskiwania dostępu do usługi GitHub. Domyślny klient można zarejestrować w celu uzyskania dostępu ogólnego. - Koduje koncepcję wychodzącego oprogramowania pośredniczącego poprzez delegowanie programów obsługi w programie
HttpClient
. Udostępnia rozszerzenia oprogramowania pośredniczącego opartego na usłudze Polly, które umożliwiają delegowanie programów obsługi w programieHttpClient
. - Zarządza buforowaniem i okresem istnienia wystąpień bazowych
HttpClientMessageHandler
. Automatyczne zarządzanie pozwala uniknąć typowych problemów z systemem DNS (system nazw domen), które występują podczas ręcznego zarządzania okresamiHttpClient
istnienia. - Dodaje konfigurowalne środowisko rejestrowania (za pośrednictwem
ILogger
) dla wszystkich żądań wysyłanych przez klientów utworzonych przez fabrykę.
Wyświetl lub pobierz przykładowy kod (jak pobrać).
Przykładowy kod w tej wersji tematu używa System.Text.Json do deserializacji JSzawartości ON zwróconej w odpowiedziach HTTP. W przypadku przykładów korzystających z Json.NET
elementów i ReadAsAsync<T>
użyj selektora wersji, aby wybrać wersję 2.x tego tematu.
Konsumpcji
Istnieje kilka sposobów IHttpClientFactory
użycia w aplikacji:
Najlepsze podejście zależy od wymagań aplikacji.
Podstawowy sposób użycia
IHttpClientFactory
można zarejestrować, wywołując polecenie AddHttpClient
:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient();
// Remaining code deleted for brevity.
Żądanie może IHttpClientFactory
być wymagane przy użyciu wstrzykiwania zależności (DI). Poniższy kod używa metody IHttpClientFactory
do utworzenia HttpClient
wystąpienia:
public class BasicUsageModel : PageModel
{
private readonly IHttpClientFactory _clientFactory;
public IEnumerable<GitHubBranch> Branches { get; private set; }
public bool GetBranchesError { get; private set; }
public BasicUsageModel(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task OnGet()
{
var request = new HttpRequestMessage(HttpMethod.Get,
"https://api.github.com/repos/dotnet/AspNetCore.Docs/branches");
request.Headers.Add("Accept", "application/vnd.github.v3+json");
request.Headers.Add("User-Agent", "HttpClientFactory-Sample");
var client = _clientFactory.CreateClient();
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
using var responseStream = await response.Content.ReadAsStreamAsync();
Branches = await JsonSerializer.DeserializeAsync
<IEnumerable<GitHubBranch>>(responseStream);
}
else
{
GetBranchesError = true;
Branches = Array.Empty<GitHubBranch>();
}
}
}
Użycie IHttpClientFactory
takich jak w poprzednim przykładzie jest dobrym sposobem refaktoryzacji istniejącej aplikacji. Nie ma to wpływu na sposób HttpClient
użycia. W miejscach, w których HttpClient
wystąpienia są tworzone w istniejącej aplikacji, zastąp te wystąpienia wywołaniami do CreateClient.
Nazwani klienci
Nazwani klienci są dobrym wyborem, gdy:
- Aplikacja wymaga wielu odrębnych zastosowań programu
HttpClient
. - Wiele
HttpClient
z nich ma inną konfigurację.
Konfigurację nazwanego HttpClient
można określić podczas rejestracji w programie Startup.ConfigureServices
:
services.AddHttpClient("github", c =>
{
c.BaseAddress = new Uri("https://api.github.com/");
// Github API versioning
c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
// Github requires a user-agent
c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});
W poprzednim kodzie klient jest skonfigurowany z:
- Adres
https://api.github.com/
podstawowy . - Dwa nagłówki wymagane do pracy z interfejsem API usługi GitHub.
CreateClient
Za każdym razem CreateClient jest wywoływana:
- Zostanie utworzone nowe wystąpienie
HttpClient
. - Akcja konfiguracji jest wywoływana.
Aby utworzyć nazwanego klienta, przekaż jego nazwę do CreateClient
elementu :
public class NamedClientModel : PageModel
{
private readonly IHttpClientFactory _clientFactory;
public IEnumerable<GitHubPullRequest> PullRequests { get; private set; }
public bool GetPullRequestsError { get; private set; }
public bool HasPullRequests => PullRequests.Any();
public NamedClientModel(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task OnGet()
{
var request = new HttpRequestMessage(HttpMethod.Get,
"repos/dotnet/AspNetCore.Docs/pulls");
var client = _clientFactory.CreateClient("github");
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
using var responseStream = await response.Content.ReadAsStreamAsync();
PullRequests = await JsonSerializer.DeserializeAsync
<IEnumerable<GitHubPullRequest>>(responseStream);
}
else
{
GetPullRequestsError = true;
PullRequests = Array.Empty<GitHubPullRequest>();
}
}
}
W poprzednim kodzie żądanie nie musi określać nazwy hosta. Kod może przekazać tylko ścieżkę, ponieważ używany jest adres podstawowy skonfigurowany dla klienta.
Typizowane klienci
Typowi klienci:
- Zapewnij te same możliwości co nazwani klienci bez konieczności używania ciągów jako kluczy.
- Zapewnia funkcję IntelliSense i pomoc kompilatora podczas korzystania z klientów.
- Podaj pojedynczą lokalizację do skonfigurowania określonego elementu i interakcji z nim
HttpClient
. Na przykład można użyć pojedynczego klienta typizowanego:- W przypadku pojedynczego punktu końcowego zaplecza.
- Aby hermetyzować całą logikę do obsługi punktu końcowego.
- Praca z di i można wstrzyknąć tam, gdzie jest to wymagane w aplikacji.
Typowany klient akceptuje HttpClient
parametr w konstruktorze:
public class GitHubService
{
public HttpClient Client { get; }
public GitHubService(HttpClient client)
{
client.BaseAddress = new Uri("https://api.github.com/");
// GitHub API versioning
client.DefaultRequestHeaders.Add("Accept",
"application/vnd.github.v3+json");
// GitHub requires a user-agent
client.DefaultRequestHeaders.Add("User-Agent",
"HttpClientFactory-Sample");
Client = client;
}
public async Task<IEnumerable<GitHubIssue>> GetAspNetDocsIssues()
{
return await Client.GetFromJsonAsync<IEnumerable<GitHubIssue>>(
"/repos/aspnet/AspNetCore.Docs/issues?state=open&sort=created&direction=desc");
}
}
Powyższy kod ma następujące działanie:
- Konfiguracja jest przenoszona do typizowanego klienta.
- Obiekt
HttpClient
jest udostępniany jako właściwość publiczna.
Można utworzyć metody specyficzne dla interfejsu API, które uwidaczniają HttpClient
funkcjonalność. Na przykład metoda hermetyzuje kod w GetAspNetDocsIssues
celu pobrania otwartych problemów.
Następujące wywołania AddHttpClient kodu w programie w Startup.ConfigureServices
celu zarejestrowania typizowanej klasy klienta:
services.AddHttpClient<GitHubService>();
Typizowanego klienta jest rejestrowany jako przejściowy z di. W poprzednim kodzie rejestruje AddHttpClient
się GitHubService
jako usługa przejściowa. Ta rejestracja używa metody fabryki do:
- Utwórz wystąpienie elementu
HttpClient
. - Utwórz wystąpienie
GitHubService
klasy , przekazując w wystąpieniu obiektu do jego konstruktoraHttpClient
.
Typizowanego klienta można wstrzyknąć i używać bezpośrednio:
public class TypedClientModel : PageModel
{
private readonly GitHubService _gitHubService;
public IEnumerable<GitHubIssue> LatestIssues { get; private set; }
public bool HasIssue => LatestIssues.Any();
public bool GetIssuesError { get; private set; }
public TypedClientModel(GitHubService gitHubService)
{
_gitHubService = gitHubService;
}
public async Task OnGet()
{
try
{
LatestIssues = await _gitHubService.GetAspNetDocsIssues();
}
catch(HttpRequestException)
{
GetIssuesError = true;
LatestIssues = Array.Empty<GitHubIssue>();
}
}
}
Konfigurację typizowanego klienta można określić podczas rejestracji w Startup.ConfigureServices
programie , a nie w konstruktorze typizowanego klienta:
services.AddHttpClient<RepoService>(c =>
{
c.BaseAddress = new Uri("https://api.github.com/");
c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});
Element HttpClient
można hermetyzować w ramach typizowanego klienta. Zamiast uwidaczniać je jako właściwość, zdefiniuj metodę, która wywołuje HttpClient
wystąpienie wewnętrznie:
public class RepoService
{
// _httpClient isn't exposed publicly
private readonly HttpClient _httpClient;
public RepoService(HttpClient client)
{
_httpClient = client;
}
public async Task<IEnumerable<string>> GetRepos()
{
var response = await _httpClient.GetAsync("aspnet/repos");
response.EnsureSuccessStatusCode();
using var responseStream = await response.Content.ReadAsStreamAsync();
return await JsonSerializer.DeserializeAsync
<IEnumerable<string>>(responseStream);
}
}
W poprzednim kodzie element HttpClient
jest przechowywany w polu prywatnym. Dostęp do metody HttpClient
jest uzyskiwany przez metodę publiczną GetRepos
.
Wygenerowani klienci
IHttpClientFactory
można używać w połączeniu z bibliotekami innych firm, takimi jak Refit. Refit to REST biblioteka platformy .NET. Konwertuje interfejsy REST API na interfejsy na żywo. Implementacja interfejsu jest generowana dynamicznie przez RestService
element , za pomocą polecenia HttpClient
w celu wykonywania zewnętrznych wywołań HTTP.
Interfejs i odpowiedź są zdefiniowane w celu reprezentowania zewnętrznego interfejsu API i jego odpowiedzi:
public interface IHelloClient
{
[Get("/helloworld")]
Task<Reply> GetMessageAsync();
}
public class Reply
{
public string Message { get; set; }
}
Można dodać typizowanego klienta, używając polecenia Dopasuj do generowania implementacji:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient("hello", c =>
{
c.BaseAddress = new Uri("http://localhost:5000");
})
.AddTypedClient(c => Refit.RestService.For<IHelloClient>(c));
services.AddControllers();
}
Zdefiniowany interfejs może być używany w razie potrzeby z implementacją dostarczaną przez di i refit:
[ApiController]
public class ValuesController : ControllerBase
{
private readonly IHelloClient _client;
public ValuesController(IHelloClient client)
{
_client = client;
}
[HttpGet("/")]
public async Task<ActionResult<Reply>> Index()
{
return await _client.GetMessageAsync();
}
}
Żądania POST, PUT i DELETE
W poprzednich przykładach wszystkie żądania HTTP używają czasownika GET HTTP. HttpClient
Obsługuje również inne czasowniki HTTP, w tym:
- POST
- PUT
- DELETE
- PATCH
Aby uzyskać pełną listę obsługiwanych czasowników HTTP, zobacz HttpMethod.
W poniższym przykładzie pokazano, jak utworzyć żądanie HTTP POST:
public async Task CreateItemAsync(TodoItem todoItem)
{
var todoItemJson = new StringContent(
JsonSerializer.Serialize(todoItem, _jsonSerializerOptions),
Encoding.UTF8,
"application/json");
using var httpResponse =
await _httpClient.PostAsync("/api/TodoItems", todoItemJson);
httpResponse.EnsureSuccessStatusCode();
}
W poprzednim kodzie CreateItemAsync
metoda :
- Serializuje
TodoItem
parametr na JSWŁ. przy użyciu poleceniaSystem.Text.Json
. Używa to wystąpienia JsonSerializerOptions klasy , aby skonfigurować proces serializacji. - Tworzy wystąpienie StringContent elementu w celu spakowania serializowanego JSmodułu ON do wysyłania w treści żądania HTTP.
- Wywołania PostAsync w celu wysłania JSzawartości ON do określonego adresu URL. Jest to względny adres URL, który jest dodawany do obiektu HttpClient.BaseAddress.
- Wywołania EnsureSuccessStatusCode w celu zgłoszenia wyjątku, jeśli kod stanu odpowiedzi nie wskazuje powodzenia.
HttpClient
Obsługuje również inne typy zawartości. Na przykład MultipartContent i StreamContent. Aby uzyskać pełną listę obsługiwanej zawartości, zobacz HttpContent.
W poniższym przykładzie pokazano żądanie HTTP PUT:
public async Task SaveItemAsync(TodoItem todoItem)
{
var todoItemJson = new StringContent(
JsonSerializer.Serialize(todoItem),
Encoding.UTF8,
"application/json");
using var httpResponse =
await _httpClient.PutAsync($"/api/TodoItems/{todoItem.Id}", todoItemJson);
httpResponse.EnsureSuccessStatusCode();
}
Powyższy kod jest bardzo podobny do przykładu POST. Metoda SaveItemAsync
wywołuje PutAsync metodę PostAsync
zamiast .
W poniższym przykładzie pokazano żądanie HTTP DELETE:
public async Task DeleteItemAsync(long itemId)
{
using var httpResponse =
await _httpClient.DeleteAsync($"/api/TodoItems/{itemId}");
httpResponse.EnsureSuccessStatusCode();
}
W poprzednim kodzie metoda wywołuje DeleteAsyncmetodę DeleteItemAsync
. Ponieważ żądania HTTP DELETE zwykle nie zawierają treści, DeleteAsync
metoda nie zapewnia przeciążenia, które akceptuje wystąpienie HttpContent
klasy .
Aby dowiedzieć się więcej o korzystaniu z różnych czasowników HTTP w programie HttpClient
, zobacz HttpClient.
Oprogramowanie pośredniczące żądań wychodzących
HttpClient
Ma koncepcję delegowania procedur obsługi, które mogą być połączone razem dla wychodzących żądań HTTP. IHttpClientFactory
:
- Upraszcza definiowanie procedur obsługi do zastosowania dla każdego nazwanego klienta.
- Obsługuje rejestrację i tworzenie łańcucha wielu procedur obsługi w celu utworzenia potoku oprogramowania pośredniczącego żądań wychodzących. Każdy z tych programów obsługi może wykonać pracę przed i po żądaniu wychodzącym. Ten wzorzec:
- Jest podobny do potoku oprogramowania pośredniczącego dla ruchu przychodzącego w ASP.NET Core.
- Zapewnia mechanizm zarządzania problemami krzyżowymi dotyczącymi żądań HTTP, takich jak:
- Buforowanie
- obsługa błędów
- Serializacji
- rejestrowanie
Aby utworzyć procedurę obsługi delegowania:
- Wyprowadzanie z klasy DelegatingHandler.
- Zastąpij SendAsync. Wykonaj kod przed przekazaniem żądania do następnej procedury obsługi w potoku:
public class ValidateHeaderHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
if (!request.Headers.Contains("X-API-KEY"))
{
return new HttpResponseMessage(HttpStatusCode.BadRequest)
{
Content = new StringContent(
"You must supply an API key header called X-API-KEY")
};
}
return await base.SendAsync(request, cancellationToken);
}
}
Powyższy kod sprawdza, czy X-API-KEY
nagłówek znajduje się w żądaniu. Jeśli X-API-KEY
brakuje, BadRequest jest zwracany.
Do konfiguracji programu można dodać więcej niż jedną procedurę obsługi za HttpClient
pomocą Microsoft.Extensions.DependencyInjection.HttpClientBuilderExtensions.AddHttpMessageHandlerpolecenia :
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<ValidateHeaderHandler>();
services.AddHttpClient("externalservice", c =>
{
// Assume this is an "external" service which requires an API KEY
c.BaseAddress = new Uri("https://localhost:5001/");
})
.AddHttpMessageHandler<ValidateHeaderHandler>();
// Remaining code deleted for brevity.
W poprzednim kodzie ValidateHeaderHandler
element jest zarejestrowany za pomocą di. Po zarejestrowaniu AddHttpMessageHandler można wywołać metodę , przekazując typ programu obsługi.
Wiele procedur obsługi można zarejestrować w kolejności, w której powinny być wykonywane. Każda procedura obsługi opakowuje następną procedurę obsługi do momentu, aż końcowy HttpClientHandler
wykona żądanie:
services.AddTransient<SecureRequestHandler>();
services.AddTransient<RequestDataHandler>();
services.AddHttpClient("clientwithhandlers")
// This handler is on the outside and called first during the
// request, last during the response.
.AddHttpMessageHandler<SecureRequestHandler>()
// This handler is on the inside, closest to the request being
// sent.
.AddHttpMessageHandler<RequestDataHandler>();
Używanie di w rozwiązaniu pośredniczącym żądań wychodzących
Podczas IHttpClientFactory
tworzenia nowej procedury obsługi delegowania używa di do spełnienia parametrów konstruktora programu obsługi. IHttpClientFactory
Tworzy oddzielny zakres di dla każdej procedury obsługi, co może prowadzić do zaskakującego zachowania, gdy program obsługi korzysta z usługi o określonym zakresie .
Rozważmy na przykład następujący interfejs i jego implementację, która reprezentuje zadanie jako operację o identyfikatorze : OperationId
public interface IOperationScoped
{
string OperationId { get; }
}
public class OperationScoped : IOperationScoped
{
public string OperationId { get; } = Guid.NewGuid().ToString()[^4..];
}
Jak sugeruje jego nazwa, IOperationScoped
jest rejestrowana w di przy użyciu okresu istnienia o określonym zakresie :
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<TodoContext>(options =>
options.UseInMemoryDatabase("TodoItems"));
services.AddHttpContextAccessor();
services.AddHttpClient<TodoClient>((sp, httpClient) =>
{
var httpRequest = sp.GetRequiredService<IHttpContextAccessor>().HttpContext.Request;
// For sample purposes, assume TodoClient is used in the context of an incoming request.
httpClient.BaseAddress = new Uri(UriHelper.BuildAbsolute(httpRequest.Scheme,
httpRequest.Host, httpRequest.PathBase));
httpClient.Timeout = TimeSpan.FromSeconds(5);
});
services.AddScoped<IOperationScoped, OperationScoped>();
services.AddTransient<OperationHandler>();
services.AddTransient<OperationResponseHandler>();
services.AddHttpClient("Operation")
.AddHttpMessageHandler<OperationHandler>()
.AddHttpMessageHandler<OperationResponseHandler>()
.SetHandlerLifetime(TimeSpan.FromSeconds(5));
services.AddControllers();
services.AddRazorPages();
}
Następująca procedura obsługi delegowania używa metody i używa IOperationScoped
jej do ustawiania nagłówka X-OPERATION-ID
dla żądania wychodzącego:
public class OperationHandler : DelegatingHandler
{
private readonly IOperationScoped _operationService;
public OperationHandler(IOperationScoped operationScoped)
{
_operationService = operationScoped;
}
protected async override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Headers.Add("X-OPERATION-ID", _operationService.OperationId);
return await base.SendAsync(request, cancellationToken);
}
}
W obszarze pobieraniaHttpRequestsSample
przejdź do /Operation
strony i odśwież stronę. Wartość zakresu żądania zmienia się dla każdego żądania, ale wartość zakresu procedury obsługi zmienia się tylko co 5 sekund.
Programy obsługi mogą zależeć od usług dowolnego zakresu. Usługi, od których zależą programy obsługi, są usuwane po usunięciu programu obsługi.
Użyj jednej z następujących metod udostępniania stanu poszczególnych żądań z procedurami obsługi komunikatów:
- Przekazywanie danych do programu obsługi przy użyciu polecenia HttpRequestMessage.Options.
- Użyj polecenia IHttpContextAccessor , aby uzyskać dostęp do bieżącego żądania.
- Utwórz niestandardowy AsyncLocal<T> obiekt magazynu w celu przekazania danych.
Korzystanie z programów obsługi opartych na usłudze Polly
IHttpClientFactory
program integruje się z biblioteką innej firmy Polly. Polly to kompleksowa odporność i biblioteka obsługi błędów przejściowych dla platformy .NET. Umożliwia deweloperom wyrażanie zasad, takich jak ponawianie prób, wyłącznik, przekroczenie limitu czasu, izolacja grodziowa i powrót w sposób płynny i bezpieczny wątkowo.
Dostępne są metody rozszerzenia umożliwiające korzystanie z zasad polly ze skonfigurowanymi HttpClient
wystąpieniami. Rozszerzenia Polly obsługują dodawanie programów obsługi opartych na usłudze Polly do klientów. Polly wymaga pakietu NuGet Microsoft.Extensions.Http.Polly .
Obsługa błędów przejściowych
Błędy zwykle występują, gdy zewnętrzne wywołania HTTP są przejściowe. AddTransientHttpErrorPolicy umożliwia zdefiniowanie zasad w celu obsługi błędów przejściowych. Zasady skonfigurowane z obsługą AddTransientHttpErrorPolicy
następujących odpowiedzi:
- HttpRequestException
- HTTP 5xx
- HTTP 408
AddTransientHttpErrorPolicy
zapewnia dostęp do obiektu skonfigurowanego PolicyBuilder
do obsługi błędów reprezentujących możliwy błąd przejściowy:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient<UnreliableEndpointCallerService>()
.AddTransientHttpErrorPolicy(p =>
p.WaitAndRetryAsync(3, _ => TimeSpan.FromMilliseconds(600)));
// Remaining code deleted for brevity.
W poprzednim kodzie zdefiniowano WaitAndRetryAsync
zasady. Żądania, które zakończyły się niepowodzeniem, są ponawiane do trzech razy z opóźnieniem 600 ms między próbami.
Dynamiczne wybieranie zasad
Metody rozszerzeń są udostępniane w celu dodania procedur obsługi opartych na usłudze Polly, na przykład AddPolicyHandler. Następujące AddPolicyHandler
przeciążenie sprawdza żądanie, aby zdecydować, które zasady mają być stosowane:
var timeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(10));
var longTimeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(30));
services.AddHttpClient("conditionalpolicy")
// Run some code to select a policy based on the request
.AddPolicyHandler(request =>
request.Method == HttpMethod.Get ? timeout : longTimeout);
W poprzednim kodzie, jeśli żądanie wychodzące jest żądaniem HTTP GET, zostanie zastosowany 10-sekundowy limit czasu. W przypadku każdej innej metody HTTP jest używany limit czasu 30 sekund.
Dodawanie wielu procedur obsługi polly
Często zagnieżdżanie zasad polly:
services.AddHttpClient("multiplepolicies")
.AddTransientHttpErrorPolicy(p => p.RetryAsync(3))
.AddTransientHttpErrorPolicy(
p => p.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));
W powyższym przykładzie:
- Dodano dwie procedury obsługi.
- Pierwsza procedura obsługi używa AddTransientHttpErrorPolicy metody w celu dodania zasad ponawiania. Żądania, które zakończyły się niepowodzeniem, są ponawiane do trzech razy.
- Drugie
AddTransientHttpErrorPolicy
wywołanie dodaje zasady wyłącznika. Kolejne żądania zewnętrzne są blokowane przez 30 sekund, jeśli 5 nieudanych prób nastąpi sekwencyjnie. Zasady wyłącznika są stanowe. Wszystkie wywołania za pośrednictwem tego klienta mają ten sam stan obwodu.
Dodawanie zasad z rejestru Polly
Podejście do zarządzania regularnie używanymi zasadami polega na zdefiniowaniu ich raz i zarejestrowaniu ich w obiekcie PolicyRegistry
.
W poniższym kodzie:
- Dodawane są zasady "regularne" i "długie".
- AddPolicyHandlerFromRegistry Dodaje zasady "regularne" i "długie" z rejestru.
public void ConfigureServices(IServiceCollection services)
{
var timeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(10));
var longTimeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(30));
var registry = services.AddPolicyRegistry();
registry.Add("regular", timeout);
registry.Add("long", longTimeout);
services.AddHttpClient("regularTimeoutHandler")
.AddPolicyHandlerFromRegistry("regular");
services.AddHttpClient("longTimeoutHandler")
.AddPolicyHandlerFromRegistry("long");
// Remaining code deleted for brevity.
Aby uzyskać więcej informacji na IHttpClientFactory
temat integracji z usługą Polly, zobacz witrynę typu wiki polly.
Zarządzanie klientem HttpClient i okresem istnienia
HttpClient
Nowe wystąpienie jest zwracane za każdym razem, gdy CreateClient
jest wywoływane w elemecie IHttpClientFactory
. Element HttpMessageHandler jest tworzony na nazwanego klienta. Fabryka zarządza okresami HttpMessageHandler
istnienia wystąpień.
IHttpClientFactory
HttpMessageHandler
pule wystąpień utworzonych przez fabrykę w celu zmniejszenia zużycia zasobów. Wystąpienie HttpMessageHandler
może zostać ponownie użyte z puli podczas tworzenia nowego HttpClient
wystąpienia, jeśli jego okres istnienia nie wygasł.
Buforowanie procedur obsługi jest pożądane, ponieważ każda procedura obsługi zwykle zarządza własnymi podstawowymi połączeniami HTTP. Utworzenie większej liczby procedur obsługi niż jest to konieczne może spowodować opóźnienia połączeń. Niektóre programy obsługi utrzymują również połączenia otwarte na czas nieokreślony, co może uniemożliwić programowi obsługi reagowanie na zmiany systemu DNS (systemu nazw domen).
Domyślny okres obsługi to dwie minuty. Wartość domyślną można zastąpić na podstawie nazwanego klienta:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient("extendedhandlerlifetime")
.SetHandlerLifetime(TimeSpan.FromMinutes(5));
// Remaining code deleted for brevity.
HttpClient
wystąpienia mogą być zwykle traktowane jako obiekty platformy .NET , które nie wymagają usuwania. Usuwanie anuluje wychodzące żądania i gwarantuje, że dane HttpClient
wystąpienie nie może być używane po wywołaniu metody Dispose. IHttpClientFactory
śledzi i usuwa zasoby używane przez HttpClient
wystąpienia.
Utrzymywanie aktywności pojedynczego HttpClient
wystąpienia przez długi czas jest typowym wzorcem używanym przed utworzeniem klasy IHttpClientFactory
. Ten wzorzec staje się zbędny po przeprowadzeniu migracji do programu IHttpClientFactory
.
Alternatywy dla IHttpClientFactory
Użycie IHttpClientFactory
w aplikacji z włączoną obsługą di pozwala uniknąć:
- Problemy z wyczerpaniem zasobów przez wystąpienia puli
HttpMessageHandler
. - Nieaktualne problemy z systemem DNS przez wystąpienia rowerowe
HttpMessageHandler
w regularnych odstępach czasu.
Istnieją alternatywne sposoby rozwiązywania powyższych problemów przy użyciu długotrwałego SocketsHttpHandler wystąpienia.
- Utwórz wystąpienie aplikacji
SocketsHttpHandler
, gdy aplikacja zostanie uruchomiona i użyj jej do końca życia aplikacji. - Skonfiguruj PooledConnectionLifetime do odpowiedniej wartości na podstawie czasów odświeżania DNS.
- Utwórz
HttpClient
wystąpienia przy użyciu zgodnie znew HttpClient(handler, disposeHandler: false)
potrzebami.
Powyższe podejścia rozwiązują problemy z zarządzaniem zasobami, które IHttpClientFactory
są rozwiązywane w podobny sposób.
- Udziały
SocketsHttpHandler
połączeń międzyHttpClient
wystąpieniami. Udostępnianie zapobiega wyczerpaniu gniazd. - Połączenia
SocketsHttpHandler
cykli zgodnie z, abyPooledConnectionLifetime
uniknąć nieaktualnych problemów DNS.
CookieS
Wystąpienia w HttpMessageHandler
puli powoduje CookieContainer
współużytkowanie obiektów. Nieprzewidziane CookieContainer
udostępnianie obiektów często powoduje niepoprawny kod. W przypadku aplikacji wymagających cookiewartości należy wziąć pod uwagę następujące kwestie:
- Wyłączanie automatycznej cookie obsługi
- Unikanie
IHttpClientFactory
Wywołanie ConfigurePrimaryHttpMessageHandler w celu wyłączenia automatycznej cookie obsługi:
services.AddHttpClient("configured-disable-automatic-cookies")
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler()
{
UseCookies = false,
};
});
Rejestrowanie
Klienci utworzeni za pośrednictwem IHttpClientFactory
komunikatów dziennika rekordów dla wszystkich żądań. Włącz odpowiedni poziom informacji w konfiguracji rejestrowania, aby wyświetlić domyślne komunikaty dziennika. Dodatkowe rejestrowanie, takie jak rejestrowanie nagłówków żądań, jest uwzględniane tylko na poziomie śledzenia.
Kategoria dziennika używana dla każdego klienta zawiera nazwę klienta. Klient o nazwie MyNamedClient, na przykład, rejestruje komunikaty z kategorią "System.Net.Http.HttpClient. MyNamedClient. LogicalHandler". Komunikaty z sufiksem LogicalHandler występują poza potokiem obsługi żądań. W żądaniu komunikaty są rejestrowane, zanim wszystkie inne programy obsługi w potoku przetworzyły je. W odpowiedzi komunikaty są rejestrowane po otrzymaniu odpowiedzi przez inne programy obsługi potoku.
Rejestrowanie odbywa się również wewnątrz potoku procedury obsługi żądań. W przykładzie MyNamedClient te komunikaty są rejestrowane z kategorią dziennika "System.Net.Http.HttpClient. MyNamedClient. ClientHandler". W przypadku żądania odbywa się to po uruchomieniu wszystkich innych procedur obsługi i bezpośrednio przed wysłaniem żądania. W odpowiedzi to rejestrowanie zawiera stan odpowiedzi, zanim przejdzie z powrotem przez potok obsługi.
Włączenie rejestrowania poza potokiem i wewnątrz potoku umożliwia inspekcję zmian wprowadzonych przez inne procedury obsługi potoku. Może to obejmować zmiany nagłówków żądań lub kod stanu odpowiedzi.
Uwzględnienie nazwy klienta w kategorii dziennika umożliwia filtrowanie dzienników dla określonych nazwanych klientów.
Konfigurowanie programu HttpMessageHandler
Może być konieczne kontrolowanie konfiguracji wewnętrznej HttpMessageHandler
używanej przez klienta.
Obiekt IHttpClientBuilder
jest zwracany podczas dodawania nazwanych lub wpisanych klientów. Metoda ConfigurePrimaryHttpMessageHandler rozszerzenia może służyć do definiowania delegata. Delegat służy do tworzenia i konfigurowania podstawowego HttpMessageHandler
używanego przez tego klienta:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient("configured-inner-handler")
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler()
{
AllowAutoRedirect = false,
UseDefaultCredentials = true
};
});
// Remaining code deleted for brevity.
Używanie elementu IHttpClientFactory w aplikacji konsolowej
W aplikacji konsolowej dodaj następujące odwołania do pakietu do projektu:
W poniższym przykładzie:
- IHttpClientFactory jest zarejestrowany w kontenerze usługi hosta ogólnego .
MyService
Tworzy wystąpienie fabryki klienta na podstawie usługi, która jest używana do utworzenia obiektuHttpClient
.HttpClient
służy do pobierania strony internetowej.Main
Tworzy zakres umożliwiający wykonanie metody usługiGetPage
i zapisanie pierwszych 500 znaków zawartości strony internetowej w konsoli.
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
class Program
{
static async Task<int> Main(string[] args)
{
var builder = new HostBuilder()
.ConfigureServices((hostContext, services) =>
{
services.AddHttpClient();
services.AddTransient<IMyService, MyService>();
}).UseConsoleLifetime();
var host = builder.Build();
try
{
var myService = host.Services.GetRequiredService<IMyService>();
var pageContent = await myService.GetPage();
Console.WriteLine(pageContent.Substring(0, 500));
}
catch (Exception ex)
{
var logger = host.Services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred.");
}
return 0;
}
public interface IMyService
{
Task<string> GetPage();
}
public class MyService : IMyService
{
private readonly IHttpClientFactory _clientFactory;
public MyService(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task<string> GetPage()
{
// Content from BBC One: Dr. Who website (©BBC)
var request = new HttpRequestMessage(HttpMethod.Get,
"https://www.bbc.co.uk/programmes/b006q2x0");
var client = _clientFactory.CreateClient();
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
return await response.Content.ReadAsStringAsync();
}
else
{
return $"StatusCode: {response.StatusCode}";
}
}
}
}
Oprogramowanie pośredniczące propagacji nagłówka
Propagacja nagłówka to oprogramowanie pośredniczące ASP.NET Core propagujące nagłówki HTTP z żądania przychodzącego do wychodzących żądań klienta HTTP. Aby użyć propagacji nagłówka:
Odwołaj się do pakietu Microsoft.AspNetCore.HeaderPropagation .
Skonfiguruj oprogramowanie pośredniczące i
HttpClient
w programieStartup
:public void ConfigureServices(IServiceCollection services) { services.AddControllers(); services.AddHttpClient("MyForwardingClient").AddHeaderPropagation(); services.AddHeaderPropagation(options => { options.Headers.Add("X-TraceId"); }); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseHttpsRedirection(); app.UseHeaderPropagation(); app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); }
Klient zawiera skonfigurowane nagłówki dla żądań wychodzących:
var client = clientFactory.CreateClient("MyForwardingClient"); var response = client.GetAsync(...);
Dodatkowe zasoby
Przez Kirka Larkina, Steve'a Gordona, Glenna Condrona i Ryana Nowaka.
Element IHttpClientFactory można zarejestrować i użyć do konfigurowania i tworzenia HttpClient wystąpień w aplikacji. IHttpClientFactory
oferuje następujące korzyści:
- Zapewnia centralną lokalizację nazewnictwa i konfigurowania wystąpień logicznych
HttpClient
. Na przykład klient o nazwie github może zostać zarejestrowany i skonfigurowany do uzyskiwania dostępu do usługi GitHub. Domyślny klient można zarejestrować w celu uzyskania dostępu ogólnego. - Koduje koncepcję wychodzącego oprogramowania pośredniczącego poprzez delegowanie programów obsługi w programie
HttpClient
. Udostępnia rozszerzenia oprogramowania pośredniczącego opartego na usłudze Polly, które umożliwiają delegowanie programów obsługi w programieHttpClient
. - Zarządza buforowaniem i okresem istnienia wystąpień bazowych
HttpClientMessageHandler
. Automatyczne zarządzanie pozwala uniknąć typowych problemów z systemem DNS (system nazw domen), które występują podczas ręcznego zarządzania okresamiHttpClient
istnienia. - Dodaje konfigurowalne środowisko rejestrowania (za pośrednictwem
ILogger
) dla wszystkich żądań wysyłanych przez klientów utworzonych przez fabrykę.
Wyświetl lub pobierz przykładowy kod (jak pobrać).
Przykładowy kod w tej wersji tematu używa System.Text.Json do deserializacji JSzawartości ON zwróconej w odpowiedziach HTTP. W przypadku przykładów korzystających z Json.NET
elementów i ReadAsAsync<T>
użyj selektora wersji, aby wybrać wersję 2.x tego tematu.
Konsumpcji
Istnieje kilka sposobów IHttpClientFactory
użycia w aplikacji:
Najlepsze podejście zależy od wymagań aplikacji.
Podstawowy sposób użycia
IHttpClientFactory
można zarejestrować, wywołując polecenie AddHttpClient
:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient();
// Remaining code deleted for brevity.
Żądanie może IHttpClientFactory
być wymagane przy użyciu wstrzykiwania zależności (DI). Poniższy kod używa metody IHttpClientFactory
do utworzenia HttpClient
wystąpienia:
public class BasicUsageModel : PageModel
{
private readonly IHttpClientFactory _clientFactory;
public IEnumerable<GitHubBranch> Branches { get; private set; }
public bool GetBranchesError { get; private set; }
public BasicUsageModel(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task OnGet()
{
var request = new HttpRequestMessage(HttpMethod.Get,
"https://api.github.com/repos/dotnet/AspNetCore.Docs/branches");
request.Headers.Add("Accept", "application/vnd.github.v3+json");
request.Headers.Add("User-Agent", "HttpClientFactory-Sample");
var client = _clientFactory.CreateClient();
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
using var responseStream = await response.Content.ReadAsStreamAsync();
Branches = await JsonSerializer.DeserializeAsync
<IEnumerable<GitHubBranch>>(responseStream);
}
else
{
GetBranchesError = true;
Branches = Array.Empty<GitHubBranch>();
}
}
}
Użycie IHttpClientFactory
takich jak w poprzednim przykładzie jest dobrym sposobem refaktoryzacji istniejącej aplikacji. Nie ma to wpływu na sposób HttpClient
użycia. W miejscach, w których HttpClient
wystąpienia są tworzone w istniejącej aplikacji, zastąp te wystąpienia wywołaniami do CreateClient.
Nazwani klienci
Nazwani klienci są dobrym wyborem, gdy:
- Aplikacja wymaga wielu odrębnych zastosowań programu
HttpClient
. - Wiele
HttpClient
z nich ma inną konfigurację.
Konfigurację nazwanego HttpClient
można określić podczas rejestracji w programie Startup.ConfigureServices
:
services.AddHttpClient("github", c =>
{
c.BaseAddress = new Uri("https://api.github.com/");
// Github API versioning
c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
// Github requires a user-agent
c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});
W poprzednim kodzie klient jest skonfigurowany z:
- Adres
https://api.github.com/
podstawowy . - Dwa nagłówki wymagane do pracy z interfejsem API usługi GitHub.
CreateClient
Za każdym razem CreateClient jest wywoływana:
- Zostanie utworzone nowe wystąpienie
HttpClient
. - Akcja konfiguracji jest wywoływana.
Aby utworzyć nazwanego klienta, przekaż jego nazwę do CreateClient
elementu :
public class NamedClientModel : PageModel
{
private readonly IHttpClientFactory _clientFactory;
public IEnumerable<GitHubPullRequest> PullRequests { get; private set; }
public bool GetPullRequestsError { get; private set; }
public bool HasPullRequests => PullRequests.Any();
public NamedClientModel(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task OnGet()
{
var request = new HttpRequestMessage(HttpMethod.Get,
"repos/dotnet/AspNetCore.Docs/pulls");
var client = _clientFactory.CreateClient("github");
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
using var responseStream = await response.Content.ReadAsStreamAsync();
PullRequests = await JsonSerializer.DeserializeAsync
<IEnumerable<GitHubPullRequest>>(responseStream);
}
else
{
GetPullRequestsError = true;
PullRequests = Array.Empty<GitHubPullRequest>();
}
}
}
W poprzednim kodzie żądanie nie musi określać nazwy hosta. Kod może przekazać tylko ścieżkę, ponieważ używany jest adres podstawowy skonfigurowany dla klienta.
Typizowane klienci
Typowi klienci:
- Zapewnij te same możliwości co nazwani klienci bez konieczności używania ciągów jako kluczy.
- Zapewnia funkcję IntelliSense i pomoc kompilatora podczas korzystania z klientów.
- Podaj jedną lokalizację do skonfigurowania i interakcji z określonym
HttpClient
elementem . Na przykład można użyć pojedynczego typu klienta:- W przypadku pojedynczego punktu końcowego zaplecza.
- Aby hermetyzować całą logikę dotyczącą punktu końcowego.
- Praca z di i może być wstrzykiwana tam, gdzie jest to wymagane w aplikacji.
Typowany klient akceptuje HttpClient
parametr w jego konstruktorze:
public class GitHubService
{
public HttpClient Client { get; }
public GitHubService(HttpClient client)
{
client.BaseAddress = new Uri("https://api.github.com/");
// GitHub API versioning
client.DefaultRequestHeaders.Add("Accept",
"application/vnd.github.v3+json");
// GitHub requires a user-agent
client.DefaultRequestHeaders.Add("User-Agent",
"HttpClientFactory-Sample");
Client = client;
}
public async Task<IEnumerable<GitHubIssue>> GetAspNetDocsIssues()
{
var response = await Client.GetAsync(
"/repos/dotnet/AspNetCore.Docs/issues?state=open&sort=created&direction=desc");
response.EnsureSuccessStatusCode();
using var responseStream = await response.Content.ReadAsStreamAsync();
return await JsonSerializer.DeserializeAsync
<IEnumerable<GitHubIssue>>(responseStream);
}
}
Jeśli chcesz zobaczyć komentarze kodu przetłumaczone na języki inne niż angielski, poinformuj nas o tym w tym problemie z dyskusją w usłudze GitHub.
Powyższy kod ma następujące działanie:
- Konfiguracja jest przenoszona do typizowanego klienta.
- Obiekt
HttpClient
jest uwidoczniony jako właściwość publiczna.
Metody specyficzne dla interfejsu API można utworzyć, aby uwidocznić HttpClient
funkcjonalność. Na przykład GetAspNetDocsIssues
metoda hermetyzuje kod w celu pobrania otwartych problemów.
Następujące wywołania AddHttpClient kodu w programie w Startup.ConfigureServices
celu zarejestrowania wpisanej klasy klienta:
services.AddHttpClient<GitHubService>();
Typowany klient jest zarejestrowany jako przejściowy z di. W poprzednim kodzie AddHttpClient
rejestruje się GitHubService
jako usługa przejściowa. Ta rejestracja używa metody fabryki do:
- Utwórz wystąpienie elementu
HttpClient
. - Utwórz wystąpienie klasy
GitHubService
, przekazując wystąpienie do jego konstruktoraHttpClient
.
Typizowanego klienta można wstrzyknąć i używać bezpośrednio:
public class TypedClientModel : PageModel
{
private readonly GitHubService _gitHubService;
public IEnumerable<GitHubIssue> LatestIssues { get; private set; }
public bool HasIssue => LatestIssues.Any();
public bool GetIssuesError { get; private set; }
public TypedClientModel(GitHubService gitHubService)
{
_gitHubService = gitHubService;
}
public async Task OnGet()
{
try
{
LatestIssues = await _gitHubService.GetAspNetDocsIssues();
}
catch(HttpRequestException)
{
GetIssuesError = true;
LatestIssues = Array.Empty<GitHubIssue>();
}
}
}
Konfigurację typizowanego klienta można określić podczas rejestracji w programie Startup.ConfigureServices
, a nie w konstruktorze typu klienta:
services.AddHttpClient<RepoService>(c =>
{
c.BaseAddress = new Uri("https://api.github.com/");
c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});
Element HttpClient
można hermetyzować w typowym kliencie. Zamiast uwidaczniać go jako właściwość, zdefiniuj metodę, która wywołuje HttpClient
wystąpienie wewnętrznie:
public class RepoService
{
// _httpClient isn't exposed publicly
private readonly HttpClient _httpClient;
public RepoService(HttpClient client)
{
_httpClient = client;
}
public async Task<IEnumerable<string>> GetRepos()
{
var response = await _httpClient.GetAsync("aspnet/repos");
response.EnsureSuccessStatusCode();
using var responseStream = await response.Content.ReadAsStreamAsync();
return await JsonSerializer.DeserializeAsync
<IEnumerable<string>>(responseStream);
}
}
W poprzednim kodzie HttpClient
element jest przechowywany w polu prywatnym. Dostęp do metody HttpClient
jest używany przez metodę publiczną GetRepos
.
Wygenerowani klienci
IHttpClientFactory
można używać w połączeniu z bibliotekami innych firm, takimi jak Refit. Refit to REST biblioteka dla platformy .NET. Konwertuje REST interfejsy API na interfejsy na żywo. Implementacja interfejsu jest generowana dynamicznie przez metodę RestService
, używając metody HttpClient
do wykonywania zewnętrznych wywołań HTTP.
Interfejs i odpowiedź są definiowane w celu reprezentowania zewnętrznego interfejsu API i jego odpowiedzi:
public interface IHelloClient
{
[Get("/helloworld")]
Task<Reply> GetMessageAsync();
}
public class Reply
{
public string Message { get; set; }
}
Można dodać typizowanego klienta przy użyciu funkcji Refit w celu wygenerowania implementacji:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient("hello", c =>
{
c.BaseAddress = new Uri("http://localhost:5000");
})
.AddTypedClient(c => Refit.RestService.For<IHelloClient>(c));
services.AddControllers();
}
W razie potrzeby można używać zdefiniowanego interfejsu z implementacją zapewnianą przez di i refit:
[ApiController]
public class ValuesController : ControllerBase
{
private readonly IHelloClient _client;
public ValuesController(IHelloClient client)
{
_client = client;
}
[HttpGet("/")]
public async Task<ActionResult<Reply>> Index()
{
return await _client.GetMessageAsync();
}
}
Żądania POST, PUT i DELETE
W poprzednich przykładach wszystkie żądania HTTP używają czasownika GET HTTP. HttpClient
Obsługuje również inne czasowniki HTTP, w tym:
- POST
- PUT
- DELETE
- PATCH
Aby uzyskać pełną listę obsługiwanych czasowników HTTP, zobacz HttpMethod.
W poniższym przykładzie pokazano, jak utworzyć żądanie HTTP POST:
public async Task CreateItemAsync(TodoItem todoItem)
{
var todoItemJson = new StringContent(
JsonSerializer.Serialize(todoItem, _jsonSerializerOptions),
Encoding.UTF8,
"application/json");
using var httpResponse =
await _httpClient.PostAsync("/api/TodoItems", todoItemJson);
httpResponse.EnsureSuccessStatusCode();
}
W poprzednim kodzie CreateItemAsync
metoda:
- Serializuje parametr JSna
TodoItem
WŁ. przy użyciu poleceniaSystem.Text.Json
. Używa to wystąpienia programu JsonSerializerOptions do konfigurowania procesu serializacji. - Tworzy wystąpienie do StringContent spakowania serializowanego JSwłączonego do wysyłania w treści żądania HTTP.
- Wywołania PostAsync w celu wysłania JSzawartości ON do określonego adresu URL. Jest to względny adres URL, który jest dodawany do obiektu HttpClient.BaseAddress.
- Wywołania EnsureSuccessStatusCode w celu zgłoszenia wyjątku, jeśli kod stanu odpowiedzi nie wskazuje powodzenia.
HttpClient
obsługuje również inne typy zawartości. Na przykład MultipartContent i StreamContent. Aby uzyskać pełną listę obsługiwanych zawartości, zobacz HttpContent.
W poniższym przykładzie przedstawiono żądanie HTTP PUT:
public async Task SaveItemAsync(TodoItem todoItem)
{
var todoItemJson = new StringContent(
JsonSerializer.Serialize(todoItem),
Encoding.UTF8,
"application/json");
using var httpResponse =
await _httpClient.PutAsync($"/api/TodoItems/{todoItem.Id}", todoItemJson);
httpResponse.EnsureSuccessStatusCode();
}
Powyższy kod jest bardzo podobny do przykładu POST. Metoda SaveItemAsync
wywołuje PutAsync metodę PostAsync
zamiast .
W poniższym przykładzie przedstawiono żądanie HTTP DELETE:
public async Task DeleteItemAsync(long itemId)
{
using var httpResponse =
await _httpClient.DeleteAsync($"/api/TodoItems/{itemId}");
httpResponse.EnsureSuccessStatusCode();
}
W poprzednim kodzie metoda wywołuje DeleteAsyncmetodę DeleteItemAsync
. Ponieważ żądania HTTP DELETE zwykle nie zawierają treści, DeleteAsync
metoda nie zapewnia przeciążenia, które akceptuje wystąpienie klasy HttpContent
.
Aby dowiedzieć się więcej o korzystaniu z różnych czasowników HTTP w programie HttpClient
, zobacz HttpClient.
Oprogramowanie pośredniczące żądań wychodzących
HttpClient
ma koncepcję delegowania procedur obsługi, które mogą być połączone ze sobą dla wychodzących żądań HTTP. IHttpClientFactory
:
- Upraszcza definiowanie procedur obsługi do zastosowania dla każdego nazwanego klienta.
- Obsługuje rejestrację i tworzenie łańcucha wielu procedur obsługi w celu utworzenia potoku oprogramowania pośredniczącego żądań wychodzących. Każda z tych procedur obsługi może wykonać pracę przed żądaniem wychodzącym i po nim. Ten wzorzec:
- Jest podobny do potoku oprogramowania pośredniczącego dla ruchu przychodzącego w ASP.NET Core.
- Zapewnia mechanizm zarządzania problemami krzyżowymi dotyczącymi żądań HTTP, takich jak:
- Buforowanie
- obsługa błędów
- Serializacji
- rejestrowanie
Aby utworzyć program obsługi delegowania:
- Pochodzi z DelegatingHandlerelementu .
- Zastąpij SendAsync. Wykonaj kod przed przekazaniem żądania do następnej procedury obsługi w potoku:
public class ValidateHeaderHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
if (!request.Headers.Contains("X-API-KEY"))
{
return new HttpResponseMessage(HttpStatusCode.BadRequest)
{
Content = new StringContent(
"You must supply an API key header called X-API-KEY")
};
}
return await base.SendAsync(request, cancellationToken);
}
}
Powyższy kod sprawdza, czy X-API-KEY
nagłówek znajduje się w żądaniu. Jeśli X-API-KEY
brakuje, BadRequest jest zwracany.
Do konfiguracji programu można dodać więcej niż jedną procedurę HttpClient
obsługi za pomocą polecenia Microsoft.Extensions.DependencyInjection.HttpClientBuilderExtensions.AddHttpMessageHandler:
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<ValidateHeaderHandler>();
services.AddHttpClient("externalservice", c =>
{
// Assume this is an "external" service which requires an API KEY
c.BaseAddress = new Uri("https://localhost:5001/");
})
.AddHttpMessageHandler<ValidateHeaderHandler>();
// Remaining code deleted for brevity.
W poprzednim kodzie ValidateHeaderHandler
element jest zarejestrowany w usłudze DI. Po zarejestrowaniu AddHttpMessageHandler można wywołać metodę , przekazując typ programu obsługi.
Wiele procedur obsługi można zarejestrować w kolejności wykonywania. Każda procedura obsługi opakowuje następną procedurę obsługi do momentu HttpClientHandler
zakończenia wykonania żądania:
services.AddTransient<SecureRequestHandler>();
services.AddTransient<RequestDataHandler>();
services.AddHttpClient("clientwithhandlers")
// This handler is on the outside and called first during the
// request, last during the response.
.AddHttpMessageHandler<SecureRequestHandler>()
// This handler is on the inside, closest to the request being
// sent.
.AddHttpMessageHandler<RequestDataHandler>();
Używanie di w programie pośredniczącym żądań wychodzących
Podczas IHttpClientFactory
tworzenia nowej procedury obsługi delegowania używa di do spełnienia parametrów konstruktora programu obsługi. IHttpClientFactory
Tworzy oddzielny zakres di dla każdej procedury obsługi, co może prowadzić do zaskakującego zachowania, gdy program obsługi korzysta z usługi o określonym zakresie .
Rozważmy na przykład następujący interfejs i jego implementację, która reprezentuje zadanie jako operację z identyfikatorem: OperationId
public interface IOperationScoped
{
string OperationId { get; }
}
public class OperationScoped : IOperationScoped
{
public string OperationId { get; } = Guid.NewGuid().ToString()[^4..];
}
Jak sugeruje jego nazwa, IOperationScoped
jest rejestrowana w usłudze DI przy użyciu okresu istnienia o określonym zakresie :
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<TodoContext>(options =>
options.UseInMemoryDatabase("TodoItems"));
services.AddHttpContextAccessor();
services.AddHttpClient<TodoClient>((sp, httpClient) =>
{
var httpRequest = sp.GetRequiredService<IHttpContextAccessor>().HttpContext.Request;
// For sample purposes, assume TodoClient is used in the context of an incoming request.
httpClient.BaseAddress = new Uri(UriHelper.BuildAbsolute(httpRequest.Scheme,
httpRequest.Host, httpRequest.PathBase));
httpClient.Timeout = TimeSpan.FromSeconds(5);
});
services.AddScoped<IOperationScoped, OperationScoped>();
services.AddTransient<OperationHandler>();
services.AddTransient<OperationResponseHandler>();
services.AddHttpClient("Operation")
.AddHttpMessageHandler<OperationHandler>()
.AddHttpMessageHandler<OperationResponseHandler>()
.SetHandlerLifetime(TimeSpan.FromSeconds(5));
services.AddControllers();
services.AddRazorPages();
}
Poniższa procedura obsługi delegowania używa polecenia i używa IOperationScoped
go do ustawienia nagłówka X-OPERATION-ID
dla żądania wychodzącego:
public class OperationHandler : DelegatingHandler
{
private readonly IOperationScoped _operationService;
public OperationHandler(IOperationScoped operationScoped)
{
_operationService = operationScoped;
}
protected async override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Headers.Add("X-OPERATION-ID", _operationService.OperationId);
return await base.SendAsync(request, cancellationToken);
}
}
W pobieraniuHttpRequestsSample
przejdź do /Operation
strony i odśwież stronę. Wartość zakresu żądania zmienia się dla każdego żądania, ale wartość zakresu obsługi zmienia się tylko co 5 sekund.
Programy obsługi mogą zależeć od usług z dowolnego zakresu. Usługi obsługiwane przez programy obsługi zależą od tego, są usuwane po usunięciu programu obsługi.
Użyj jednej z następujących metod udostępniania stanu na żądanie za pomocą programów obsługi komunikatów:
- Przekazywanie danych do programu obsługi przy użyciu polecenia HttpRequestMessage.Properties.
- Użyj IHttpContextAccessor polecenia , aby uzyskać dostęp do bieżącego żądania.
- Utwórz niestandardowy AsyncLocal<T> obiekt magazynu, aby przekazać dane.
Korzystanie z programów obsługi opartych na usłudze Polly
IHttpClientFactory
integruje się z biblioteką innej firmy Polly. Polly to kompleksowa biblioteka obsługi błędów przejściowych dla platformy .NET. Dzięki temu deweloperzy mogą wyrażać zasady, takie jak ponawianie, wyłącznik, limit czasu, izolacja bulkhead i rezerwowy w sposób płynny i bezpieczny wątkowo.
Dostępne są metody rozszerzenia umożliwiające korzystanie z zasad Polly ze skonfigurowanymi HttpClient
wystąpieniami. Rozszerzenia Polly obsługują dodawanie programów obsługi opartych na usłudze Polly do klientów. Polly wymaga pakietu NuGet Microsoft.Extensions.Http.Polly .
Obsługa błędów przejściowych
Błędy zwykle występują, gdy zewnętrzne wywołania HTTP są przejściowe. AddTransientHttpErrorPolicy umożliwia zdefiniowanie zasad w celu obsługi błędów przejściowych. Zasady skonfigurowane z obsługą AddTransientHttpErrorPolicy
następujących odpowiedzi:
- HttpRequestException
- HTTP 5xx
- HTTP 408
AddTransientHttpErrorPolicy
zapewnia dostęp do obiektu skonfigurowanego PolicyBuilder
do obsługi błędów reprezentujących możliwy błąd przejściowy:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient<UnreliableEndpointCallerService>()
.AddTransientHttpErrorPolicy(p =>
p.WaitAndRetryAsync(3, _ => TimeSpan.FromMilliseconds(600)));
// Remaining code deleted for brevity.
W poprzednim kodzie WaitAndRetryAsync
zdefiniowano zasady. Żądania nieudane są ponawiane do trzech razy z opóźnieniem 600 ms między próbami.
Dynamiczne wybieranie zasad
Metody rozszerzeń są udostępniane w celu dodania programów obsługi opartych na usłudze Polly, na przykład AddPolicyHandler. Następujące AddPolicyHandler
przeciążenie sprawdza żądanie, aby zdecydować, które zasady mają być stosowane:
var timeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(10));
var longTimeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(30));
services.AddHttpClient("conditionalpolicy")
// Run some code to select a policy based on the request
.AddPolicyHandler(request =>
request.Method == HttpMethod.Get ? timeout : longTimeout);
W poprzednim kodzie, jeśli żądanie wychodzące jest żądaniem HTTP GET, zostanie zastosowany 10-sekundowy limit czasu. W przypadku każdej innej metody HTTP jest używany limit czasu 30 sekund.
Dodawanie wielu programów obsługi polly
Często należy zagnieżdżać zasady Polly:
services.AddHttpClient("multiplepolicies")
.AddTransientHttpErrorPolicy(p => p.RetryAsync(3))
.AddTransientHttpErrorPolicy(
p => p.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));
W powyższym przykładzie:
- Dodano dwie procedury obsługi.
- Pierwsza procedura obsługi używa AddTransientHttpErrorPolicy polecenia do dodawania zasad ponawiania prób. Żądania, które zakończyły się niepowodzeniem, są ponawiane do trzech razy.
- Drugie
AddTransientHttpErrorPolicy
wywołanie dodaje zasady wyłącznika. Dalsze żądania zewnętrzne są blokowane przez 30 sekund, jeśli 5 nieudanych prób nastąpi sekwencyjnie. Zasady wyłącznika są stanowe. Wszystkie wywołania za pośrednictwem tego klienta mają ten sam stan obwodu.
Dodawanie zasad z rejestru Polly
Podejście do zarządzania regularnie używanymi zasadami polega na zdefiniowaniu ich raz i zarejestrowaniu ich przy użyciu elementu PolicyRegistry
.
W poniższym kodzie:
- Dodawane są zasady "regularne" i "długie".
- AddPolicyHandlerFromRegistry dodaje zasady "regularne" i "długie" z rejestru.
public void ConfigureServices(IServiceCollection services)
{
var timeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(10));
var longTimeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(30));
var registry = services.AddPolicyRegistry();
registry.Add("regular", timeout);
registry.Add("long", longTimeout);
services.AddHttpClient("regularTimeoutHandler")
.AddPolicyHandlerFromRegistry("regular");
services.AddHttpClient("longTimeoutHandler")
.AddPolicyHandlerFromRegistry("long");
// Remaining code deleted for brevity.
Aby uzyskać więcej informacji na temat IHttpClientFactory
integracji usługi Polly, zobacz witrynę typu wiki Polly.
Zarządzanie klientem HttpClient i okresem istnienia
Nowe HttpClient
wystąpienie jest zwracane za każdym razem, gdy CreateClient
jest wywoływane IHttpClientFactory
polecenie . Element HttpMessageHandler jest tworzony na nazwanego klienta. Fabryka zarządza okresami HttpMessageHandler
istnienia wystąpień.
IHttpClientFactory
HttpMessageHandler
pule wystąpień utworzonych przez fabrykę w celu zmniejszenia zużycia zasobów. Wystąpienie HttpMessageHandler
może zostać ponownie użyte z puli podczas tworzenia nowego HttpClient
wystąpienia, jeśli jego okres istnienia nie wygasł.
Buforowanie procedur obsługi jest pożądane, ponieważ każda procedura obsługi zwykle zarządza własnymi podstawowymi połączeniami HTTP. Tworzenie większej liczby procedur obsługi niż jest to konieczne może spowodować opóźnienia połączeń. Niektóre programy obsługi utrzymują również połączenia otwarte na czas nieokreślony, co może uniemożliwić programowi obsługi reagowanie na zmiany systemu DNS (systemu nazw domen).
Domyślny okres istnienia procedury obsługi to dwie minuty. Wartość domyślna może być zastępowana według nazwanego klienta:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient("extendedhandlerlifetime")
.SetHandlerLifetime(TimeSpan.FromMinutes(5));
// Remaining code deleted for brevity.
HttpClient
wystąpienia mogą być zwykle traktowane jako obiekty platformy .NET , które nie wymagają usuwania. Usuwanie anuluje żądania wychodzące i gwarantuje, że dane HttpClient
wystąpienie nie może być używane po wywołaniu polecenia Dispose. IHttpClientFactory
śledzi i usuwa zasoby używane przez HttpClient
wystąpienia.
Utrzymywanie pojedynczego HttpClient
wystąpienia przy życiu przez długi czas jest typowym wzorcem używanym przed utworzeniem elementu IHttpClientFactory
. Ten wzorzec staje się niepotrzebny po przeprowadzeniu migracji do IHttpClientFactory
programu .
Alternatywy dla IHttpClientFactory
Używanie IHttpClientFactory
w aplikacji z włączoną obsługą di pozwala uniknąć:
- Problemy z wyczerpaniem zasobów przez wystąpienia puli
HttpMessageHandler
. - Nieaktualne problemy z systemem DNS przez wystąpienia rowerowe
HttpMessageHandler
w regularnych odstępach czasu.
Istnieją alternatywne sposoby rozwiązywania powyższych problemów przy użyciu długotrwałego SocketsHttpHandler wystąpienia.
- Utwórz wystąpienie
SocketsHttpHandler
, gdy aplikacja zostanie uruchomiona i użyj jej na całe życie aplikacji. - Skonfiguruj PooledConnectionLifetime odpowiednią wartość na podstawie czasów odświeżania DNS.
- Utwórz
HttpClient
wystąpienia przy użyciu zgodnie znew HttpClient(handler, disposeHandler: false)
potrzebami.
Powyższe podejścia rozwiązują problemy z zarządzaniem zasobami, które IHttpClientFactory
są rozwiązywane w podobny sposób.
- Udziały
SocketsHttpHandler
połączeń międzyHttpClient
wystąpieniami. To udostępnianie uniemożliwia wyczerpanie gniazd. - Połączenia
SocketsHttpHandler
cykli zgodnie z, abyPooledConnectionLifetime
uniknąć nieaktualnych problemów z systemem DNS.
CookieS
Wystąpienia w puli HttpMessageHandler
powoduje CookieContainer
udostępnianie obiektów. Nieprzewidziane CookieContainer
udostępnianie obiektów często powoduje niepoprawny kod. W przypadku aplikacji wymagających wartości cookies należy wziąć pod uwagę jedną z następujących kwestii:
- Wyłączanie automatycznej cookie obsługi
- Unikanie
IHttpClientFactory
Wywołaj, ConfigurePrimaryHttpMessageHandler aby wyłączyć automatyczną cookie obsługę:
services.AddHttpClient("configured-disable-automatic-cookies")
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler()
{
UseCookies = false,
};
});
Rejestrowanie
Klienci utworzeni za pośrednictwem IHttpClientFactory
komunikatów dziennika rekordów dla wszystkich żądań. Włącz odpowiedni poziom informacji w konfiguracji rejestrowania, aby wyświetlić domyślne komunikaty dziennika. Dodatkowe rejestrowanie, takie jak rejestrowanie nagłówków żądań, jest uwzględniane tylko na poziomie śledzenia.
Kategoria dziennika używana dla każdego klienta zawiera nazwę klienta. Klient o nazwie MyNamedClient, na przykład, rejestruje komunikaty z kategorią "System.Net.Http.HttpClient. MyNamedClient. Logiczna procedura obsługi". Komunikaty sufiksowane za pomocą programu LogicalHandler występują poza potokiem obsługi żądań. W żądaniu komunikaty są rejestrowane, zanim wszystkie inne programy obsługi w potoku przetworzyły je. W odpowiedzi komunikaty są rejestrowane po otrzymaniu odpowiedzi przez inne programy obsługi potoku.
Rejestrowanie odbywa się również wewnątrz potoku procedury obsługi żądań. W przykładzie MyNamedClient te komunikaty są rejestrowane z kategorią dziennika "System.Net.Http.HttpClient. MyNamedClient. ClientHandler". W przypadku żądania dzieje się tak po uruchomieniu wszystkich innych procedur obsługi i bezpośrednio przed wysłaniem żądania. W odpowiedzi to rejestrowanie zawiera stan odpowiedzi, zanim przejdzie z powrotem przez potok obsługi.
Włączenie rejestrowania na zewnątrz i wewnątrz potoku umożliwia inspekcję zmian wprowadzonych przez inne programy obsługi potoków. Może to obejmować zmiany nagłówków żądań lub kod stanu odpowiedzi.
Uwzględnienie nazwy klienta w kategorii dziennika umożliwia filtrowanie dzienników dla określonych nazwanych klientów.
Konfigurowanie programu HttpMessageHandler
Może być konieczne kontrolowanie konfiguracji wewnętrznej HttpMessageHandler
używanej przez klienta.
Element IHttpClientBuilder
jest zwracany podczas dodawania nazwanych lub wpisanych klientów. Metoda ConfigurePrimaryHttpMessageHandler rozszerzenia może służyć do definiowania delegata. Delegat służy do tworzenia i konfigurowania podstawowego HttpMessageHandler
używanego przez tego klienta:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient("configured-inner-handler")
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler()
{
AllowAutoRedirect = false,
UseDefaultCredentials = true
};
});
// Remaining code deleted for brevity.
Używanie elementu IHttpClientFactory w aplikacji konsolowej
W aplikacji konsolowej dodaj następujące odwołania do pakietu do projektu:
W poniższym przykładzie:
- IHttpClientFactory jest zarejestrowany w kontenerze usługi hosta ogólnego .
MyService
tworzy wystąpienie fabryki klienta na podstawie usługi, która jest używana do utworzenia elementuHttpClient
.HttpClient
służy do pobierania strony internetowej.Main
Tworzy zakres umożliwiający wykonanie metody usługiGetPage
i zapisanie pierwszych 500 znaków zawartości strony internetowej w konsoli programu .
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
class Program
{
static async Task<int> Main(string[] args)
{
var builder = new HostBuilder()
.ConfigureServices((hostContext, services) =>
{
services.AddHttpClient();
services.AddTransient<IMyService, MyService>();
}).UseConsoleLifetime();
var host = builder.Build();
try
{
var myService = host.Services.GetRequiredService<IMyService>();
var pageContent = await myService.GetPage();
Console.WriteLine(pageContent.Substring(0, 500));
}
catch (Exception ex)
{
var logger = host.Services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred.");
}
return 0;
}
public interface IMyService
{
Task<string> GetPage();
}
public class MyService : IMyService
{
private readonly IHttpClientFactory _clientFactory;
public MyService(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task<string> GetPage()
{
// Content from BBC One: Dr. Who website (©BBC)
var request = new HttpRequestMessage(HttpMethod.Get,
"https://www.bbc.co.uk/programmes/b006q2x0");
var client = _clientFactory.CreateClient();
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
return await response.Content.ReadAsStringAsync();
}
else
{
return $"StatusCode: {response.StatusCode}";
}
}
}
}
Oprogramowanie pośredniczące propagacji nagłówka
Propagacja nagłówka to oprogramowanie pośredniczące ASP.NET Core propagujące nagłówki HTTP z żądania przychodzącego do wychodzących żądań klienta HTTP. Aby użyć propagacji nagłówka:
Odwołaj się do pakietu Microsoft.AspNetCore.HeaderPropagation .
Skonfiguruj oprogramowanie pośredniczące i
HttpClient
w programieStartup
:public void ConfigureServices(IServiceCollection services) { services.AddControllers(); services.AddHttpClient("MyForwardingClient").AddHeaderPropagation(); services.AddHeaderPropagation(options => { options.Headers.Add("X-TraceId"); }); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseHttpsRedirection(); app.UseHeaderPropagation(); app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); }
Klient zawiera skonfigurowane nagłówki dla żądań wychodzących:
var client = clientFactory.CreateClient("MyForwardingClient"); var response = client.GetAsync(...);
Dodatkowe zasoby
Przez Glenn Condron, Ryan Nowak i Steve Gordon
Element IHttpClientFactory można zarejestrować i użyć do konfigurowania i tworzenia HttpClient wystąpień w aplikacji. Oferuje ona następujące korzyści:
- Zapewnia centralną lokalizację nazewnictwa i konfigurowania wystąpień logicznych
HttpClient
. Na przykład można zarejestrować i skonfigurować klienta github w celu uzyskania dostępu do usługi GitHub. Domyślny klient może być zarejestrowany w innych celach. - Koduje koncepcję wychodzącego oprogramowania pośredniczącego poprzez delegowanie programów obsługi w systemie
HttpClient
i udostępnia rozszerzenia oprogramowania pośredniczącego opartego na polly, aby skorzystać z tego. - Zarządza buforowaniem i okresem istnienia wystąpień bazowych
HttpClientMessageHandler
, aby uniknąć typowych problemów z systemem DNS występujących podczas ręcznego zarządzania okresamiHttpClient
istnienia. - Dodaje konfigurowalne środowisko rejestrowania (za pośrednictwem
ILogger
) dla wszystkich żądań wysyłanych przez klientów utworzonych przez fabrykę.
Wyświetl lub pobierz przykładowy kod (jak pobrać)
Wymagania wstępne
Projekty przeznaczone dla .NET Framework wymagają instalacji pakietu NuGet Microsoft.Extensions.Http. Projekty przeznaczone dla platformy .NET Core i odwołujące się do Microsoft.AspNetCore.App metapakiet już zawierają Microsoft.Extensions.Http
pakiet.
Konsumpcji
Istnieje kilka sposobów IHttpClientFactory
użycia w aplikacji:
Żaden z nich nie jest ściśle lepszy od innego. Najlepsze podejście zależy od ograniczeń aplikacji.
Podstawowy sposób użycia
Można IHttpClientFactory
je zarejestrować, wywołując metodę AddHttpClient
rozszerzenia w metodzie IServiceCollection
Startup.ConfigureServices
, wewnątrz metody .
services.AddHttpClient();
Po zarejestrowaniu kod może akceptować IHttpClientFactory
usługi w dowolnym miejscu, które można wstrzyknąć za pomocą wstrzykiwania zależności (DI). Można IHttpClientFactory
go użyć do utworzenia HttpClient
wystąpienia:
public class BasicUsageModel : PageModel
{
private readonly IHttpClientFactory _clientFactory;
public IEnumerable<GitHubBranch> Branches { get; private set; }
public bool GetBranchesError { get; private set; }
public BasicUsageModel(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task OnGet()
{
var request = new HttpRequestMessage(HttpMethod.Get,
"https://api.github.com/repos/dotnet/AspNetCore.Docs/branches");
request.Headers.Add("Accept", "application/vnd.github.v3+json");
request.Headers.Add("User-Agent", "HttpClientFactory-Sample");
var client = _clientFactory.CreateClient();
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
Branches = await response.Content
.ReadAsAsync<IEnumerable<GitHubBranch>>();
}
else
{
GetBranchesError = true;
Branches = Array.Empty<GitHubBranch>();
}
}
}
Użycie IHttpClientFactory
w ten sposób jest dobrym sposobem refaktoryzacji istniejącej aplikacji. Nie ma wpływu na sposób HttpClient
użycia. W miejscach, w których HttpClient
wystąpienia są obecnie tworzone, zastąp te wystąpienia wywołaniem .CreateClient
Nazwani klienci
Jeśli aplikacja wymaga wielu odrębnych zastosowań programu HttpClient
, z których każda ma inną konfigurację, należy użyć nazwanych klientów. Konfigurację nazwanego HttpClient
można określić podczas rejestracji w programie Startup.ConfigureServices
.
services.AddHttpClient("github", c =>
{
c.BaseAddress = new Uri("https://api.github.com/");
// Github API versioning
c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
// Github requires a user-agent
c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});
W poprzednim kodzie AddHttpClient
jest wywoływana nazwa github. Ten klient ma zastosowaną konfigurację domyślną — czyli podstawowy adres i dwa nagłówki wymagane do pracy z interfejsem API usługi GitHub.
Za każdym razem CreateClient
wywoływane jest nowe wystąpienie HttpClient
, a akcja konfiguracji jest wywoływana.
Aby korzystać z nazwanego klienta, parametr ciągu można przekazać do CreateClient
. Określ nazwę klienta do utworzenia:
public class NamedClientModel : PageModel
{
private readonly IHttpClientFactory _clientFactory;
public IEnumerable<GitHubPullRequest> PullRequests { get; private set; }
public bool GetPullRequestsError { get; private set; }
public bool HasPullRequests => PullRequests.Any();
public NamedClientModel(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task OnGet()
{
var request = new HttpRequestMessage(HttpMethod.Get,
"repos/dotnet/AspNetCore.Docs/pulls");
var client = _clientFactory.CreateClient("github");
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
PullRequests = await response.Content
.ReadAsAsync<IEnumerable<GitHubPullRequest>>();
}
else
{
GetPullRequestsError = true;
PullRequests = Array.Empty<GitHubPullRequest>();
}
}
}
W poprzednim kodzie żądanie nie musi określać nazwy hosta. Może ona przejść tylko ścieżkę, ponieważ używany jest adres podstawowy skonfigurowany dla klienta.
Klienci typizowane
Klienci typizowane:
- Zapewnij te same możliwości, co nazwani klienci bez konieczności używania ciągów jako kluczy.
- Zapewnia funkcję IntelliSense i pomoc kompilatora podczas korzystania z klientów.
- Podaj pojedynczą lokalizację do skonfigurowania określonego elementu i interakcji z nim
HttpClient
. Na przykład jeden typ klienta może być używany dla pojedynczego punktu końcowego zaplecza i hermetyzować całą logikę zajmującą się tym punktem końcowym. - Współpracuj z di i można wstrzyknąć tam, gdzie jest to wymagane w aplikacji.
Typowany klient akceptuje HttpClient
parametr w konstruktorze:
public class GitHubService
{
public HttpClient Client { get; }
public GitHubService(HttpClient client)
{
client.BaseAddress = new Uri("https://api.github.com/");
// GitHub API versioning
client.DefaultRequestHeaders.Add("Accept",
"application/vnd.github.v3+json");
// GitHub requires a user-agent
client.DefaultRequestHeaders.Add("User-Agent",
"HttpClientFactory-Sample");
Client = client;
}
public async Task<IEnumerable<GitHubIssue>> GetAspNetDocsIssues()
{
var response = await Client.GetAsync(
"/repos/dotnet/AspNetCore.Docs/issues?state=open&sort=created&direction=desc");
response.EnsureSuccessStatusCode();
var result = await response.Content
.ReadAsAsync<IEnumerable<GitHubIssue>>();
return result;
}
}
W poprzednim kodzie konfiguracja jest przenoszona do typizowanego klienta. Obiekt HttpClient
jest udostępniany jako właściwość publiczna. Istnieje możliwość zdefiniowania metod specyficznych dla interfejsu API, które uwidaczniają HttpClient
funkcjonalność. Metoda GetAspNetDocsIssues
hermetyzuje kod potrzebny do wykonywania zapytań i analizowania najnowszych otwartych problemów z repozytorium GitHub.
Aby zarejestrować typizowanego klienta, można użyć ogólnej AddHttpClient metody rozszerzenia w programie Startup.ConfigureServices
, określając typową klasę klienta:
services.AddHttpClient<GitHubService>();
Typizowanego klienta jest rejestrowany jako przejściowy z di. Typizowanego klienta można wstrzyknąć i używać bezpośrednio:
public class TypedClientModel : PageModel
{
private readonly GitHubService _gitHubService;
public IEnumerable<GitHubIssue> LatestIssues { get; private set; }
public bool HasIssue => LatestIssues.Any();
public bool GetIssuesError { get; private set; }
public TypedClientModel(GitHubService gitHubService)
{
_gitHubService = gitHubService;
}
public async Task OnGet()
{
try
{
LatestIssues = await _gitHubService.GetAspNetDocsIssues();
}
catch(HttpRequestException)
{
GetIssuesError = true;
LatestIssues = Array.Empty<GitHubIssue>();
}
}
}
Jeśli jest to preferowane, konfigurację typizowanego klienta można określić podczas rejestracji w Startup.ConfigureServices
programie , a nie w konstruktorze typizowanego klienta:
services.AddHttpClient<RepoService>(c =>
{
c.BaseAddress = new Uri("https://api.github.com/");
c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});
Można całkowicie hermetyzować HttpClient
w ramach typizowanego klienta. Zamiast uwidaczniać je jako właściwość, można podać metody publiczne, które wywołają HttpClient
wystąpienie wewnętrznie.
public class RepoService
{
// _httpClient isn't exposed publicly
private readonly HttpClient _httpClient;
public RepoService(HttpClient client)
{
_httpClient = client;
}
public async Task<IEnumerable<string>> GetRepos()
{
var response = await _httpClient.GetAsync("aspnet/repos");
response.EnsureSuccessStatusCode();
var result = await response.Content
.ReadAsAsync<IEnumerable<string>>();
return result;
}
}
W poprzednim kodzie element HttpClient
jest przechowywany jako pole prywatne. Cały dostęp do wywołań zewnętrznych przechodzi przez metodę GetRepos
.
Wygenerowani klienci
IHttpClientFactory
można używać w połączeniu z innymi bibliotekami innych firm, takimi jak Refit. Refit to REST biblioteka platformy .NET. Konwertuje interfejsy REST API na interfejsy na żywo. Implementacja interfejsu jest generowana dynamicznie przez RestService
element , za pomocą polecenia HttpClient
w celu wykonywania zewnętrznych wywołań HTTP.
Interfejs i odpowiedź są zdefiniowane w celu reprezentowania zewnętrznego interfejsu API i jego odpowiedzi:
public interface IHelloClient
{
[Get("/helloworld")]
Task<Reply> GetMessageAsync();
}
public class Reply
{
public string Message { get; set; }
}
Można dodać typizowanego klienta, używając polecenia Dopasuj do generowania implementacji:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient("hello", c =>
{
c.BaseAddress = new Uri("http://localhost:5000");
})
.AddTypedClient(c => Refit.RestService.For<IHelloClient>(c));
services.AddMvc();
}
Zdefiniowany interfejs może być używany w razie potrzeby z implementacją dostarczaną przez di i refit:
[ApiController]
public class ValuesController : ControllerBase
{
private readonly IHelloClient _client;
public ValuesController(IHelloClient client)
{
_client = client;
}
[HttpGet("/")]
public async Task<ActionResult<Reply>> Index()
{
return await _client.GetMessageAsync();
}
}
Oprogramowanie pośredniczące żądań wychodzących
HttpClient
Już ma koncepcję delegowania procedur obsługi, które mogą być połączone razem dla wychodzących żądań HTTP. Dzięki IHttpClientFactory
temu można łatwo zdefiniować programy obsługi, które mają być stosowane dla każdego nazwanego klienta. Obsługuje rejestrację i tworzenie łańcucha wielu procedur obsługi w celu utworzenia potoku oprogramowania pośredniczącego żądań wychodzących. Każdy z tych programów obsługi może wykonać pracę przed i po żądaniu wychodzącym. Ten wzorzec jest podobny do potoku oprogramowania pośredniczącego dla ruchu przychodzącego w ASP.NET Core. Wzorzec zapewnia mechanizm zarządzania problemami krzyżowymi dotyczącymi żądań HTTP, w tym buforowania, obsługi błędów, serializacji i rejestrowania.
Aby utworzyć procedurę obsługi, zdefiniuj klasę pochodną klasy .DelegatingHandler Zastąpij metodę SendAsync
, aby wykonać kod przed przekazaniem żądania do następnej procedury obsługi w potoku:
public class ValidateHeaderHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
if (!request.Headers.Contains("X-API-KEY"))
{
return new HttpResponseMessage(HttpStatusCode.BadRequest)
{
Content = new StringContent(
"You must supply an API key header called X-API-KEY")
};
}
return await base.SendAsync(request, cancellationToken);
}
}
Powyższy kod definiuje podstawową procedurę obsługi. Sprawdza, czy X-API-KEY
nagłówek został uwzględniony w żądaniu. Jeśli brakuje nagłówka, może uniknąć wywołania HTTP i zwrócić odpowiednią odpowiedź.
Podczas rejestracji do konfiguracji programu można dodać co najmniej jedną procedurę obsługi dla elementu HttpClient
. To zadanie jest wykonywane za pomocą metod rozszerzeń w obiekcie IHttpClientBuilder.
services.AddTransient<ValidateHeaderHandler>();
services.AddHttpClient("externalservice", c =>
{
// Assume this is an "external" service which requires an API KEY
c.BaseAddress = new Uri("https://localhost:5000/");
})
.AddHttpMessageHandler<ValidateHeaderHandler>();
W poprzednim kodzie ValidateHeaderHandler
element jest zarejestrowany za pomocą di. Program obsługi musi być zarejestrowany w di jako usługa przejściowa, nigdy o określonym zakresie. Jeśli program obsługi jest zarejestrowany jako usługa o określonym zakresie, a wszystkie usługi, od których zależy program obsługi, są możliwe do likwidacji:
- Usługi programu obsługi mogą zostać usunięte przed wyjściem programu obsługi z zakresu.
- Usunięte usługi obsługi powodują niepowodzenie programu obsługi.
Po zarejestrowaniu AddHttpMessageHandler można wywołać metodę , przekazując typ procedury obsługi.
Wiele procedur obsługi można zarejestrować w kolejności, w której powinny być wykonywane. Każda procedura obsługi opakowuje następną procedurę obsługi do momentu, aż końcowy HttpClientHandler
wykona żądanie:
services.AddTransient<SecureRequestHandler>();
services.AddTransient<RequestDataHandler>();
services.AddHttpClient("clientwithhandlers")
// This handler is on the outside and called first during the
// request, last during the response.
.AddHttpMessageHandler<SecureRequestHandler>()
// This handler is on the inside, closest to the request being
// sent.
.AddHttpMessageHandler<RequestDataHandler>();
Użyj jednej z następujących metod udostępniania stanu poszczególnych żądań z procedurami obsługi komunikatów:
- Przekazywanie danych do programu obsługi przy użyciu polecenia
HttpRequestMessage.Properties
. - Użyj polecenia
IHttpContextAccessor
, aby uzyskać dostęp do bieżącego żądania. - Utwórz niestandardowy
AsyncLocal
obiekt magazynu w celu przekazania danych.
Korzystanie z programów obsługi opartych na usłudze Polly
IHttpClientFactory
integruje się z popularną biblioteką innej firmy o nazwie Polly. Polly to kompleksowa odporność i biblioteka obsługi błędów przejściowych dla platformy .NET. Umożliwia deweloperom wyrażanie zasad, takich jak ponawianie prób, wyłącznik, przekroczenie limitu czasu, izolacja grodziowa i powrót w sposób płynny i bezpieczny wątkowo.
Dostępne są metody rozszerzenia umożliwiające korzystanie z zasad polly ze skonfigurowanymi HttpClient
wystąpieniami. Rozszerzenia Polly:
- Obsługa dodawania programów obsługi opartych na usłudze Polly do klientów.
- Można go użyć po zainstalowaniu pakietu NuGet Microsoft.Extensions.Http.Polly . Pakiet nie jest uwzględniony w strukturze udostępnionej ASP.NET Core.
Obsługa błędów przejściowych
Najczęstsze błędy występują, gdy zewnętrzne wywołania HTTP są przejściowe. Dołączono wygodną metodę AddTransientHttpErrorPolicy
rozszerzenia, która umożliwia zdefiniowanie zasad w celu obsługi błędów przejściowych. Zasady skonfigurowane przy użyciu tej metody rozszerzenia obsługują HttpRequestException
odpowiedzi HTTP 5xx i odpowiedzi HTTP 408.
Rozszerzenie AddTransientHttpErrorPolicy
może być używane w programie Startup.ConfigureServices
. Rozszerzenie zapewnia dostęp do obiektu skonfigurowanego PolicyBuilder
do obsługi błędów reprezentujących możliwy błąd przejściowy:
services.AddHttpClient<UnreliableEndpointCallerService>()
.AddTransientHttpErrorPolicy(p =>
p.WaitAndRetryAsync(3, _ => TimeSpan.FromMilliseconds(600)));
W poprzednim kodzie zdefiniowano WaitAndRetryAsync
zasady. Żądania, które zakończyły się niepowodzeniem, są ponawiane do trzech razy z opóźnieniem 600 ms między próbami.
Dynamiczne wybieranie zasad
Istnieją dodatkowe metody rozszerzenia, których można użyć do dodawania procedur obsługi opartych na usłudze Polly. Jednym z takich rozszerzeń jest AddPolicyHandler
, które ma wiele przeciążeń. Jedno przeciążenie umożliwia sprawdzenie żądania podczas definiowania zasad do zastosowania:
var timeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(10));
var longTimeout = Policy.TimeoutAsync<HttpResponseMessage>(
TimeSpan.FromSeconds(30));
services.AddHttpClient("conditionalpolicy")
// Run some code to select a policy based on the request
.AddPolicyHandler(request =>
request.Method == HttpMethod.Get ? timeout : longTimeout);
W poprzednim kodzie, jeśli żądanie wychodzące jest żądaniem HTTP GET, zostanie zastosowany 10-sekundowy limit czasu. W przypadku każdej innej metody HTTP jest używany limit czasu 30 sekund.
Dodawanie wielu procedur obsługi polly
Typowe jest zagnieżdżanie zasad polly w celu zapewnienia rozszerzonych funkcji:
services.AddHttpClient("multiplepolicies")
.AddTransientHttpErrorPolicy(p => p.RetryAsync(3))
.AddTransientHttpErrorPolicy(
p => p.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));
W poprzednim przykładzie są dodawane dwa programy obsługi. Pierwszy używa AddTransientHttpErrorPolicy
rozszerzenia do dodawania zasad ponawiania. Żądania, które zakończyły się niepowodzeniem, są ponawiane do trzech razy. Drugie wywołanie metody powoduje AddTransientHttpErrorPolicy
dodanie zasad wyłącznika. Kolejne żądania zewnętrzne są blokowane przez 30 sekund, jeśli pięć nieudanych prób nastąpi sekwencyjnie. Zasady wyłącznika są stanowe. Wszystkie wywołania za pośrednictwem tego klienta mają ten sam stan obwodu.
Dodawanie zasad z rejestru Polly
Podejście do zarządzania regularnie używanymi zasadami polega na zdefiniowaniu ich raz i zarejestrowaniu ich w obiekcie PolicyRegistry
. Udostępniono metodę rozszerzenia, która umożliwia dodanie programu obsługi przy użyciu zasad z rejestru:
var registry = services.AddPolicyRegistry();
registry.Add("regular", timeout);
registry.Add("long", longTimeout);
services.AddHttpClient("regulartimeouthandler")
.AddPolicyHandlerFromRegistry("regular");
W poprzednim kodzie dwie zasady są rejestrowane po PolicyRegistry
dodaniu elementu do elementu ServiceCollection
. Aby użyć zasad z rejestru, AddPolicyHandlerFromRegistry
używana jest metoda przekazująca nazwę zasad do zastosowania.
Więcej informacji na temat IHttpClientFactory
integracji usługi Polly można znaleźć na stronie wiki polly.
Zarządzanie klientem HttpClient i okresem istnienia
HttpClient
Nowe wystąpienie jest zwracane za każdym razem, gdy CreateClient
jest wywoływane w elemecie IHttpClientFactory
. Istnieje nazwany HttpMessageHandler klient. Fabryka zarządza okresami HttpMessageHandler
istnienia wystąpień.
IHttpClientFactory
HttpMessageHandler
pule wystąpień utworzonych przez fabrykę w celu zmniejszenia zużycia zasobów. Wystąpienie HttpMessageHandler
może zostać ponownie użyte z puli podczas tworzenia nowego HttpClient
wystąpienia, jeśli jego okres istnienia nie wygasł.
Buforowanie procedur obsługi jest pożądane, ponieważ każda procedura obsługi zwykle zarządza własnymi podstawowymi połączeniami HTTP. Utworzenie większej liczby procedur obsługi niż jest to konieczne może spowodować opóźnienia połączeń. Niektóre programy obsługi utrzymują również otwarte połączenia na czas nieokreślony, co może uniemożliwić programowi obsługi reagowanie na zmiany DNS.
Domyślny okres obsługi to dwie minuty. Wartość domyślna może zostać zastąpiona na podstawie nazwanego klienta. Aby go zastąpić, wywołaj SetHandlerLifetime metodę , IHttpClientBuilder
która jest zwracana podczas tworzenia klienta:
services.AddHttpClient("extendedhandlerlifetime")
.SetHandlerLifetime(TimeSpan.FromMinutes(5));
Usuwanie klienta nie jest wymagane. Usuwanie anuluje wychodzące żądania i gwarantuje, że dane HttpClient
wystąpienie nie może być używane po wywołaniu metody Dispose. IHttpClientFactory
śledzi i usuwa zasoby używane przez HttpClient
wystąpienia. Wystąpienia HttpClient
mogą być zwykle traktowane jako obiekty platformy .NET, które nie wymagają usuwania.
Utrzymywanie aktywności pojedynczego HttpClient
wystąpienia przez długi czas jest typowym wzorcem używanym przed utworzeniem klasy IHttpClientFactory
. Ten wzorzec staje się zbędny po przeprowadzeniu migracji do programu IHttpClientFactory
.
Alternatywy dla IHttpClientFactory
Użycie IHttpClientFactory
w aplikacji z włączoną obsługą di pozwala uniknąć:
- Problemy z wyczerpaniem zasobów przez wystąpienia puli
HttpMessageHandler
. - Nieaktualne problemy z systemem DNS przez wystąpienia rowerowe
HttpMessageHandler
w regularnych odstępach czasu.
Istnieją alternatywne sposoby rozwiązywania powyższych problemów przy użyciu długotrwałego SocketsHttpHandler wystąpienia.
- Utwórz wystąpienie aplikacji
SocketsHttpHandler
, gdy aplikacja zostanie uruchomiona i użyj jej do końca życia aplikacji. - Skonfiguruj PooledConnectionLifetime do odpowiedniej wartości na podstawie czasów odświeżania DNS.
- Utwórz
HttpClient
wystąpienia przy użyciu zgodnie znew HttpClient(handler, disposeHandler: false)
potrzebami.
Powyższe podejścia rozwiązują problemy z zarządzaniem zasobami, które IHttpClientFactory
są rozwiązywane w podobny sposób.
- Udziały
SocketsHttpHandler
połączeń międzyHttpClient
wystąpieniami. Udostępnianie zapobiega wyczerpaniu gniazd. - Połączenia
SocketsHttpHandler
cykli zgodnie z, abyPooledConnectionLifetime
uniknąć nieaktualnych problemów DNS.
CookieS
Wystąpienia w HttpMessageHandler
puli powoduje CookieContainer
współużytkowanie obiektów. Nieprzewidziane CookieContainer
udostępnianie obiektów często powoduje niepoprawny kod. W przypadku aplikacji wymagających cookiewartości należy wziąć pod uwagę następujące kwestie:
- Wyłączanie automatycznej cookie obsługi
- Unikanie
IHttpClientFactory
Wywołanie ConfigurePrimaryHttpMessageHandler w celu wyłączenia automatycznej cookie obsługi:
services.AddHttpClient("configured-disable-automatic-cookies")
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler()
{
UseCookies = false,
};
});
Rejestrowanie
Klienci utworzeni za pośrednictwem IHttpClientFactory
komunikatów dziennika rekordów dla wszystkich żądań. Włącz odpowiedni poziom informacji w konfiguracji rejestrowania, aby wyświetlić domyślne komunikaty dziennika. Dodatkowe rejestrowanie, takie jak rejestrowanie nagłówków żądań, jest uwzględniane tylko na poziomie śledzenia.
Kategoria dziennika używana dla każdego klienta zawiera nazwę klienta. Klient o nazwie MyNamedClient, na przykład, rejestruje komunikaty z kategorią System.Net.Http.HttpClient.MyNamedClient.LogicalHandler
. Komunikaty z sufiksem LogicalHandler występują poza potokiem obsługi żądań. W żądaniu komunikaty są rejestrowane, zanim wszystkie inne programy obsługi w potoku przetworzyły je. W odpowiedzi komunikaty są rejestrowane po otrzymaniu odpowiedzi przez inne programy obsługi potoku.
Rejestrowanie odbywa się również wewnątrz potoku procedury obsługi żądań. W przykładzie MyNamedClient te komunikaty są rejestrowane w kategorii System.Net.Http.HttpClient.MyNamedClient.ClientHandler
dziennika . W przypadku żądania odbywa się to po uruchomieniu wszystkich innych procedur obsługi i bezpośrednio przed wysłaniem żądania w sieci. W odpowiedzi to rejestrowanie zawiera stan odpowiedzi, zanim przejdzie z powrotem przez potok obsługi.
Włączenie rejestrowania poza potokiem i wewnątrz potoku umożliwia inspekcję zmian wprowadzonych przez inne procedury obsługi potoku. Może to obejmować zmiany nagłówków żądań, na przykład lub kod stanu odpowiedzi.
Uwzględnienie nazwy klienta w kategorii dziennika umożliwia filtrowanie dzienników dla określonych nazwanych klientów, jeśli jest to konieczne.
Konfigurowanie programu HttpMessageHandler
Może być konieczne kontrolowanie konfiguracji wewnętrznej HttpMessageHandler
używanej przez klienta.
Obiekt IHttpClientBuilder
jest zwracany podczas dodawania nazwanych lub wpisanych klientów. Metoda ConfigurePrimaryHttpMessageHandler rozszerzenia może służyć do definiowania delegata. Delegat służy do tworzenia i konfigurowania podstawowego HttpMessageHandler
używanego przez tego klienta:
services.AddHttpClient("configured-inner-handler")
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler()
{
AllowAutoRedirect = false,
UseDefaultCredentials = true
};
});
Używanie elementu IHttpClientFactory w aplikacji konsolowej
W aplikacji konsolowej dodaj następujące odwołania do pakietu do projektu:
W poniższym przykładzie:
- IHttpClientFactory jest zarejestrowany w kontenerze usługi hosta ogólnego .
MyService
Tworzy wystąpienie fabryki klienta na podstawie usługi, która jest używana do utworzenia obiektuHttpClient
.HttpClient
służy do pobierania strony internetowej.- Metoda usługi jest wykonywana
GetPage
w celu zapisania pierwszych 500 znaków zawartości strony internetowej w konsoli. Aby uzyskać więcej informacji na temat wywoływania usług zProgram.Main
programu , zobacz Wstrzykiwanie zależności w ASP.NET Core.
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
class Program
{
static async Task<int> Main(string[] args)
{
var builder = new HostBuilder()
.ConfigureServices((hostContext, services) =>
{
services.AddHttpClient();
services.AddTransient<IMyService, MyService>();
}).UseConsoleLifetime();
var host = builder.Build();
try
{
var myService = host.Services.GetRequiredService<IMyService>();
var pageContent = await myService.GetPage();
Console.WriteLine(pageContent.Substring(0, 500));
}
catch (Exception ex)
{
var logger = host.Services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred.");
}
return 0;
}
public interface IMyService
{
Task<string> GetPage();
}
public class MyService : IMyService
{
private readonly IHttpClientFactory _clientFactory;
public MyService(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task<string> GetPage()
{
// Content from BBC One: Dr. Who website (©BBC)
var request = new HttpRequestMessage(HttpMethod.Get,
"https://www.bbc.co.uk/programmes/b006q2x0");
var client = _clientFactory.CreateClient();
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
return await response.Content.ReadAsStringAsync();
}
else
{
return $"StatusCode: {response.StatusCode}";
}
}
}
}
Oprogramowanie pośredniczące propagacji nagłówka
Propagacja nagłówka to oprogramowanie pośredniczące obsługiwane przez społeczność, które propaguje nagłówki HTTP z żądania przychodzącego do wychodzących żądań klienta HTTP. Aby użyć propagacji nagłówka:
Odwoływanie się do obsługiwanego przez społeczność portu headerPropagation pakietu. program ASP.NET Core 3.1 lub nowszy obsługuje wersję Microsoft.AspNetCore.HeaderPropagation.
Skonfiguruj oprogramowanie pośredniczące i
HttpClient
w programieStartup
: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(...);