Stellen von HTTP-Anforderungen mithilfe von IHttpClientFactory in ASP.NET Core
Hinweis
Dies ist nicht die neueste Version dieses Artikels. Die aktuelle Version finden Sie in der .NET 9-Version dieses Artikels.
Warnung
Diese Version von ASP.NET Core wird nicht mehr unterstützt. Weitere Informationen finden Sie in der Supportrichtlinie für .NET und .NET Core. Informationen zum aktuellen Release finden Sie in der .NET 8-Version dieses Artikels.
Wichtig
Diese Informationen beziehen sich auf ein Vorabversionsprodukt, das vor der kommerziellen Freigabe möglicherweise noch wesentlichen Änderungen unterliegt. Microsoft gibt keine Garantie, weder ausdrücklich noch impliziert, hinsichtlich der hier bereitgestellten Informationen.
Die aktuelle Version finden Sie in der .NET 9-Version dieses Artikels.
Von Kirk Larkin, Steve Gordon, Glenn Condron und Ryan Nowak.
IHttpClientFactory kann registriert und zum Konfigurieren und Erstellen von HttpClient-Instanzen in einer App verwendet werden. IHttpClientFactory
bietet die folgenden Vorteile:
- Ein zentraler Ort für das Benennen und Konfigurieren logischer
HttpClient
-Instanzen wird damit geboten. Beispielsweise kann ein Client namens github registriert und für den Zugriff auf GitHub konfiguriert werden. Ein Standardclient kann für den allgemeinen Zugriff registriert werden. - Das Konzept der ausgehenden Middleware wird über delegierende Handler in
HttpClient
in Code umgesetzt. Außerdem werden Erweiterungen für auf Polly basierende Middleware bereitgestellt, um die delegierenden Handler inHttpClient
zu nutzen. - Das Pooling und die Lebensdauer von zugrunde liegenden
HttpClientMessageHandler
-Instanzen werden verwaltet. Durch die automatische Verwaltung werden allgemeine DNS-Probleme (Domain Name System) vermieden, die bei der manuellen Verwaltung der Lebensdauer vonHttpClient
auftreten. - Eine konfigurierbare Protokollierungsfunktion wird (über
ILogger
) für alle Anforderungen hinzugefügt, die über Clients gesendet werden, die von der Factory erstellt wurden.
Der Beispielcode in dieser Version des Themas verwendet System.Text.Json, um JSON-Inhalte zu deserialisieren, die in HTTP-Antworten zurückgegeben wurden. Verwenden Sie bei Beispielen, die Json.NET
und ReadAsAsync<T>
verwenden, die Versionsauswahl, um eine 2.x-Version dieses Themas auszuwählen.
Verbrauchsmuster
Es gibt mehrere Möglichkeiten IHttpClientFactory
in einer App zu verwenden:
Der beste Ansatz richtet sich nach den Anforderungen der App.
Grundlegende Verwendung
Registrieren Sie IHttpClientFactory
, indem Sie AddHttpClient
in Program.cs
aufrufen:
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddHttpClient();
Eine IHttpClientFactory
-Schnittstelle kann mithilfe der Dependency Injection (DI) angefordert werden. Im folgenden Code wird IHttpClientFactory
verwendet, um eine HttpClient
-Instanz zu erstellen:
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);
}
}
}
IHttpClientFactory
wie im vorhergehenden Beispiel zu verwenden ist eine gute Möglichkeit zum Umgestalten einer vorhandene App. Dies hat keine Auswirkung auf die Verwendung von HttpClient
. An Stellen, an denen HttpClient
-Instanzen in einer vorhandenen App erstellt werden, können Sie diese Ereignisse mit Aufrufen von CreateClient ersetzen.
Benannte Clients
Benannte Clients sind in folgenden Fällen eine gute Wahl:
- Die App erfordert viele verschiedene Verwendungen von
HttpClient
. - Viele
HttpClient
s verfügen über unterschiedliche Konfigurationen.
Geben Sie die Konfiguration für einen benannten HttpClient
bei dessen Registrierung in Program.cs
an:
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");
});
Im vorangehenden Code wird der Client mit Folgendem konfiguriert:
- der Basisadresse
https://api.github.com/
- zwei Header, die für die Arbeit mit der GitHub-API erforderlich sind
CreateClient
Jedes Mal, wenn CreateClient aufgerufen wird, geschieht Folgendes:
- Eine neue Instanz von
HttpClient
wird erstellt. - Die Konfigurationsaktion wird aufgerufen.
Übergeben Sie für die Erstellung eines benannten Clients dessen Namen an 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);
}
}
}
Im vorangehenden Code muss die Anforderung keinen Hostnamen angeben. Der Code muss nur den Pfad übergeben, da die für den Client konfigurierte Basisadresse verwendet wird.
Typisierte Clients
Typisierte Clients:
- Stellen dieselben Funktionen wie benannte Clients bereit, ohne Zeichenfolgen als Schlüssel verwenden zu müssen.
- Bieten Hilfe durch IntelliSense und Compiler beim Verarbeiten von Clients.
- Stellen einen einzelnen Ort zum Konfigurieren und Interagieren mit einem bestimmten
HttpClient
bereit. Beispielsweise kann ein einzelner typisierter Client für Folgendes verwendet werden:- für einen einzelnen Back-End-Endpunkt
- um die gesamte Logik zu kapseln, die den Endpunkt behandelt
- Funktionieren mit DI und können überall in der App eingefügt werden, wo sie benötigt werden.
Ein typisierter Client akzeptiert einen HttpClient
-Parameter in seinem Konstruktor:
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");
}
Für den Code oben gilt:
- Die Konfiguration wird in den typisierten Client verschoben.
- Die bereitgestellte
HttpClient
-Instanz wird als privates Feld gespeichert.
Es können API-spezifische Methoden erstellt werden, die die HttpClient
-Funktionalität verfügbar machen. Die GetAspNetCoreDocsBranches
-Methode kapselt z. B. Code zum Abrufen von GitHub-Branches mit Dokumentation.
Der folgende Code ruft AddHttpClient in Program.cs
auf, um die typisierte GitHubService
-Clientklasse zu registrieren:
builder.Services.AddHttpClient<GitHubService>();
Der typisierte Client wird mit DI als „vorübergehend“ registriert. Im vorherigen Code registriert AddHttpClient
GitHubService
als vorübergehenden Dienst. Diese Registrierung verwendet eine Factorymethode für folgende Aktionen:
- Erstellen Sie eine Instanz von
HttpClient
: - Erstellen einer
GitHubService
-Instanz, wobei die Instanz vonHttpClient
an ihren Konstrukt übergeben wird
Der typisierte Client kann direkt eingefügt und verwendet werden:
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)
{
// ...
}
}
}
Die Konfiguration kann für einen typisierten Client auch während dessen Registrierung in Program.cs
angegeben werden, anstatt im Konstruktor des typisierten Clients:
builder.Services.AddHttpClient<GitHubService>(httpClient =>
{
httpClient.BaseAddress = new Uri("https://api.github.com/");
// ...
});
Generierte Clients
IHttpClientFactory
kann in Verbindung mit Bibliotheken von Drittanbietern verwendet werden, z. B. Refit. Refit ist eine REST-Bibliothek für .NET. Sie konvertiert REST-APIs in Live-Schnittstellen. Rufen Sie AddRefitClient
auf, um eine dynamische Implementierung einer Schnittstelle zu generieren, die HttpClient
zum Durchführen der externen HTTP-Aufrufe verwendet.
Eine benutzerdefinierte Schnittstelle stellt die externe API dar:
public interface IGitHubClient
{
[Get("/repos/dotnet/AspNetCore.Docs/branches")]
Task<IEnumerable<GitHubBranch>> GetAspNetCoreDocsBranchesAsync();
}
Rufen Sie AddRefitClient
auf, um die dynamische Implementierung zu generieren, und rufen Sie dann ConfigureHttpClient
auf, um den zugrunde liegenden HttpClient
zu konfigurieren:
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");
});
Verwenden Sie DI, um auf die dynamische Implementierung von IGitHubClient
zuzugreifen:
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)
{
// ...
}
}
}
Stellen von POST-, PUT- und DELETE-Anforderungen
In den vorangehenden Beispielen verwenden alle Anforderungen das GET-HTTP-Verb. HttpClient
unterstützt ebenso andere HTTP-Verben, einschließlich der folgenden:
- POST
- PUT
- DELETE
- PATCH
Eine komplette Liste der unterstützten HTTP-Verben finden Sie unter HttpMethod.
Im folgenden Beispiel wird gezeigt, wie Sie eine HTTP-POST-Anforderung stellen:
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();
}
Für die CreateItemAsync
-Methoden im Code oben gilt Folgendes:
- Sie serialisiert den
TodoItem
-Parameter mithilfe vonSystem.Text.Json
in JSON. - Sie erstellt eine Instanz von StringContent, um den serialisierten JSON-Code zum Senden im HTTP-Anforderungstext zu packen.
- Die Methode ruft PostAsync auf, um den JSON-Inhalt an die angegebene URL zu senden. Dies ist eine relative URL, die zu HttpClient.BaseAddress hinzugefügt wird.
- Sie ruft EnsureSuccessStatusCode auf, um eine Ausnahme auszulösen, wenn der Antwortstatuscode nicht auf Erfolg hinweist.
HttpClient
unterstützt auch andere Inhaltstypen. Beispiel: MultipartContent und StreamContent. Eine komplette Liste der unterstützten Inhaltstypen finden Sie unter HttpContent.
Das folgende Beispiel zeigt eine HTTP-PUT-Anforderung.
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();
}
Der vorangehende Code ist dem POST-Beispiel ähnlich. Die SaveItemAsync
-Methode ruft PutAsync anstelle von PostAsync
auf.
Das folgende Beispiel zeigt eine HTTP-DELETE-Anforderung.
public async Task DeleteItemAsync(long itemId)
{
using var httpResponseMessage =
await _httpClient.DeleteAsync($"/api/TodoItems/{itemId}");
httpResponseMessage.EnsureSuccessStatusCode();
}
Im vorangehenden Code ruft die DeleteItemAsync
-Methode DeleteAsync auf. Da HTTP-DELETE-Anforderungen normalerweise keinen Text enthalten, stellt die DeleteAsync
-Methode keine Überladung bereit, die eine Instanz von HttpContent
akzeptiert.
Weitere Informationen zur Verwendung unterschiedlicher HTTP-Verben mit HttpClient
finden Sie unter HttpClient.
Middleware für ausgehende Anforderungen
HttpClient
enthält das Konzept, Handler zu delegieren, die für ausgehende HTTP-Anforderungen miteinander verknüpft werden können. IHttpClientFactory
:
- Erleichtert das Definieren der Handler, die für jeden benannten Client angewendet werden
- Unterstützt die Registrierung und Verkettung von mehreren Handlern, um eine Pipeline für die Middleware für ausgehende Anforderungen zu erstellen. Jeder dieser Handler kann vor und nach der ausgehenden Anforderung Aufgaben ausführen. Dieses Muster:
- ähnelt der eingehenden Pipeline für Middleware in ASP.NET Core
- bietet einen Mechanismus zum Verwalten von übergreifenden Belangen bezüglich HTTP-Anforderungen, z. B.:
- Zwischenspeicherung
- Fehlerbehandlung
- Serialisierung
- Protokollierung
So erstellen Sie einen delegierenden Handler:
- Leiten Sie von DelegatingHandler ab.
- Überschreiben Sie SendAsync. Führen Sie Code aus, bevor die Anforderung an den nächsten Handler in der Pipeline übergeben wird:
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);
}
}
Der vorangehende Code überprüft, ob die Anforderung einen X-API-KEY
-Header enthält. Wenn X-API-KEY
fehlt, wird BadRequest zurückgegeben.
Für eine HttpClient
-Klasse mit Microsoft.Extensions.DependencyInjection.HttpClientBuilderExtensions.AddHttpMessageHandler kann mehr als ein Handler zur Konfiguration hinzugefügt werden:
builder.Services.AddTransient<ValidateHeaderHandler>();
builder.Services.AddHttpClient("HttpMessageHandler")
.AddHttpMessageHandler<ValidateHeaderHandler>();
Im vorangehenden Code ist ValidateHeaderHandler
mit DI registriert. Nach der Registrierung kann AddHttpMessageHandler aufgerufen werden, was den Typ für den Handler übergibt.
Mehrere Handler können in der Reihenfolge registriert werden, in der sie ausgeführt werden sollen. Jeder Handler umschließt den nächsten Handler, bis der endgültige HttpClientHandler
die Anforderung ausführt:
builder.Services.AddTransient<SampleHandler1>();
builder.Services.AddTransient<SampleHandler2>();
builder.Services.AddHttpClient("MultipleHttpMessageHandlers")
.AddHttpMessageHandler<SampleHandler1>()
.AddHttpMessageHandler<SampleHandler2>();
Im vorangehenden Code wird SampleHandler1
zuerst ausgeführt, vor SampleHandler2
.
Verwenden von DI in Middleware für ausgehende Anforderungen
Wenn IHttpClientFactory
einen neuen delegierenden Handler erstellt, wird DI verwendet, um die Konstruktorparameter des Handlers zu erfüllen. IHttpClientFactory
erstellt einen separaten DI-Bereich für jeden Handler. Dies kann zu überraschendem Verhalten führen, wenn ein Handler einen bereichsbezogenen Dienst nutzt.
Sehen Sie sich beispielsweise die folgende Schnittstelle und ihre Implementierung an, die eine Aufgabe als Vorgang mit einem Bezeichner OperationId
darstellt:
public interface IOperationScoped
{
string OperationId { get; }
}
public class OperationScoped : IOperationScoped
{
public string OperationId { get; } = Guid.NewGuid().ToString()[^4..];
}
Wie der Name bereits vermuten lässt, wird IOperationScoped
bei DI registriert, wobei eine bereichsbezogene Lebensdauer verwendet wird:
builder.Services.AddScoped<IOperationScoped, OperationScoped>();
Der folgende delegierende Handler verwendet IOperationScoped
, um den X-OPERATION-ID
-Header für die ausgehende Anforderung festzulegen:
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);
}
}
Navigieren Sie im HttpRequestsSample
-Download zu /Operation
, und aktualisieren Sie die Seite. Der Wert für den Anforderungsbereich ändert sich für jede Anforderung, aber der Wert des Handlerbereichs ändert sich nur alle 5 Sekunden.
Handler können von Diensten eines beliebigen Bereichs abhängen. Dienste, von denen Handler abhängig sind, werden verworfen, wenn der Handler verworfen wird.
Verwenden Sie einen der folgenden Ansätze, um den anforderungsspezifischen Zustand mit Meldungshandlern zu teilen:
- Übergeben Sie Daten mit HttpRequestMessage.Options an den Handler.
- Greifen Sie mit IHttpContextAccessor auf die aktuelle Anforderung zu.
- Erstellen Sie ein benutzerdefiniertes AsyncLocal<T>-Speicherobjekt, um die Daten zu übergeben.
Verwenden von Polly-basierten Handlern
IHttpClientFactory
ist mit der Drittanbieterbibliothek Polly integriert. Polly ist eine umfassende Bibliothek für die Behandlung von beständigen und vorübergehenden Fehlern für .NET. Entwicklern wird damit ermöglicht, Richtlinien wie Wiederholungsrichtlinien, Trennschalterrichtlinien, Timeout-Richtlinien, Bulkhead Isolation-Richtlinien und Ausweichrichtlinien in einer flüssigen und threadsicheren Art und Weise auszudrücken.
Erweiterungsmethoden werden bereitgestellt, um die Verwendung von Polly-Richtlinien mit konfigurierten HttpClient
-Instanzen zu aktivieren. Die Polly-Erweiterungen unterstützen das Hinzufügen von Polly-basierten Handlern zu Clients. Polly erfordert das Microsoft.Extensions.Http.Polly-NuGet-Paket.
Behandeln von vorrübergehenden Fehlern
Fehler treten normalerweise auf, wenn externe HTTP-Aufrufe vorübergehend sind. AddTransientHttpErrorPolicy ermöglicht die Definition einer Richtlinie, um vorübergehende Fehler zu behandeln. Mit AddTransientHttpErrorPolicy
konfigurierte Richtlinien behandeln die folgenden Antworten:
- HttpRequestException
- HTTP 5xx
- HTTP 408
AddTransientHttpErrorPolicy
ermöglicht den Zugriff auf ein PolicyBuilder
-Objekt, das für die Fehlerbehandlung konfiguriert wurde und mögliche vorübergehende Fehler darstellt:
builder.Services.AddHttpClient("PollyWaitAndRetry")
.AddTransientHttpErrorPolicy(policyBuilder =>
policyBuilder.WaitAndRetryAsync(
3, retryNumber => TimeSpan.FromMilliseconds(600)));
Im vorangehenden Code wird eine WaitAndRetryAsync
-Richtlinie definiert. Anforderungsfehler werden bis zu dreimal mit einer Verzögerung von 600 ms wiederholt.
Dynamisches Auswählen von Richtlinien
Erweiterungsmethoden werden zum Hinzufügen von Polly-basierten Handlern bereitgestellt, z. B. AddPolicyHandler. Der folgende AddPolicyHandler
-Überladung prüft die Anforderung, um zu entscheiden, welche Richtlinie angewendet werden soll:
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);
Im vorangehenden Code wird ein 10 Sekunden langer Timeout angewendet, wenn die ausgehende Anforderung eine HTTP GET-Anforderung ist. Für alle anderen HTTP-Methoden wird ein 30 Sekunden langer Timeout verwendet.
Hinzufügen mehrerer Polly-Handler
Es ist üblich, Polly-Richtlinien zu schachteln:
builder.Services.AddHttpClient("PollyMultiple")
.AddTransientHttpErrorPolicy(policyBuilder =>
policyBuilder.RetryAsync(3))
.AddTransientHttpErrorPolicy(policyBuilder =>
policyBuilder.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));
Im vorherigen Beispiel:
- Zwei Handler werden hinzugefügt.
- Der erste Handler verwendet AddTransientHttpErrorPolicy, um eine Wiederholungsrichtlinie hinzuzufügen. Fehlgeschlagene Anforderungen werden bis zu drei Mal wiederholt.
- Der zweite
AddTransientHttpErrorPolicy
-Aufruf fügt eine Trennschalterrichtlinie hinzu. Weitere externe Anforderungen werden 30 Sekunden lang blockiert, wenn fünf Fehlversuche hintereinander stattfinden. Trennschalterrichtlinien sind zustandsbehaftet. Alle Aufrufe über diesen Client teilen den gleichen Schalterzustand.
Hinzufügen von Richtlinien aus der Polly-Registrierung
Eine Methode zum Verwalten regelmäßig genutzter Richtlinien ist, Sie einmal zu definieren und mit PolicyRegistry
zu registrieren. Beispiel:
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");
Für den Code oben gilt:
- Der Polly-Registrierung werden zwei Richtlinien hinzugefügt:
Regular
undLong
. - AddPolicyHandlerFromRegistry konfiguriert einzelne benannte Clients für die Verwendung dieser Richtlinien aus der Polly-Registrierung.
Weitere Informationen zu IHttpClientFactory
und Polly-Integrationen finden Sie im Polly-Wiki.
HttpClient und die Verwaltung der Lebensdauer
Bei jedem Aufruf von CreateClient
in der IHttpClientFactory
wird eine neue Instanz von HttpClient
zurückgegeben. Pro benannter Client wird ein HttpMessageHandler erstellt. Die Factory verwaltet die Lebensdauer der HttpMessageHandler
-Instanzen.
IHttpClientFactory
legt die HttpMessageHandler
-Instanzen zusammen, die von der Factory zum Reduzieren des Ressourcenverbrauchs erstellt wurden. Eine HttpMessageHandler
-Instanz kann aus dem Pool wiederverwendet werden, wenn eine neue HttpClient
-Instanz erstellt wird und deren Lebensdauer noch nicht abgelaufen ist.
Das Zusammenlegen von Handlern ist ein wünschenswerter Vorgang, da jeder Handler in der Regel seine zugrunde liegenden HTTP-Verbindungen selbst verwaltet. Wenn mehr Handler als nötig erstellt werden, können Verzögerungen bei Verbindungen entstehen. Einige Handler halten Verbindungen auch unbegrenzt offen, was verhindert, dass der Handler auf DNS-Änderungen (Domain Name System) reagiert.
Die Standardlebensdauer von Handlern beträgt zwei Minuten. Der Standardwert kann für jeden benannten Client überschrieben werden:
builder.Services.AddHttpClient("HandlerLifetime")
.SetHandlerLifetime(TimeSpan.FromMinutes(5));
HttpClient
-Instanzen können im Allgemeinen als .NET-Objekte behandelt werden, die nicht verworfen werden müssen. Beim Verwerfen werden ausgehende Anforderungen abgebrochen, und es wird sichergestellt, dass die angegebene HttpClient
-Instanz nach dem Aufruf von Dispose nicht mehr verwendet werden kann. IHttpClientFactory
verfolgt von HttpClient
-Instanzen verwendete Ressourcen nach und verwirft sie.
Das Beibehalten einer einzelnen HttpClient
-Instanz für einen langen Zeitraum ist ein allgemeines Muster, das vor der Einführung von IHttpClientFactory
verwendet wurde. Dieses Muster wird nach der Migration zu IHttpClientFactory
überflüssig.
Alternativen zu IHttpClientFactory
Mit der Verwendung von IHttpClientFactory
in einer DI-fähigen App wird Folgendes vermieden:
- Probleme mit der Ressourcenauslastung durch Zusammenlegen von
HttpMessageHandler
-Instanzen - Probleme durch veraltetes DNS durch regelmäßigen Wechsel von
HttpMessageHandler
-Instanzen
Es gibt alternative Möglichkeiten zum Lösen des vorangehenden Problems mithilfe einer langlebigen SocketsHttpHandler-Instanz:
- Erstellen Sie eine
SocketsHttpHandler
-Instanz, wenn die App gestartet wird, und verwenden Sie diese für die Lebensdauer der App. - Konfigurieren Sie einen geeigneten Wert für PooledConnectionLifetime basierend auf den DNS-Aktualisierungszeiten.
- Erstellen Sie bei Bedarf
HttpClient
-Instanzen mithilfe vonnew HttpClient(handler, disposeHandler: false)
.
Diese Ansätze lösen die Ressourcenverwaltungsprobleme, die IHttpClientFactory
auf ähnliche Weise löst.
SocketsHttpHandler
gibt Verbindungen fürHttpClient
-Instanzen frei. Durch diese Freigabe wird die Erschöpfung von Sockets vermieden.SocketsHttpHandler
wechselt die Verbindungen anhand vonPooledConnectionLifetime
, um Probleme durch veraltetes DNS zu umgehen.
Protokollierung
Über IHttpClientFactory
erstellte Clients zeichnen Protokollmeldungen für alle Anforderungen auf. Aktivieren Sie die entsprechende Informationsebene in der Protokollierungskonfiguration, um die Standardprotokollmeldungen anzuzeigen. Zusätzliche Protokollierung, z.B. das Protokollieren von Anforderungsheadern, wird nur auf der Ablaufverfolgungsebene enthalten.
Die Protokollierungskategorie für jeden Client enthält den Namen des Clients. Ein Client namens MyNamedClient protokolliert z. B. Nachrichten mit der Kategorie „System.Net.Http.HttpClient.MyNamedClient.LogicalHandler“. Meldungen mit dem Suffix LogicalHandler treten außerhalb der Anforderungshandlerpipeline auf. In der Anforderung werden Meldungen protokolliert, bevor andere Handler in der Pipeline sie verarbeitet haben. In der Antwort werden Meldungen protokolliert, nachdem andere Handler in der Pipeline die Antwort empfangen haben.
Die Protokollierung tritt ebenfalls innerhalb der Anforderungshandlerpipeline auf. Im Beispiel von MyNamedClient werden diese Nachrichten mit der Protokollkategorie „System.Net.Http.HttpClient.MyNamedClient.ClientHandler“ protokolliert. Für die Anforderung erfolgt dies, nachdem alle anderen Handler ausgeführt wurden und bevor die Anforderung gesendet wird. Diese Protokollierung enthält den Zustand der Antwort in der Antwort, bevor sie an die Handlerpipeline zurückgegeben wird.
Das Aktivieren der Protokollierung außerhalb und innerhalb der Pipeline ermöglicht die Überprüfung der Änderungen, die durch andere Handler in der Pipeline erfolgt sind. Dies kann die Änderungen an Anforderungsheadern oder am Statuscode der Antwort enthalten.
Das Einschließen des Namens des Clients in der Protokollkategorie aktiviert die Protokollfilterung für spezifische benannte Clients.
Konfigurieren von HttpMessageHandler
Es kann notwendig sein, die Konfiguration des inneren von einem Client verwendeten HttpMessageHandler
zu steuern.
IHttpClientBuilder
wird zurückgegeben, wenn benannte oder typisierte Clients hinzugefügt werden. Die Erweiterungsmethode ConfigurePrimaryHttpMessageHandler kann zum Definieren eines Delegaten verwendet werden. Der Delegat wird verwendet, um den primären HttpMessageHandler
zu erstellen und konfigurieren, der von dem Client verwendet wird:
builder.Services.AddHttpClient("ConfiguredHttpMessageHandler")
.ConfigurePrimaryHttpMessageHandler(() =>
new HttpClientHandler
{
AllowAutoRedirect = true,
UseDefaultCredentials = true
});
Cookies
Die zusammengelegten HttpMessageHandler
-Instanzen resultieren in der Freigabe von CookieContainer
-Objekten. Die unerwartete Freigabe von CookieContainer
-Objekten führt oft zu fehlerhaftem Code. Ziehen Sie für Apps, die Cookies erfordern, folgende Vorgehensweisen in Betracht:
- Deaktivieren der automatischen cookieverarbeitung
- Vermeiden der Verwendung von
IHttpClientFactory
Rufen Sie ConfigurePrimaryHttpMessageHandler auf, um die automatische cookieverarbeitung zu deaktivieren:
builder.Services.AddHttpClient("NoAutomaticCookies")
.ConfigurePrimaryHttpMessageHandler(() =>
new HttpClientHandler
{
UseCookies = false
});
Verwenden von IHttpClientFactory in einer Konsolen-App
Fügen Sie in einer Konsolen-App dem Projekt die folgenden Paketverweise hinzu:
Im folgenden Beispiel:
- IHttpClientFactory und
GitHubService
sind im Dienstcontainer des generischen Hosts registriert. GitHubService
wird über DI angefordert, die wiederum eine Instanz vonIHttpClientFactory
anfordert.GitHubService
erstellt überIHttpClientFactory
eine Instanz vonHttpClient
, die verwendet wird, um GitHub-Branches mit Dokumentation abzurufen.
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);
Middleware für Headerweitergabe
Für die Headerweitergabe wird eine ASP.NET Core-Middleware verwendet, um HTTP-Header von eingehenden Anforderungen an ausgehende HttpClient
-Anforderungen weiterzugeben. So verwenden Sie die Headerweitergabe:
Installieren Sie das Paket Microsoft.AspNetCore.HeaderPropagation.
Konfigurieren Sie den
HttpClient
und die Middlewarepipeline inProgram.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();
Erstellen Sie ausgehende Anforderungen mithilfe der konfigurierten
HttpClient
-Instanz, die die hinzugefügten Header enthält.
Zusätzliche Ressourcen
- Anzeigen oder Herunterladen von Beispielcode (Vorgehensweise zum Herunterladen)
- Verwenden von HttpClientFactory zur Implementierung robuster HTTP-Anforderungen
- Implementieren von Wiederholungen von HTTP-Aufrufen mit exponentiellem Backoff mit HttpClientFactory und Polly-Richtlinien
- Implementieren des Trennschaltermusters
- Serialisieren und Deserialisieren von JSON-Daten in .NET
Von Kirk Larkin, Steve Gordon, Glenn Condron und Ryan Nowak.
IHttpClientFactory kann registriert und zum Konfigurieren und Erstellen von HttpClient-Instanzen in einer App verwendet werden. IHttpClientFactory
bietet die folgenden Vorteile:
- Ein zentraler Ort für das Benennen und Konfigurieren logischer
HttpClient
-Instanzen wird damit geboten. Beispielsweise kann ein Client namens github registriert und für den Zugriff auf GitHub konfiguriert werden. Ein Standardclient kann für den allgemeinen Zugriff registriert werden. - Das Konzept der ausgehenden Middleware wird über delegierende Handler in
HttpClient
in Code umgesetzt. Außerdem werden Erweiterungen für auf Polly basierende Middleware bereitgestellt, um die delegierenden Handler inHttpClient
zu nutzen. - Das Pooling und die Lebensdauer von zugrunde liegenden
HttpClientMessageHandler
-Instanzen werden verwaltet. Durch die automatische Verwaltung werden allgemeine DNS-Probleme (Domain Name System) vermieden, die bei der manuellen Verwaltung der Lebensdauer vonHttpClient
auftreten. - Eine konfigurierbare Protokollierungsfunktion wird (über
ILogger
) für alle Anforderungen hinzugefügt, die über Clients gesendet werden, die von der Factory erstellt wurden.
Zeigen Sie Beispielcode an, oder laden Sie diesen herunter (Vorgehensweise zum Herunterladen).
Der Beispielcode in dieser Version des Themas verwendet System.Text.Json, um JSON-Inhalte zu deserialisieren, die in HTTP-Antworten zurückgegeben wurden. Verwenden Sie bei Beispielen, die Json.NET
und ReadAsAsync<T>
verwenden, die Versionsauswahl, um eine 2.x-Version dieses Themas auszuwählen.
Verbrauchsmuster
Es gibt mehrere Möglichkeiten IHttpClientFactory
in einer App zu verwenden:
Der beste Ansatz richtet sich nach den Anforderungen der App.
Grundlegende Verwendung
IHttpClientFactory
kann durch Aufrufen von AddHttpClient
registriert werden:
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.
Eine IHttpClientFactory
-Schnittstelle kann mithilfe der Dependency Injection (DI) angefordert werden. Im folgenden Code wird IHttpClientFactory
verwendet, um eine HttpClient
-Instanz zu erstellen:
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>();
}
}
}
IHttpClientFactory
wie im vorhergehenden Beispiel zu verwenden ist eine gute Möglichkeit zum Umgestalten einer vorhandene App. Dies hat keine Auswirkung auf die Verwendung von HttpClient
. An Stellen, an denen HttpClient
-Instanzen in einer vorhandenen App erstellt werden, können Sie diese Ereignisse mit Aufrufen von CreateClient ersetzen.
Benannte Clients
Benannte Clients sind in folgenden Fällen eine gute Wahl:
- Die App erfordert viele verschiedene Verwendungen von
HttpClient
. - Viele
HttpClient
s verfügen über unterschiedliche Konfigurationen.
Die Konfiguration eines benannten HttpClient
kann während der Registrierung in Startup.ConfigureServices
angegeben werden:
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");
});
Im vorangehenden Code wird der Client mit Folgendem konfiguriert:
- der Basisadresse
https://api.github.com/
- zwei Header, die für die Arbeit mit der GitHub-API erforderlich sind
CreateClient
Jedes Mal, wenn CreateClient aufgerufen wird, geschieht Folgendes:
- Eine neue Instanz von
HttpClient
wird erstellt. - Die Konfigurationsaktion wird aufgerufen.
Übergeben Sie für die Erstellung eines benannten Clients dessen Namen an 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>();
}
}
}
Im vorangehenden Code muss die Anforderung keinen Hostnamen angeben. Der Code muss nur den Pfad übergeben, da die für den Client konfigurierte Basisadresse verwendet wird.
Typisierte Clients
Typisierte Clients:
- Stellen dieselben Funktionen wie benannte Clients bereit, ohne Zeichenfolgen als Schlüssel verwenden zu müssen.
- Bieten Hilfe durch IntelliSense und Compiler beim Verarbeiten von Clients.
- Stellen einen einzelnen Ort zum Konfigurieren und Interagieren mit einem bestimmten
HttpClient
bereit. Beispielsweise kann ein einzelner typisierter Client für Folgendes verwendet werden:- für einen einzelnen Back-End-Endpunkt
- um die gesamte Logik zu kapseln, die den Endpunkt behandelt
- Funktionieren mit DI und können überall in der App eingefügt werden, wo sie benötigt werden.
Ein typisierter Client akzeptiert einen HttpClient
-Parameter in seinem Konstruktor:
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");
}
}
Für den Code oben gilt:
- Die Konfiguration wird in den typisierten Client verschoben.
- Das
HttpClient
-Objekt wird als öffentliche Eigenschaft zur Verfügung gestellt.
Es können API-spezifische Methoden erstellt werden, die die HttpClient
-Funktionalität verfügbar machen. Die GetAspNetDocsIssues
-Methode kapselt z. B. Code zum Abrufen offener Probleme.
Der folgende Code ruft AddHttpClient in Startup.ConfigureServices
auf, um eine typisierte Clientklasse zu registrieren:
services.AddHttpClient<GitHubService>();
Der typisierte Client wird mit DI als „vorübergehend“ registriert. Im vorherigen Code registriert AddHttpClient
GitHubService
als vorübergehenden Dienst. Diese Registrierung verwendet eine Factorymethode für folgende Aktionen:
- Erstellen Sie eine Instanz von
HttpClient
: - Erstellen einer
GitHubService
-Instanz, wobei die Instanz vonHttpClient
an ihren Konstrukt übergeben wird
Der typisierte Client kann direkt eingefügt und verwendet werden:
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>();
}
}
}
Die Konfiguration kann für einen typisierten Client während der Registrierung in Startup.ConfigureServices
angegeben werden, anstatt im Konstruktor des typisierten Clients:
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");
});
HttpClient
kann in einem typisierten Client gekapselt werden. Anstatt ihn als eine Eigenschaft zur Verfügung zu stellen, definieren Sie eine Methode, die die HttpClient
-Instanz intern aufruft:
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);
}
}
Im vorangehenden Code wird HttpClient
in einem privaten Feld gespeichert. Der Zugriff auf HttpClient
erfolgt durch die öffentliche GetRepos
-Methode.
Generierte Clients
IHttpClientFactory
kann in Verbindung mit Bibliotheken von Drittanbietern verwendet werden, z. B. Refit. Refit ist eine REST-Bibliothek für .NET. Sie konvertiert REST-APIs in Live-Schnittstellen. Eine Implementierung der Schnittstelle wird dynamisch von RestService
generiert. HttpClient
wird für die externen HTTP-Aufrufe verwendet.
Eine Schnittstelle und eine Antwort werden definiert, um die externe API und die Antwort darzustellen:
public interface IHelloClient
{
[Get("/helloworld")]
Task<Reply> GetMessageAsync();
}
public class Reply
{
public string Message { get; set; }
}
Ein typisierter Client kann hinzugefügt werden. Refit wird zum Generieren der Implementierung verwendet:
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();
}
Die definierte Schnittstelle kann bei Bedarf mit der von DI und Refit bereitgestellten Implementierung verwendet werden:
[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();
}
}
Stellen von POST-, PUT- und DELETE-Anforderungen
In den vorangehenden Beispielen verwenden alle Anforderungen das GET-HTTP-Verb. HttpClient
unterstützt ebenso andere HTTP-Verben, einschließlich der folgenden:
- POST
- PUT
- DELETE
- PATCH
Eine komplette Liste der unterstützten HTTP-Verben finden Sie unter HttpMethod.
Im folgenden Beispiel wird gezeigt, wie Sie eine HTTP-POST-Anforderung stellen:
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();
}
Für die CreateItemAsync
-Methoden im Code oben gilt Folgendes:
- Sie serialisiert den
TodoItem
-Parameter mithilfe vonSystem.Text.Json
in JSON. Dabei wird eine Instanz von JsonSerializerOptions verwenden, um den Serialisierungsprozess zu konfigurieren. - Sie erstellt eine Instanz von StringContent, um den serialisierten JSON-Code zum Senden im HTTP-Anforderungstext zu packen.
- Die Methode ruft PostAsync auf, um den JSON-Inhalt an die angegebene URL zu senden. Dies ist eine relative URL, die zu HttpClient.BaseAddress hinzugefügt wird.
- Sie ruft EnsureSuccessStatusCode auf, um eine Ausnahme auszulösen, wenn der Antwortstatuscode nicht auf Erfolg hinweist.
HttpClient
unterstützt auch andere Inhaltstypen. Beispiel: MultipartContent und StreamContent. Eine komplette Liste der unterstützten Inhaltstypen finden Sie unter HttpContent.
Das folgende Beispiel zeigt eine HTTP-PUT-Anforderung.
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();
}
Der vorangehende Code ist dem POST-Beispiel sehr ähnlich. Die SaveItemAsync
-Methode ruft PutAsync anstelle von PostAsync
auf.
Das folgende Beispiel zeigt eine HTTP-DELETE-Anforderung.
public async Task DeleteItemAsync(long itemId)
{
using var httpResponse =
await _httpClient.DeleteAsync($"/api/TodoItems/{itemId}");
httpResponse.EnsureSuccessStatusCode();
}
Im vorangehenden Code ruft die DeleteItemAsync
-Methode DeleteAsync auf. Da HTTP-DELETE-Anforderungen normalerweise keinen Text enthalten, stellt die DeleteAsync
-Methode keine Überladung bereit, die eine Instanz von HttpContent
akzeptiert.
Weitere Informationen zur Verwendung unterschiedlicher HTTP-Verben mit HttpClient
finden Sie unter HttpClient.
Middleware für ausgehende Anforderungen
HttpClient
enthält das Konzept, Handler zu delegieren, die für ausgehende HTTP-Anforderungen miteinander verknüpft werden können. IHttpClientFactory
:
- Erleichtert das Definieren der Handler, die für jeden benannten Client angewendet werden
- Unterstützt die Registrierung und Verkettung von mehreren Handlern, um eine Pipeline für die Middleware für ausgehende Anforderungen zu erstellen. Jeder dieser Handler kann vor und nach der ausgehenden Anforderung Aufgaben ausführen. Dieses Muster:
- ähnelt der eingehenden Pipeline für Middleware in ASP.NET Core
- bietet einen Mechanismus zum Verwalten von übergreifenden Belangen bezüglich HTTP-Anforderungen, z. B.:
- Zwischenspeicherung
- Fehlerbehandlung
- Serialisierung
- Protokollierung
So erstellen Sie einen delegierenden Handler:
- Leiten Sie von DelegatingHandler ab.
- Überschreiben Sie SendAsync. Führen Sie Code aus, bevor die Anforderung an den nächsten Handler in der Pipeline übergeben wird:
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);
}
}
Der vorangehende Code überprüft, ob die Anforderung einen X-API-KEY
-Header enthält. Wenn X-API-KEY
fehlt, wird BadRequest zurückgegeben.
Für eine HttpClient
-Klasse mit Microsoft.Extensions.DependencyInjection.HttpClientBuilderExtensions.AddHttpMessageHandler kann mehr als ein Handler zur Konfiguration hinzugefügt werden:
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.
Im vorangehenden Code ist ValidateHeaderHandler
mit DI registriert. Nach der Registrierung kann AddHttpMessageHandler aufgerufen werden, was den Typ für den Handler übergibt.
Mehrere Handler können in der Reihenfolge registriert werden, in der sie ausgeführt werden sollen. Jeder Handler umschließt den nächsten Handler, bis der endgültige HttpClientHandler
die Anforderung ausführt:
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>();
Verwenden von DI in Middleware für ausgehende Anforderungen
Wenn IHttpClientFactory
einen neuen delegierenden Handler erstellt, wird DI verwendet, um die Konstruktorparameter des Handlers zu erfüllen. IHttpClientFactory
erstellt einen separaten DI-Bereich für jeden Handler. Dies kann zu überraschendem Verhalten führen, wenn ein Handler einen bereichsbezogenen Dienst nutzt.
Sehen Sie sich beispielsweise die folgende Schnittstelle und ihre Implementierung an, die eine Aufgabe als Vorgang mit einem Bezeichner OperationId
darstellt:
public interface IOperationScoped
{
string OperationId { get; }
}
public class OperationScoped : IOperationScoped
{
public string OperationId { get; } = Guid.NewGuid().ToString()[^4..];
}
Wie der Name bereits vermuten lässt, wird IOperationScoped
bei DI registriert, wobei eine bereichsbezogene Lebensdauer verwendet wird:
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();
}
Der folgende delegierende Handler verwendet IOperationScoped
, um den X-OPERATION-ID
-Header für die ausgehende Anforderung festzulegen:
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);
}
}
Navigieren Sie im HttpRequestsSample
-Download] zu /Operation
, und aktualisieren Sie die Seite. Der Wert für den Anforderungsbereich ändert sich für jede Anforderung, aber der Wert des Handlerbereichs ändert sich nur alle 5 Sekunden.
Handler können von Diensten eines beliebigen Bereichs abhängen. Dienste, von denen Handler abhängig sind, werden verworfen, wenn der Handler verworfen wird.
Verwenden Sie einen der folgenden Ansätze, um den anforderungsspezifischen Zustand mit Meldungshandlern zu teilen:
- Übergeben Sie Daten mit HttpRequestMessage.Options an den Handler.
- Greifen Sie mit IHttpContextAccessor auf die aktuelle Anforderung zu.
- Erstellen Sie ein benutzerdefiniertes AsyncLocal<T>-Speicherobjekt, um die Daten zu übergeben.
Verwenden von Polly-basierten Handlern
IHttpClientFactory
ist mit der Drittanbieterbibliothek Polly integriert. Polly ist eine umfassende Bibliothek für die Behandlung von beständigen und vorübergehenden Fehlern für .NET. Entwicklern wird damit ermöglicht, Richtlinien wie Wiederholungsrichtlinien, Trennschalterrichtlinien, Timeout-Richtlinien, Bulkhead Isolation-Richtlinien und Ausweichrichtlinien in einer flüssigen und threadsicheren Art und Weise auszudrücken.
Erweiterungsmethoden werden bereitgestellt, um die Verwendung von Polly-Richtlinien mit konfigurierten HttpClient
-Instanzen zu aktivieren. Die Polly-Erweiterungen unterstützen das Hinzufügen von Polly-basierten Handlern zu Clients. Polly erfordert das Microsoft.Extensions.Http.Polly-NuGet-Paket.
Behandeln von vorrübergehenden Fehlern
Fehler treten normalerweise auf, wenn externe HTTP-Aufrufe vorübergehend sind. AddTransientHttpErrorPolicy ermöglicht die Definition einer Richtlinie, um vorübergehende Fehler zu behandeln. Mit AddTransientHttpErrorPolicy
konfigurierte Richtlinien behandeln die folgenden Antworten:
- HttpRequestException
- HTTP 5xx
- HTTP 408
AddTransientHttpErrorPolicy
ermöglicht den Zugriff auf ein PolicyBuilder
-Objekt, das für die Fehlerbehandlung konfiguriert wurde und mögliche vorübergehende Fehler darstellt:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient<UnreliableEndpointCallerService>()
.AddTransientHttpErrorPolicy(p =>
p.WaitAndRetryAsync(3, _ => TimeSpan.FromMilliseconds(600)));
// Remaining code deleted for brevity.
Im vorangehenden Code wird eine WaitAndRetryAsync
-Richtlinie definiert. Anforderungsfehler werden bis zu dreimal mit einer Verzögerung von 600 ms wiederholt.
Dynamisches Auswählen von Richtlinien
Erweiterungsmethoden werden zum Hinzufügen von Polly-basierten Handlern bereitgestellt, z. B. AddPolicyHandler. Der folgende AddPolicyHandler
-Überladung prüft die Anforderung, um zu entscheiden, welche Richtlinie angewendet werden soll:
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);
Im vorangehenden Code wird ein 10 Sekunden langer Timeout angewendet, wenn die ausgehende Anforderung eine HTTP GET-Anforderung ist. Für alle anderen HTTP-Methoden wird ein 30 Sekunden langer Timeout verwendet.
Hinzufügen mehrerer Polly-Handler
Es ist üblich, Polly-Richtlinien zu schachteln:
services.AddHttpClient("multiplepolicies")
.AddTransientHttpErrorPolicy(p => p.RetryAsync(3))
.AddTransientHttpErrorPolicy(
p => p.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));
Im vorherigen Beispiel:
- Zwei Handler werden hinzugefügt.
- Der erste Handler verwendet AddTransientHttpErrorPolicy, um eine Wiederholungsrichtlinie hinzuzufügen. Fehlgeschlagene Anforderungen werden bis zu drei Mal wiederholt.
- Der zweite
AddTransientHttpErrorPolicy
-Aufruf fügt eine Trennschalterrichtlinie hinzu. Weitere externe Anforderungen werden 30 Sekunden lang blockiert, wenn fünf Fehlversuche hintereinander stattfinden. Trennschalterrichtlinien sind zustandsbehaftet. Alle Aufrufe über diesen Client teilen den gleichen Schalterzustand.
Hinzufügen von Richtlinien aus der Polly-Registrierung
Eine Methode zum Verwalten regelmäßig genutzter Richtlinien ist, Sie einmal zu definieren und mit PolicyRegistry
zu registrieren.
Im folgenden Code wird Folgendes ausgeführt:
- Die Richtlinien „regular“ und „long“ werden hinzugefügt.
- AddPolicyHandlerFromRegistry fügt die Richtlinien „regular“ und „long“ aus der Registrierung hinzu.
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.
Weitere Informationen zu IHttpClientFactory
und Polly-Integrationen finden Sie im Polly-Wiki.
HttpClient und die Verwaltung der Lebensdauer
Bei jedem Aufruf von CreateClient
in der IHttpClientFactory
wird eine neue Instanz von HttpClient
zurückgegeben. Pro benannter Client wird ein HttpMessageHandler erstellt. Die Factory verwaltet die Lebensdauer der HttpMessageHandler
-Instanzen.
IHttpClientFactory
legt die HttpMessageHandler
-Instanzen zusammen, die von der Factory zum Reduzieren des Ressourcenverbrauchs erstellt wurden. Eine HttpMessageHandler
-Instanz kann aus dem Pool wiederverwendet werden, wenn eine neue HttpClient
-Instanz erstellt wird und deren Lebensdauer noch nicht abgelaufen ist.
Das Zusammenlegen von Handlern ist ein wünschenswerter Vorgang, da jeder Handler in der Regel seine zugrunde liegenden HTTP-Verbindungen selbst verwaltet. Wenn mehr Handler als nötig erstellt werden, können Verzögerungen bei Verbindungen entstehen. Einige Handler halten Verbindungen auch unbegrenzt offen, was verhindert, dass der Handler auf DNS-Änderungen (Domain Name System) reagiert.
Die Standardlebensdauer von Handlern beträgt zwei Minuten. Der Standardwert kann für jeden benannten Client überschrieben werden:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient("extendedhandlerlifetime")
.SetHandlerLifetime(TimeSpan.FromMinutes(5));
// Remaining code deleted for brevity.
HttpClient
-Instanzen können im Allgemeinen als .NET-Objekte behandelt werden, die nicht verworfen werden müssen. Beim Verwerfen werden ausgehende Anforderungen abgebrochen, und es wird sichergestellt, dass die angegebene HttpClient
-Instanz nach dem Aufruf von Dispose nicht mehr verwendet werden kann. IHttpClientFactory
verfolgt von HttpClient
-Instanzen verwendete Ressourcen nach und verwirft sie.
Das Beibehalten einer einzelnen HttpClient
-Instanz für einen langen Zeitraum ist ein allgemeines Muster, das vor der Einführung von IHttpClientFactory
verwendet wurde. Dieses Muster wird nach der Migration zu IHttpClientFactory
überflüssig.
Alternativen zu IHttpClientFactory
Mit der Verwendung von IHttpClientFactory
in einer DI-fähigen App wird Folgendes vermieden:
- Probleme mit der Ressourcenauslastung durch Zusammenlegen von
HttpMessageHandler
-Instanzen - Probleme durch veraltetes DNS durch regelmäßigen Wechsel von
HttpMessageHandler
-Instanzen
Es gibt alternative Möglichkeiten zum Lösen des vorangehenden Problems mithilfe einer langlebigen SocketsHttpHandler-Instanz:
- Erstellen Sie eine
SocketsHttpHandler
-Instanz, wenn die App gestartet wird, und verwenden Sie diese für die Lebensdauer der App. - Konfigurieren Sie einen geeigneten Wert für PooledConnectionLifetime basierend auf den DNS-Aktualisierungszeiten.
- Erstellen Sie bei Bedarf
HttpClient
-Instanzen mithilfe vonnew HttpClient(handler, disposeHandler: false)
.
Diese Ansätze lösen die Ressourcenverwaltungsprobleme, die IHttpClientFactory
auf ähnliche Weise löst.
SocketsHttpHandler
gibt Verbindungen fürHttpClient
-Instanzen frei. Durch diese Freigabe wird die Erschöpfung von Sockets vermieden.SocketsHttpHandler
wechselt die Verbindungen anhand vonPooledConnectionLifetime
, um Probleme durch veraltetes DNS zu umgehen.
Cookies
Die zusammengelegten HttpMessageHandler
-Instanzen resultieren in der Freigabe von CookieContainer
-Objekten. Die unerwartete Freigabe von CookieContainer
-Objekten führt oft zu fehlerhaftem Code. Ziehen Sie für Apps, die Cookies erfordern, folgende Vorgehensweisen in Betracht:
- Deaktivieren der automatischen cookieverarbeitung
- Vermeiden der Verwendung von
IHttpClientFactory
Rufen Sie ConfigurePrimaryHttpMessageHandler auf, um die automatische cookieverarbeitung zu deaktivieren:
services.AddHttpClient("configured-disable-automatic-cookies")
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler()
{
UseCookies = false,
};
});
Protokollierung
Über IHttpClientFactory
erstellte Clients zeichnen Protokollmeldungen für alle Anforderungen auf. Aktivieren Sie die entsprechende Informationsebene in der Protokollierungskonfiguration, um die Standardprotokollmeldungen anzuzeigen. Zusätzliche Protokollierung, z.B. das Protokollieren von Anforderungsheadern, wird nur auf der Ablaufverfolgungsebene enthalten.
Die Protokollierungskategorie für jeden Client enthält den Namen des Clients. Ein Client namens MyNamedClient protokolliert z. B. Nachrichten mit der Kategorie „System.Net.Http.HttpClient.MyNamedClient.LogicalHandler“. Meldungen mit dem Suffix LogicalHandler treten außerhalb der Anforderungshandlerpipeline auf. In der Anforderung werden Meldungen protokolliert, bevor andere Handler in der Pipeline sie verarbeitet haben. In der Antwort werden Meldungen protokolliert, nachdem andere Handler in der Pipeline die Antwort empfangen haben.
Die Protokollierung tritt ebenfalls innerhalb der Anforderungshandlerpipeline auf. Im Beispiel von MyNamedClient werden diese Nachrichten mit der Protokollkategorie „System.Net.Http.HttpClient.MyNamedClient.ClientHandler“ protokolliert. Für die Anforderung erfolgt dies, nachdem alle anderen Handler ausgeführt wurden und bevor die Anforderung gesendet wird. Diese Protokollierung enthält den Zustand der Antwort in der Antwort, bevor sie an die Handlerpipeline zurückgegeben wird.
Das Aktivieren der Protokollierung außerhalb und innerhalb der Pipeline ermöglicht die Überprüfung der Änderungen, die durch andere Handler in der Pipeline erfolgt sind. Dies kann die Änderungen an Anforderungsheadern oder am Statuscode der Antwort enthalten.
Das Einschließen des Namens des Clients in der Protokollkategorie aktiviert die Protokollfilterung für spezifische benannte Clients.
Konfigurieren von HttpMessageHandler
Es kann notwendig sein, die Konfiguration des inneren von einem Client verwendeten HttpMessageHandler
zu steuern.
IHttpClientBuilder
wird zurückgegeben, wenn benannte oder typisierte Clients hinzugefügt werden. Die Erweiterungsmethode ConfigurePrimaryHttpMessageHandler kann zum Definieren eines Delegaten verwendet werden. Der Delegat wird verwendet, um den primären HttpMessageHandler
zu erstellen und konfigurieren, der von dem Client verwendet wird:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient("configured-inner-handler")
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler()
{
AllowAutoRedirect = false,
UseDefaultCredentials = true
};
});
// Remaining code deleted for brevity.
Verwenden von IHttpClientFactory in einer Konsolen-App
Fügen Sie in einer Konsolen-App dem Projekt die folgenden Paketverweise hinzu:
Im folgenden Beispiel:
- IHttpClientFactory ist im Dienstcontainer des generischen Hosts registriert.
MyService
erstellt eine Clientfactoryinstanz aus dem-Dienst, der zum Erstellen einesHttpClient
verwendet wird.HttpClient
wird zum Abrufen einer Webseite verwendet.Main
erstellt einen Bereich, um dieGetPage
-Methode des Diensts auszuführen und die ersten 500 Zeichen des Webseiteninhalts in die Konsole zu schreiben.
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}";
}
}
}
}
Middleware für Headerweitergabe
Für die Headerweitergabe wird eine ASP.NET Core-Middleware verwendet, um HTTP-Header von eingehenden Anforderungen an ausgehende HTTP-Clientanforderungen weiterzugeben. So verwenden Sie die Headerweitergabe:
Erstellen Sie einen Verweis auf das Microsoft.AspNetCore.HeaderPropagation-Paket.
Konfigurieren Sie in
Startup
die Middleware undHttpClient
: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(); }); }
Der Client schließt die konfigurierten Header für ausgehende Anforderungen ein:
var client = clientFactory.CreateClient("MyForwardingClient"); var response = client.GetAsync(...);
Zusätzliche Ressourcen
Von Kirk Larkin, Steve Gordon, Glenn Condron und Ryan Nowak.
IHttpClientFactory kann registriert und zum Konfigurieren und Erstellen von HttpClient-Instanzen in einer App verwendet werden. IHttpClientFactory
bietet die folgenden Vorteile:
- Ein zentraler Ort für das Benennen und Konfigurieren logischer
HttpClient
-Instanzen wird damit geboten. Beispielsweise kann ein Client namens github registriert und für den Zugriff auf GitHub konfiguriert werden. Ein Standardclient kann für den allgemeinen Zugriff registriert werden. - Das Konzept der ausgehenden Middleware wird über delegierende Handler in
HttpClient
in Code umgesetzt. Außerdem werden Erweiterungen für auf Polly basierende Middleware bereitgestellt, um die delegierenden Handler inHttpClient
zu nutzen. - Das Pooling und die Lebensdauer von zugrunde liegenden
HttpClientMessageHandler
-Instanzen werden verwaltet. Durch die automatische Verwaltung werden allgemeine DNS-Probleme (Domain Name System) vermieden, die bei der manuellen Verwaltung der Lebensdauer vonHttpClient
auftreten. - Eine konfigurierbare Protokollierungsfunktion wird (über
ILogger
) für alle Anforderungen hinzugefügt, die über Clients gesendet werden, die von der Factory erstellt wurden.
Zeigen Sie Beispielcode an, oder laden Sie diesen herunter (Vorgehensweise zum Herunterladen).
Der Beispielcode in dieser Version des Themas verwendet System.Text.Json, um JSON-Inhalte zu deserialisieren, die in HTTP-Antworten zurückgegeben wurden. Verwenden Sie bei Beispielen, die Json.NET
und ReadAsAsync<T>
verwenden, die Versionsauswahl, um eine 2.x-Version dieses Themas auszuwählen.
Verbrauchsmuster
Es gibt mehrere Möglichkeiten IHttpClientFactory
in einer App zu verwenden:
Der beste Ansatz richtet sich nach den Anforderungen der App.
Grundlegende Verwendung
IHttpClientFactory
kann durch Aufrufen von AddHttpClient
registriert werden:
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.
Eine IHttpClientFactory
-Schnittstelle kann mithilfe der Dependency Injection (DI) angefordert werden. Im folgenden Code wird IHttpClientFactory
verwendet, um eine HttpClient
-Instanz zu erstellen:
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>();
}
}
}
IHttpClientFactory
wie im vorhergehenden Beispiel zu verwenden ist eine gute Möglichkeit zum Umgestalten einer vorhandene App. Dies hat keine Auswirkung auf die Verwendung von HttpClient
. An Stellen, an denen HttpClient
-Instanzen in einer vorhandenen App erstellt werden, können Sie diese Ereignisse mit Aufrufen von CreateClient ersetzen.
Benannte Clients
Benannte Clients sind in folgenden Fällen eine gute Wahl:
- Die App erfordert viele verschiedene Verwendungen von
HttpClient
. - Viele
HttpClient
s verfügen über unterschiedliche Konfigurationen.
Die Konfiguration eines benannten HttpClient
kann während der Registrierung in Startup.ConfigureServices
angegeben werden:
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");
});
Im vorangehenden Code wird der Client mit Folgendem konfiguriert:
- der Basisadresse
https://api.github.com/
- zwei Header, die für die Arbeit mit der GitHub-API erforderlich sind
CreateClient
Jedes Mal, wenn CreateClient aufgerufen wird, geschieht Folgendes:
- Eine neue Instanz von
HttpClient
wird erstellt. - Die Konfigurationsaktion wird aufgerufen.
Übergeben Sie für die Erstellung eines benannten Clients dessen Namen an 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>();
}
}
}
Im vorangehenden Code muss die Anforderung keinen Hostnamen angeben. Der Code muss nur den Pfad übergeben, da die für den Client konfigurierte Basisadresse verwendet wird.
Typisierte Clients
Typisierte Clients:
- Stellen dieselben Funktionen wie benannte Clients bereit, ohne Zeichenfolgen als Schlüssel verwenden zu müssen.
- Bieten Hilfe durch IntelliSense und Compiler beim Verarbeiten von Clients.
- Stellen einen einzelnen Ort zum Konfigurieren und Interagieren mit einem bestimmten
HttpClient
bereit. Beispielsweise kann ein einzelner typisierter Client für Folgendes verwendet werden:- für einen einzelnen Back-End-Endpunkt
- um die gesamte Logik zu kapseln, die den Endpunkt behandelt
- Funktionieren mit DI und können überall in der App eingefügt werden, wo sie benötigt werden.
Ein typisierter Client akzeptiert einen HttpClient
-Parameter in seinem Konstruktor:
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);
}
}
Wenn Sie möchten, dass Codekommentare in anderen Sprachen als Englisch angezeigt werden, informieren Sie uns in diesem GitHub-Issue.
Für den Code oben gilt:
- Die Konfiguration wird in den typisierten Client verschoben.
- Das
HttpClient
-Objekt wird als öffentliche Eigenschaft zur Verfügung gestellt.
Es können API-spezifische Methoden erstellt werden, die die HttpClient
-Funktionalität verfügbar machen. Die GetAspNetDocsIssues
-Methode kapselt z. B. Code zum Abrufen offener Probleme.
Der folgende Code ruft AddHttpClient in Startup.ConfigureServices
auf, um eine typisierte Clientklasse zu registrieren:
services.AddHttpClient<GitHubService>();
Der typisierte Client wird mit DI als „vorübergehend“ registriert. Im vorherigen Code registriert AddHttpClient
GitHubService
als vorübergehenden Dienst. Diese Registrierung verwendet eine Factorymethode für folgende Aktionen:
- Erstellen Sie eine Instanz von
HttpClient
: - Erstellen einer
GitHubService
-Instanz, wobei die Instanz vonHttpClient
an ihren Konstrukt übergeben wird
Der typisierte Client kann direkt eingefügt und verwendet werden:
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>();
}
}
}
Die Konfiguration kann für einen typisierten Client während der Registrierung in Startup.ConfigureServices
angegeben werden, anstatt im Konstruktor des typisierten Clients:
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");
});
HttpClient
kann in einem typisierten Client gekapselt werden. Anstatt ihn als eine Eigenschaft zur Verfügung zu stellen, definieren Sie eine Methode, die die HttpClient
-Instanz intern aufruft:
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);
}
}
Im vorangehenden Code wird HttpClient
in einem privaten Feld gespeichert. Der Zugriff auf HttpClient
erfolgt durch die öffentliche GetRepos
-Methode.
Generierte Clients
IHttpClientFactory
kann in Verbindung mit Bibliotheken von Drittanbietern verwendet werden, z. B. Refit. Refit ist eine REST-Bibliothek für .NET. Sie konvertiert REST-APIs in Live-Schnittstellen. Eine Implementierung der Schnittstelle wird dynamisch von RestService
generiert. HttpClient
wird für die externen HTTP-Aufrufe verwendet.
Eine Schnittstelle und eine Antwort werden definiert, um die externe API und die Antwort darzustellen:
public interface IHelloClient
{
[Get("/helloworld")]
Task<Reply> GetMessageAsync();
}
public class Reply
{
public string Message { get; set; }
}
Ein typisierter Client kann hinzugefügt werden. Refit wird zum Generieren der Implementierung verwendet:
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();
}
Die definierte Schnittstelle kann bei Bedarf mit der von DI und Refit bereitgestellten Implementierung verwendet werden:
[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();
}
}
Stellen von POST-, PUT- und DELETE-Anforderungen
In den vorangehenden Beispielen verwenden alle Anforderungen das GET-HTTP-Verb. HttpClient
unterstützt ebenso andere HTTP-Verben, einschließlich der folgenden:
- POST
- PUT
- DELETE
- PATCH
Eine komplette Liste der unterstützten HTTP-Verben finden Sie unter HttpMethod.
Im folgenden Beispiel wird gezeigt, wie Sie eine HTTP-POST-Anforderung stellen:
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();
}
Für die CreateItemAsync
-Methoden im Code oben gilt Folgendes:
- Sie serialisiert den
TodoItem
-Parameter mithilfe vonSystem.Text.Json
in JSON. Dabei wird eine Instanz von JsonSerializerOptions verwenden, um den Serialisierungsprozess zu konfigurieren. - Sie erstellt eine Instanz von StringContent, um den serialisierten JSON-Code zum Senden im HTTP-Anforderungstext zu packen.
- Die Methode ruft PostAsync auf, um den JSON-Inhalt an die angegebene URL zu senden. Dies ist eine relative URL, die zu HttpClient.BaseAddress hinzugefügt wird.
- Sie ruft EnsureSuccessStatusCode auf, um eine Ausnahme auszulösen, wenn der Antwortstatuscode nicht auf Erfolg hinweist.
HttpClient
unterstützt auch andere Inhaltstypen. Beispiel: MultipartContent und StreamContent. Eine komplette Liste der unterstützten Inhaltstypen finden Sie unter HttpContent.
Das folgende Beispiel zeigt eine HTTP-PUT-Anforderung.
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();
}
Der vorangehende Code ist dem POST-Beispiel sehr ähnlich. Die SaveItemAsync
-Methode ruft PutAsync anstelle von PostAsync
auf.
Das folgende Beispiel zeigt eine HTTP-DELETE-Anforderung.
public async Task DeleteItemAsync(long itemId)
{
using var httpResponse =
await _httpClient.DeleteAsync($"/api/TodoItems/{itemId}");
httpResponse.EnsureSuccessStatusCode();
}
Im vorangehenden Code ruft die DeleteItemAsync
-Methode DeleteAsync auf. Da HTTP-DELETE-Anforderungen normalerweise keinen Text enthalten, stellt die DeleteAsync
-Methode keine Überladung bereit, die eine Instanz von HttpContent
akzeptiert.
Weitere Informationen zur Verwendung unterschiedlicher HTTP-Verben mit HttpClient
finden Sie unter HttpClient.
Middleware für ausgehende Anforderungen
HttpClient
enthält das Konzept, Handler zu delegieren, die für ausgehende HTTP-Anforderungen miteinander verknüpft werden können. IHttpClientFactory
:
- Erleichtert das Definieren der Handler, die für jeden benannten Client angewendet werden
- Unterstützt die Registrierung und Verkettung von mehreren Handlern, um eine Pipeline für die Middleware für ausgehende Anforderungen zu erstellen. Jeder dieser Handler kann vor und nach der ausgehenden Anforderung Aufgaben ausführen. Dieses Muster:
- ähnelt der eingehenden Pipeline für Middleware in ASP.NET Core
- bietet einen Mechanismus zum Verwalten von übergreifenden Belangen bezüglich HTTP-Anforderungen, z. B.:
- Zwischenspeicherung
- Fehlerbehandlung
- Serialisierung
- Protokollierung
So erstellen Sie einen delegierenden Handler:
- Leiten Sie von DelegatingHandler ab.
- Überschreiben Sie SendAsync. Führen Sie Code aus, bevor die Anforderung an den nächsten Handler in der Pipeline übergeben wird:
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);
}
}
Der vorangehende Code überprüft, ob die Anforderung einen X-API-KEY
-Header enthält. Wenn X-API-KEY
fehlt, wird BadRequest zurückgegeben.
Für eine HttpClient
-Klasse mit Microsoft.Extensions.DependencyInjection.HttpClientBuilderExtensions.AddHttpMessageHandler kann mehr als ein Handler zur Konfiguration hinzugefügt werden:
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.
Im vorangehenden Code ist ValidateHeaderHandler
mit DI registriert. Nach der Registrierung kann AddHttpMessageHandler aufgerufen werden, was den Typ für den Handler übergibt.
Mehrere Handler können in der Reihenfolge registriert werden, in der sie ausgeführt werden sollen. Jeder Handler umschließt den nächsten Handler, bis der endgültige HttpClientHandler
die Anforderung ausführt:
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>();
Verwenden von DI in Middleware für ausgehende Anforderungen
Wenn IHttpClientFactory
einen neuen delegierenden Handler erstellt, wird DI verwendet, um die Konstruktorparameter des Handlers zu erfüllen. IHttpClientFactory
erstellt einen separaten DI-Bereich für jeden Handler. Dies kann zu überraschendem Verhalten führen, wenn ein Handler einen bereichsbezogenen Dienst nutzt.
Sehen Sie sich beispielsweise die folgende Schnittstelle und ihre Implementierung an, die eine Aufgabe als Vorgang mit einem Bezeichner OperationId
darstellt:
public interface IOperationScoped
{
string OperationId { get; }
}
public class OperationScoped : IOperationScoped
{
public string OperationId { get; } = Guid.NewGuid().ToString()[^4..];
}
Wie der Name bereits vermuten lässt, wird IOperationScoped
bei DI registriert, wobei eine bereichsbezogene Lebensdauer verwendet wird:
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();
}
Der folgende delegierende Handler verwendet IOperationScoped
, um den X-OPERATION-ID
-Header für die ausgehende Anforderung festzulegen:
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);
}
}
Navigieren Sie im HttpRequestsSample
-Download] zu /Operation
, und aktualisieren Sie die Seite. Der Wert für den Anforderungsbereich ändert sich für jede Anforderung, aber der Wert des Handlerbereichs ändert sich nur alle 5 Sekunden.
Handler können von Diensten eines beliebigen Bereichs abhängen. Dienste, von denen Handler abhängig sind, werden verworfen, wenn der Handler verworfen wird.
Verwenden Sie einen der folgenden Ansätze, um den anforderungsspezifischen Zustand mit Meldungshandlern zu teilen:
- Übergeben Sie Daten mit HttpRequestMessage.Properties an den Handler.
- Greifen Sie mit IHttpContextAccessor auf die aktuelle Anforderung zu.
- Erstellen Sie ein benutzerdefiniertes AsyncLocal<T>-Speicherobjekt, um die Daten zu übergeben.
Verwenden von Polly-basierten Handlern
IHttpClientFactory
ist mit der Drittanbieterbibliothek Polly integriert. Polly ist eine umfassende Bibliothek für die Behandlung von beständigen und vorübergehenden Fehlern für .NET. Entwicklern wird damit ermöglicht, Richtlinien wie Wiederholungsrichtlinien, Trennschalterrichtlinien, Timeout-Richtlinien, Bulkhead Isolation-Richtlinien und Ausweichrichtlinien in einer flüssigen und threadsicheren Art und Weise auszudrücken.
Erweiterungsmethoden werden bereitgestellt, um die Verwendung von Polly-Richtlinien mit konfigurierten HttpClient
-Instanzen zu aktivieren. Die Polly-Erweiterungen unterstützen das Hinzufügen von Polly-basierten Handlern zu Clients. Polly erfordert das Microsoft.Extensions.Http.Polly-NuGet-Paket.
Behandeln von vorrübergehenden Fehlern
Fehler treten normalerweise auf, wenn externe HTTP-Aufrufe vorübergehend sind. AddTransientHttpErrorPolicy ermöglicht die Definition einer Richtlinie, um vorübergehende Fehler zu behandeln. Mit AddTransientHttpErrorPolicy
konfigurierte Richtlinien behandeln die folgenden Antworten:
- HttpRequestException
- HTTP 5xx
- HTTP 408
AddTransientHttpErrorPolicy
ermöglicht den Zugriff auf ein PolicyBuilder
-Objekt, das für die Fehlerbehandlung konfiguriert wurde und mögliche vorübergehende Fehler darstellt:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient<UnreliableEndpointCallerService>()
.AddTransientHttpErrorPolicy(p =>
p.WaitAndRetryAsync(3, _ => TimeSpan.FromMilliseconds(600)));
// Remaining code deleted for brevity.
Im vorangehenden Code wird eine WaitAndRetryAsync
-Richtlinie definiert. Anforderungsfehler werden bis zu dreimal mit einer Verzögerung von 600 ms wiederholt.
Dynamisches Auswählen von Richtlinien
Erweiterungsmethoden werden zum Hinzufügen von Polly-basierten Handlern bereitgestellt, z. B. AddPolicyHandler. Der folgende AddPolicyHandler
-Überladung prüft die Anforderung, um zu entscheiden, welche Richtlinie angewendet werden soll:
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);
Im vorangehenden Code wird ein 10 Sekunden langer Timeout angewendet, wenn die ausgehende Anforderung eine HTTP GET-Anforderung ist. Für alle anderen HTTP-Methoden wird ein 30 Sekunden langer Timeout verwendet.
Hinzufügen mehrerer Polly-Handler
Es ist üblich, Polly-Richtlinien zu schachteln:
services.AddHttpClient("multiplepolicies")
.AddTransientHttpErrorPolicy(p => p.RetryAsync(3))
.AddTransientHttpErrorPolicy(
p => p.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));
Im vorherigen Beispiel:
- Zwei Handler werden hinzugefügt.
- Der erste Handler verwendet AddTransientHttpErrorPolicy, um eine Wiederholungsrichtlinie hinzuzufügen. Fehlgeschlagene Anforderungen werden bis zu drei Mal wiederholt.
- Der zweite
AddTransientHttpErrorPolicy
-Aufruf fügt eine Trennschalterrichtlinie hinzu. Weitere externe Anforderungen werden 30 Sekunden lang blockiert, wenn fünf Fehlversuche hintereinander stattfinden. Trennschalterrichtlinien sind zustandsbehaftet. Alle Aufrufe über diesen Client teilen den gleichen Schalterzustand.
Hinzufügen von Richtlinien aus der Polly-Registrierung
Eine Methode zum Verwalten regelmäßig genutzter Richtlinien ist, Sie einmal zu definieren und mit PolicyRegistry
zu registrieren.
Im folgenden Code wird Folgendes ausgeführt:
- Die Richtlinien „regular“ und „long“ werden hinzugefügt.
- AddPolicyHandlerFromRegistry fügt die Richtlinien „regular“ und „long“ aus der Registrierung hinzu.
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.
Weitere Informationen zu IHttpClientFactory
und Polly-Integrationen finden Sie im Polly-Wiki.
HttpClient und die Verwaltung der Lebensdauer
Bei jedem Aufruf von CreateClient
in der IHttpClientFactory
wird eine neue Instanz von HttpClient
zurückgegeben. Pro benannter Client wird ein HttpMessageHandler erstellt. Die Factory verwaltet die Lebensdauer der HttpMessageHandler
-Instanzen.
IHttpClientFactory
legt die HttpMessageHandler
-Instanzen zusammen, die von der Factory zum Reduzieren des Ressourcenverbrauchs erstellt wurden. Eine HttpMessageHandler
-Instanz kann aus dem Pool wiederverwendet werden, wenn eine neue HttpClient
-Instanz erstellt wird und deren Lebensdauer noch nicht abgelaufen ist.
Das Zusammenlegen von Handlern ist ein wünschenswerter Vorgang, da jeder Handler in der Regel seine zugrunde liegenden HTTP-Verbindungen selbst verwaltet. Wenn mehr Handler als nötig erstellt werden, können Verzögerungen bei Verbindungen entstehen. Einige Handler halten Verbindungen auch unbegrenzt offen, was verhindert, dass der Handler auf DNS-Änderungen (Domain Name System) reagiert.
Die Standardlebensdauer von Handlern beträgt zwei Minuten. Der Standardwert kann für jeden benannten Client überschrieben werden:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient("extendedhandlerlifetime")
.SetHandlerLifetime(TimeSpan.FromMinutes(5));
// Remaining code deleted for brevity.
HttpClient
-Instanzen können im Allgemeinen als .NET-Objekte behandelt werden, die nicht verworfen werden müssen. Beim Verwerfen werden ausgehende Anforderungen abgebrochen, und es wird sichergestellt, dass die angegebene HttpClient
-Instanz nach dem Aufruf von Dispose nicht mehr verwendet werden kann. IHttpClientFactory
verfolgt von HttpClient
-Instanzen verwendete Ressourcen nach und verwirft sie.
Das Beibehalten einer einzelnen HttpClient
-Instanz für einen langen Zeitraum ist ein allgemeines Muster, das vor der Einführung von IHttpClientFactory
verwendet wurde. Dieses Muster wird nach der Migration zu IHttpClientFactory
überflüssig.
Alternativen zu IHttpClientFactory
Mit der Verwendung von IHttpClientFactory
in einer DI-fähigen App wird Folgendes vermieden:
- Probleme mit der Ressourcenauslastung durch Zusammenlegen von
HttpMessageHandler
-Instanzen - Probleme durch veraltetes DNS durch regelmäßigen Wechsel von
HttpMessageHandler
-Instanzen
Es gibt alternative Möglichkeiten zum Lösen des vorangehenden Problems mithilfe einer langlebigen SocketsHttpHandler-Instanz:
- Erstellen Sie eine
SocketsHttpHandler
-Instanz, wenn die App gestartet wird, und verwenden Sie diese für die Lebensdauer der App. - Konfigurieren Sie einen geeigneten Wert für PooledConnectionLifetime basierend auf den DNS-Aktualisierungszeiten.
- Erstellen Sie bei Bedarf
HttpClient
-Instanzen mithilfe vonnew HttpClient(handler, disposeHandler: false)
.
Diese Ansätze lösen die Ressourcenverwaltungsprobleme, die IHttpClientFactory
auf ähnliche Weise löst.
SocketsHttpHandler
gibt Verbindungen fürHttpClient
-Instanzen frei. Durch diese Freigabe wird die Erschöpfung von Sockets vermieden.SocketsHttpHandler
wechselt die Verbindungen anhand vonPooledConnectionLifetime
, um Probleme durch veraltetes DNS zu umgehen.
Cookies
Die zusammengelegten HttpMessageHandler
-Instanzen resultieren in der Freigabe von CookieContainer
-Objekten. Die unerwartete Freigabe von CookieContainer
-Objekten führt oft zu fehlerhaftem Code. Ziehen Sie für Apps, die Cookies erfordern, folgende Vorgehensweisen in Betracht:
- Deaktivieren der automatischen cookieverarbeitung
- Vermeiden der Verwendung von
IHttpClientFactory
Rufen Sie ConfigurePrimaryHttpMessageHandler auf, um die automatische cookieverarbeitung zu deaktivieren:
services.AddHttpClient("configured-disable-automatic-cookies")
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler()
{
UseCookies = false,
};
});
Protokollierung
Über IHttpClientFactory
erstellte Clients zeichnen Protokollmeldungen für alle Anforderungen auf. Aktivieren Sie die entsprechende Informationsebene in der Protokollierungskonfiguration, um die Standardprotokollmeldungen anzuzeigen. Zusätzliche Protokollierung, z.B. das Protokollieren von Anforderungsheadern, wird nur auf der Ablaufverfolgungsebene enthalten.
Die Protokollierungskategorie für jeden Client enthält den Namen des Clients. Ein Client namens MyNamedClient protokolliert z. B. Nachrichten mit der Kategorie „System.Net.Http.HttpClient.MyNamedClient.LogicalHandler“. Meldungen mit dem Suffix LogicalHandler treten außerhalb der Anforderungshandlerpipeline auf. In der Anforderung werden Meldungen protokolliert, bevor andere Handler in der Pipeline sie verarbeitet haben. In der Antwort werden Meldungen protokolliert, nachdem andere Handler in der Pipeline die Antwort empfangen haben.
Die Protokollierung tritt ebenfalls innerhalb der Anforderungshandlerpipeline auf. Im Beispiel von MyNamedClient werden diese Nachrichten mit der Protokollkategorie „System.Net.Http.HttpClient.MyNamedClient.ClientHandler“ protokolliert. Für die Anforderung erfolgt dies, nachdem alle anderen Handler ausgeführt wurden und bevor die Anforderung gesendet wird. Diese Protokollierung enthält den Zustand der Antwort in der Antwort, bevor sie an die Handlerpipeline zurückgegeben wird.
Das Aktivieren der Protokollierung außerhalb und innerhalb der Pipeline ermöglicht die Überprüfung der Änderungen, die durch andere Handler in der Pipeline erfolgt sind. Dies kann die Änderungen an Anforderungsheadern oder am Statuscode der Antwort enthalten.
Das Einschließen des Namens des Clients in der Protokollkategorie aktiviert die Protokollfilterung für spezifische benannte Clients.
Konfigurieren von HttpMessageHandler
Es kann notwendig sein, die Konfiguration des inneren von einem Client verwendeten HttpMessageHandler
zu steuern.
IHttpClientBuilder
wird zurückgegeben, wenn benannte oder typisierte Clients hinzugefügt werden. Die Erweiterungsmethode ConfigurePrimaryHttpMessageHandler kann zum Definieren eines Delegaten verwendet werden. Der Delegat wird verwendet, um den primären HttpMessageHandler
zu erstellen und konfigurieren, der von dem Client verwendet wird:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient("configured-inner-handler")
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler()
{
AllowAutoRedirect = false,
UseDefaultCredentials = true
};
});
// Remaining code deleted for brevity.
Verwenden von IHttpClientFactory in einer Konsolen-App
Fügen Sie in einer Konsolen-App dem Projekt die folgenden Paketverweise hinzu:
Im folgenden Beispiel:
- IHttpClientFactory ist im Dienstcontainer des generischen Hosts registriert.
MyService
erstellt eine Clientfactoryinstanz aus dem-Dienst, der zum Erstellen einesHttpClient
verwendet wird.HttpClient
wird zum Abrufen einer Webseite verwendet.Main
erstellt einen Bereich, um dieGetPage
-Methode des Diensts auszuführen und die ersten 500 Zeichen des Webseiteninhalts in die Konsole zu schreiben.
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}";
}
}
}
}
Middleware für Headerweitergabe
Für die Headerweitergabe wird eine ASP.NET Core-Middleware verwendet, um HTTP-Header von eingehenden Anforderungen an ausgehende HTTP-Clientanforderungen weiterzugeben. So verwenden Sie die Headerweitergabe:
Erstellen Sie einen Verweis auf das Microsoft.AspNetCore.HeaderPropagation-Paket.
Konfigurieren Sie in
Startup
die Middleware undHttpClient
: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(); }); }
Der Client schließt die konfigurierten Header für ausgehende Anforderungen ein:
var client = clientFactory.CreateClient("MyForwardingClient"); var response = client.GetAsync(...);
Zusätzliche Ressourcen
Von Glenn Condron, Ryan Nowak, und Steve Gordon
IHttpClientFactory kann registriert und zum Konfigurieren und Erstellen von HttpClient-Instanzen in einer App verwendet werden. Dies hat folgende Vorteile:
- Ein zentraler Ort für das Benennen und Konfigurieren logischer
HttpClient
-Instanzen wird damit geboten. Ein GitHub-Client kann beispielsweise registriert werden und so konfiguriert werden, um auf GitHub zuzugreifen. Ein Standard-Client kann für andere Zwecke registriert werden. - Das Konzept der ausgehenden Middleware wird über delegierende Handler in
HttpClient
in Code umgesetzt. Außerdem werden Erweiterungen für auf Polly basierende Middleware bereitgestellt, die dies nutzen. - Das Pooling und die Lebensdauer von zugrunde liegenden
HttpClientMessageHandler
-Instanzen werden verwaltet, um gängige DNS-Probleme zu vermeiden, die bei der manuellen Verwaltung derHttpClient
-Lebensdauer auftreten. - Eine konfigurierbare Protokollierungsfunktion wird (über
ILogger
) für alle Anforderungen hinzugefügt, die über Clients gesendet werden, die von der Factory erstellt wurden.
Anzeigen oder Herunterladen von Beispielcode (Vorgehensweise zum Herunterladen)
Voraussetzungen
Für Projekte mit der Zielplattform .NET Framework muss das NuGet-Paket Microsoft.Extensions.Http installiert werden. Projekte für .NET Core, die auf das Metapaket Microsoft.AspNetCore.App verweisen, enthalten bereits das Microsoft.Extensions.Http
-Paket.
Verbrauchsmuster
Es gibt mehrere Möglichkeiten IHttpClientFactory
in einer App zu verwenden:
Keine dieser Möglichkeiten ist einer anderen überlegen. Der beste Ansatz richtet sich nach den Einschränkungen der App.
Grundlegende Verwendung
IHttpClientFactory
kann durch Aufrufen der Erweiterungsmethode AddHttpClient
auf IServiceCollection
in der Methode Startup.ConfigureServices
registriert werden.
services.AddHttpClient();
Nach der Registrierung kann Code IHttpClientFactory
überall akzeptieren, wo Dienste mithilfe der Dependency Injection (DI) eingefügt werden können. IHttpClientFactory
kann zum Erstellen einer HttpClient
-Instanz verwendet werden:
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>();
}
}
}
IHttpClientFactory
auf diese Weise zu verwenden, ist eine gute Möglichkeit zum Umgestalten einer vorhandenen App. Dies hat keine Auswirkung auf die Verwendung von HttpClient
. An Stellen, in denen HttpClient
-Instanzen derzeit erstellt werden, können Sie diese Ereignisse mit einem Aufruf von CreateClient ersetzen.
Benannte Clients
Wenn eine App mehrere verschiedene Verwendungen von HttpClient
mit jeweils unterschiedlicher Konfiguration erfordert, ist die Verwendung von benannten Clients eine Option. Die Konfiguration einer benannten HttpClient
kann während der Registrierung in Startup.ConfigureServices
angegeben werden.
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");
});
Im oben stehenden Code wird AddHttpClient
aufgerufen, und dabei wird der Name github angegeben. Auf diesen Client wird eine Standardkonfiguration angewendet, d. h. die Basisadresse und zwei Header, die für die Arbeit mit der GitHub-API erforderlich sind.
Wenn CreateClient
aufgerufen wird, wird jedes Mal eine neue Instanz von HttpClient
erstellt und die Konfigurationsaktion aufgerufen.
Zum Verarbeiten eines benannten Clients kann ein Zeichenfolgenparameter an CreateClient
übergeben werden. Geben Sie den Namen des zu erstellenden Clients an:
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>();
}
}
}
Im vorangehenden Code muss die Anforderung keinen Hostnamen angeben. Sie muss nur den Pfad übergeben, da die für den Client konfigurierte Basisadresse verwendet wird.
Typisierte Clients
Typisierte Clients:
- Stellen dieselben Funktionen wie benannte Clients bereit, ohne Zeichenfolgen als Schlüssel verwenden zu müssen.
- Bieten Hilfe durch IntelliSense und Compiler beim Verarbeiten von Clients.
- Stellen einen einzelnen Ort zum Konfigurieren und Interagieren mit einem bestimmten
HttpClient
bereit. Zum Beispiel kann ein einzelner typisierter Client für einen einzelnen Back-End-Endpunkt verwendet werden, der jegliche Logik kapselt, die mit diesem Endpunkt interagiert. - Funktionieren mit DI und können überall in Ihrer App eingefügt werden, wo sie benötigt werden.
Ein typisierter Client akzeptiert einen HttpClient
-Parameter in seinem Konstruktor:
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;
}
}
Im vorangehenden Code wird die Konfiguration in den typisierten Client verschoben. Das HttpClient
-Objekt wird als öffentliche Eigenschaft zur Verfügung gestellt. Es ist möglich, API-spezifische Methoden zu definieren, die die HttpClient
-Funktionalität zur Verfügung stellen. Die Methode GetAspNetDocsIssues
kapselt den Code, der für die Abfrage und Analyse des neuesten offenen Problems aus dem GitHub-Repository erforderlich ist.
Zum Registrieren eines typisierten Clients kann die generische Erweiterungsmethode AddHttpClient innerhalb von Startup.ConfigureServices
verwendet werden, die die typisierte Klasse angeben:
services.AddHttpClient<GitHubService>();
Der typisierte Client wird mit DI als „vorübergehend“ registriert. Der typisierte Client kann direkt eingefügt und verwendet werden:
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>();
}
}
}
Die Konfiguration kann für einen typisierten Client während der Registrierung in Startup.ConfigureServices
angegeben werden, anstatt im Konstruktor des typisierten Clients, falls dies bevorzugt wird:
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");
});
Es ist möglich, HttpClient
vollständig in einem typisierten Client zu kapseln. Anstatt ihn als eine Eigenschaft zur Verfügung zu stellen, können öffentliche Methoden bereitgestellt werden, die die HttpClient
-Instanz intern aufrufen.
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;
}
}
Im vorangehenden Code wird HttpClient
als ein privates Feld gespeichert. Alle Zugriffe, die externe Aufrufe durchführen, durchlaufen die Methode GetRepos
.
Generierte Clients
IHttpClientFactory
kann in Verbindung mit anderen Bibliotheken von Drittanbietern verwendet werden, z.B. Refit. Refit ist eine REST-Bibliothek für .NET. Sie konvertiert REST-APIs in Live-Schnittstellen. Eine Implementierung der Schnittstelle wird dynamisch von RestService
generiert. HttpClient
wird für die externen HTTP-Aufrufe verwendet.
Eine Schnittstelle und eine Antwort werden definiert, um die externe API und die Antwort darzustellen:
public interface IHelloClient
{
[Get("/helloworld")]
Task<Reply> GetMessageAsync();
}
public class Reply
{
public string Message { get; set; }
}
Ein typisierter Client kann hinzugefügt werden. Refit wird zum Generieren der Implementierung verwendet:
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();
}
Die definierte Schnittstelle kann bei Bedarf mit der von DI und Refit bereitgestellten Implementierung verwendet werden:
[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();
}
}
Middleware für ausgehende Anforderungen
HttpClient
enthält bereits das Konzept, Handler zu delegieren, die für ausgehende HTTP-Anforderungen miteinander verknüpft werden können. IHttpClientFactory
erleichtert das Definieren der Handler, die für jeden benannten Client angewendet werden. Die Registrierung und Verkettung von mehreren Handlern wird unterstützt, um eine Pipeline für die Middleware für ausgehende Anforderungen zu erstellen. Jeder dieser Handler kann vor und nach der ausgehenden Anforderung Aufgaben ausführen. Dieses Muster ähnelt der eingehenden Pipeline für Middleware in ASP.NET Core. Das Muster bietet einen Mechanismus zum Verwalten von übergreifenden Belangen bezüglich HTTP-Anforderungen, einschließlich der Zwischenspeicherung, Fehlerbehandlung, Serialisierung und Protokollierung.
Definieren Sie zum Erstellen eines Handlers eine von DelegatingHandler abgeleitete Klasse. Überschreiben Sie die Methode SendAsync
, um Code auszuführen, bevor die Anforderung an den nächsten Handler in der Pipeline übergeben wird:
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);
}
}
Der vorangehende Code definiert einen einfachen Handler. Er überprüft, ob ein X-API-KEY
-Header in die Anforderung eingefügt wurde. Wenn der Header fehlt, kann er den HTTP-Aufruf vermeiden und eine geeignete Antwort zurückgeben.
Während der Registrierung kann mindestens ein Handler der Konfiguration eines HttpClient
hinzugefügt werden. Dieser Vorgang wird über die Erweiterungsmethoden von IHttpClientBuilder ermöglicht.
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>();
Im vorangehenden Code ist ValidateHeaderHandler
mit DI registriert. Der Handler muss in DI als vorübergehender Dienst registriert sein, niemals bereichsbezogen. Wenn der Handler als bereichsbezogener Dienst registriert ist und alle Dienste, von denen der Handler abhängig ist, gelöscht werden können:
- Die Dienste des Handlers können verworfen werden, bevor der Handler den Gültigkeitsbereich verlässt.
- Der verworfenen Handlerdienst bewirken, dass der Handler fehlschlägt.
Nach der Registrierung kann AddHttpMessageHandler aufgerufen werden, wobei der Typ für den Handler übergeben wird.
Mehrere Handler können in der Reihenfolge registriert werden, in der sie ausgeführt werden sollen. Jeder Handler umschließt den nächsten Handler, bis der endgültige HttpClientHandler
die Anforderung ausführt:
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>();
Verwenden Sie einen der folgenden Ansätze, um den anforderungsspezifischen Zustand mit Meldungshandlern zu teilen:
- Übergeben Sie Daten mit
HttpRequestMessage.Properties
an den Handler. - Greifen Sie mit
IHttpContextAccessor
auf die aktuelle Anforderung zu. - Erstellen Sie ein benutzerdefiniertes
AsyncLocal
-Speicherobjekt, um die Daten zu übergeben.
Verwenden von Polly-basierten Handlern
IHttpClientFactory
integriert mit der beliebten Drittanbieter-Bibliothek namens Polly. Polly ist eine umfassende Bibliothek für die Behandlung von beständigen und vorübergehenden Fehlern für .NET. Entwicklern wird damit ermöglicht, Richtlinien wie Wiederholungsrichtlinien, Trennschalterrichtlinien, Timeout-Richtlinien, Bulkhead Isolation-Richtlinien und Ausweichrichtlinien in einer flüssigen und threadsicheren Art und Weise auszudrücken.
Erweiterungsmethoden werden bereitgestellt, um die Verwendung von Polly-Richtlinien mit konfigurierten HttpClient
-Instanzen zu aktivieren. Die Polly-Erweiterungen:
- Unterstützen das Hinzufügen von Polly-basierten Handlern zu Clients.
- Können nach Installation des Microsoft.Extensions.Http.Polly-NuGet-Pakets verwendet werden. Das Paket ist nicht im freigegebenen ASP.NET Core-Framework enthalten.
Behandeln von vorrübergehenden Fehlern
Am häufigsten treten Fehler auf, wenn externe HTTP-Aufrufe vorübergehend sind. Eine praktische Erweiterungsmethode namens AddTransientHttpErrorPolicy
ist enthalten. Sie ermöglicht das Definieren von Richtlinien zum Behandeln vorübergehender Fehler. Richtlinien, die mit dieser Erweiterungsmethode konfiguriert wurden, behandeln HttpRequestException
, HTTP 5xx-Antworten und HTTP 408-Antworten.
Die AddTransientHttpErrorPolicy
-Erweiterung kann in Startup.ConfigureServices
verwendet werden. Die Erweiterung ermöglicht den Zugriff auf ein PolicyBuilder
-Objekt, das für die Fehlerbehandlung konfiguriert wurde und mögliche vorübergehende Fehler darstellt:
services.AddHttpClient<UnreliableEndpointCallerService>()
.AddTransientHttpErrorPolicy(p =>
p.WaitAndRetryAsync(3, _ => TimeSpan.FromMilliseconds(600)));
Im vorangehenden Code wird eine WaitAndRetryAsync
-Richtlinie definiert. Anforderungsfehler werden bis zu dreimal mit einer Verzögerung von 600 ms wiederholt.
Dynamisches Auswählen von Richtlinien
Es gibt zusätzliche Erweiterungsmethoden, die zum Hinzufügen von Polly-basierten Handlern verwendet werden können. Eine dieser Erweiterungen ist AddPolicyHandler
, die über mehrere Überladungen verfügt. Eine Überladung ermöglicht, dass die Anforderung beim Definieren der zu verwendenden Richtlinie überprüft werden kann:
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);
Im vorangehenden Code wird ein 10 Sekunden langer Timeout angewendet, wenn die ausgehende Anforderung eine HTTP GET-Anforderung ist. Für alle anderen HTTP-Methoden wird ein 30 Sekunden langer Timeout verwendet.
Hinzufügen mehrerer Polly-Handler
Es ist üblich, Polly-Richtlinien zu schachteln, um erweiterte Funktionen bereitzustellen:
services.AddHttpClient("multiplepolicies")
.AddTransientHttpErrorPolicy(p => p.RetryAsync(3))
.AddTransientHttpErrorPolicy(
p => p.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));
Im vorangehenden Beispiel werden zwei Handler hinzugefügt. Der Erste verwendet die AddTransientHttpErrorPolicy
-Erweiterung, um eine Wiederholungsrichtlinie hinzuzufügen. Fehlgeschlagene Anforderungen werden bis zu drei Mal wiederholt. Der zweite Aufruf von AddTransientHttpErrorPolicy
fügt eine Trennschalterrichtlinie hinzu. Weitere externe Anforderungen werden 30 Sekunden lang blockiert, wenn fünf Fehlversuche hintereinander stattfinden. Trennschalterrichtlinien sind zustandsbehaftet. Alle Aufrufe über diesen Client teilen den gleichen Schalterzustand.
Hinzufügen von Richtlinien aus der Polly-Registrierung
Eine Methode zum Verwalten regelmäßig genutzter Richtlinien ist, Sie einmal zu definieren und mit PolicyRegistry
zu registrieren. Eine Erweiterungsmethode wird bereitgestellt, die einem Handler ermöglicht, mithilfe einer Richtlinie aus der Registrierung hinzugefügt zu werden:
var registry = services.AddPolicyRegistry();
registry.Add("regular", timeout);
registry.Add("long", longTimeout);
services.AddHttpClient("regulartimeouthandler")
.AddPolicyHandlerFromRegistry("regular");
Im oben stehenden Code werden zwei Richtlinien registriert, wenn PolicyRegistry
zu ServiceCollection
hinzugefügt wird. Damit eine Richtlinie aus der Registrierung verwendet werden kann, wird die Methode AddPolicyHandlerFromRegistry
verwendet. Dabei wird der Name der anzuwendenden Richtlinie übergeben.
Weitere Informationen zu IHttpClientFactory
und Polly-Integrationen finden Sie im Polly Wiki.
HttpClient und die Verwaltung der Lebensdauer
Bei jedem Aufruf von CreateClient
in der IHttpClientFactory
wird eine neue Instanz von HttpClient
zurückgegeben. Es gibt einen HttpMessageHandler pro benanntem Client. Die Factory verwaltet die Lebensdauer der HttpMessageHandler
-Instanzen.
IHttpClientFactory
legt die HttpMessageHandler
-Instanzen zusammen, die von der Factory zum Reduzieren des Ressourcenverbrauchs erstellt wurden. Eine HttpMessageHandler
-Instanz kann aus dem Pool wiederverwendet werden, wenn eine neue HttpClient
-Instanz erstellt wird und deren Lebensdauer noch nicht abgelaufen ist.
Das Zusammenlegen von Handlern ist ein wünschenswerter Vorgang, da jeder Handler in der Regel seine zugrunde liegenden HTTP-Verbindungen selbst verwaltet. Wenn mehr Handler als nötig erstellt werden, können Verzögerungen bei Verbindungen entstehen. Einige Handler halten Verbindungen auch unbegrenzt offen, was verhindert, dass der Handler auf DNS-Änderungen reagiert.
Die Standardlebensdauer von Handlern beträgt zwei Minuten. Der Standardwert kann für jeden benannten Client überschrieben werden. Rufen Sie SetHandlerLifetime auf dem bei der Erstellung des Clients zurückgegebenen IHttpClientBuilder
auf, um den Wert zu überschreiben:
services.AddHttpClient("extendedhandlerlifetime")
.SetHandlerLifetime(TimeSpan.FromMinutes(5));
Das Verwerfen des Client ist nicht erforderlich. Beim Verwerfen werden ausgehende Anforderungen abgebrochen, und es wird sichergestellt, dass die angegebene HttpClient
-Instanz nach dem Aufruf von Dispose nicht mehr verwendet werden kann. IHttpClientFactory
verfolgt von HttpClient
-Instanzen verwendete Ressourcen nach und verwirft sie. Die HttpClient
-Instanzen können im Allgemeinen als .NET-Objekte behandelt werden, die nicht verworfen werden müssen.
Das Beibehalten einer einzelnen HttpClient
-Instanz für einen langen Zeitraum ist ein allgemeines Muster, das vor der Einführung von IHttpClientFactory
verwendet wurde. Dieses Muster wird nach der Migration zu IHttpClientFactory
überflüssig.
Alternativen zu IHttpClientFactory
Mit der Verwendung von IHttpClientFactory
in einer DI-fähigen App wird Folgendes vermieden:
- Probleme mit der Ressourcenauslastung durch Zusammenlegen von
HttpMessageHandler
-Instanzen - Probleme durch veraltetes DNS durch regelmäßigen Wechsel von
HttpMessageHandler
-Instanzen
Es gibt alternative Möglichkeiten zum Lösen des vorangehenden Problems mithilfe einer langlebigen SocketsHttpHandler-Instanz:
- Erstellen Sie eine
SocketsHttpHandler
-Instanz, wenn die App gestartet wird, und verwenden Sie diese für die Lebensdauer der App. - Konfigurieren Sie einen geeigneten Wert für PooledConnectionLifetime basierend auf den DNS-Aktualisierungszeiten.
- Erstellen Sie bei Bedarf
HttpClient
-Instanzen mithilfe vonnew HttpClient(handler, disposeHandler: false)
.
Diese Ansätze lösen die Ressourcenverwaltungsprobleme, die IHttpClientFactory
auf ähnliche Weise löst.
SocketsHttpHandler
gibt Verbindungen fürHttpClient
-Instanzen frei. Durch diese Freigabe wird die Erschöpfung von Sockets vermieden.SocketsHttpHandler
wechselt die Verbindungen anhand vonPooledConnectionLifetime
, um Probleme durch veraltetes DNS zu umgehen.
Cookies
Die zusammengelegten HttpMessageHandler
-Instanzen resultieren in der Freigabe von CookieContainer
-Objekten. Die unerwartete Freigabe von CookieContainer
-Objekten führt oft zu fehlerhaftem Code. Ziehen Sie für Apps, die Cookies erfordern, folgende Vorgehensweisen in Betracht:
- Deaktivieren der automatischen cookieverarbeitung
- Vermeiden der Verwendung von
IHttpClientFactory
Rufen Sie ConfigurePrimaryHttpMessageHandler auf, um die automatische cookieverarbeitung zu deaktivieren:
services.AddHttpClient("configured-disable-automatic-cookies")
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler()
{
UseCookies = false,
};
});
Protokollierung
Über IHttpClientFactory
erstellte Clients zeichnen Protokollmeldungen für alle Anforderungen auf. Aktivieren Sie die entsprechende Informationsebene in Ihrer Protokollierungskonfiguration, um die Standardprotokollmeldungen anzuzeigen. Zusätzliche Protokollierung, z.B. das Protokollieren von Anforderungsheadern, wird nur auf der Ablaufverfolgungsebene enthalten.
Die Protokollierungskategorie für jeden Client enthält den Namen des Clients. Ein Client namens MyNamedClient protokolliert beispielsweise Meldungen mit der Kategorie System.Net.Http.HttpClient.MyNamedClient.LogicalHandler
. Meldungen mit dem Suffix LogicalHandler treten außerhalb der Anforderungshandlerpipeline auf. In der Anforderung werden Meldungen protokolliert, bevor andere Handler in der Pipeline sie verarbeitet haben. In der Antwort werden Meldungen protokolliert, nachdem andere Handler in der Pipeline die Antwort empfangen haben.
Die Protokollierung tritt ebenfalls innerhalb der Anforderungshandlerpipeline auf. Im Beispiel MyNamedClient werden diese Meldungen für die Protokollkategorie System.Net.Http.HttpClient.MyNamedClient.ClientHandler
protokolliert. Dies findet für die Anforderung statt, nachdem alle anderen Handler ausgeführt wurden und bevor die Anforderung an das Netzwerk gesendet wird. Diese Protokollierung enthält den Zustand der Antwort in der Antwort, bevor sie an die Handlerpipeline zurückgegeben wird.
Das Aktivieren der Protokollierung außerhalb und innerhalb der Pipeline ermöglicht die Überprüfung der Änderungen, die durch andere Handler in der Pipeline erfolgt sind. Dies kann beispielsweise die Änderungen an Anforderungsheadern oder am Statuscode der Antwort enthalten.
Das Einschließen des Namens des Clients in der Protokollkategorie aktiviert bei Bedarf die Protokollfilterung für spezifische benannte Clients.
Konfigurieren von HttpMessageHandler
Es kann notwendig sein, die Konfiguration des inneren von einem Client verwendeten HttpMessageHandler
zu steuern.
IHttpClientBuilder
wird zurückgegeben, wenn benannte oder typisierte Clients hinzugefügt werden. Die Erweiterungsmethode ConfigurePrimaryHttpMessageHandler kann zum Definieren eines Delegaten verwendet werden. Der Delegat wird verwendet, um den primären HttpMessageHandler
zu erstellen und konfigurieren, der von dem Client verwendet wird:
services.AddHttpClient("configured-inner-handler")
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler()
{
AllowAutoRedirect = false,
UseDefaultCredentials = true
};
});
Verwenden von IHttpClientFactory in einer Konsolen-App
Fügen Sie in einer Konsolen-App dem Projekt die folgenden Paketverweise hinzu:
Im folgenden Beispiel:
- IHttpClientFactory ist im Dienstcontainer des generischen Hosts registriert.
MyService
erstellt eine Clientfactoryinstanz aus dem-Dienst, der zum Erstellen einesHttpClient
verwendet wird.HttpClient
wird zum Abrufen einer Webseite verwendet.- Die Methode
GetPage
des Diensts wird ausgeführt, um die ersten 500 Zeichen des Webseiteninhalts in die Konsole zu schreiben. Weitere Informationen zum Aufrufen von Diensten überProgram.Main
finden Sie unter Abhängigkeitsinjektion in 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}";
}
}
}
}
Middleware für Headerweitergabe
Für die Headerweitergabe wird eine von der Community unterstützte Middleware verwendet, um HTTP-Header von eingehenden Anforderungen an ausgehende HTTP-Clientanforderungen weiterzugeben. So verwenden Sie die Headerweitergabe:
Erstellen Sie einen Verweis auf das von der Community unterstützte HeaderPropagation-Paket. ASp.NET Core 3.1 und höher unterstützen Microsoft.AspNetCore.HeaderPropagation.
Konfigurieren Sie in
Startup
die Middleware undHttpClient
: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(); }
Der Client schließt die konfigurierten Header für ausgehende Anforderungen ein:
var client = clientFactory.CreateClient("MyForwardingClient"); var response = client.GetAsync(...);