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