Verwenden von IHttpClientFactory zur Implementierung robuster HTTP-AnforderungenUse IHttpClientFactory to implement resilient HTTP requests

IHttpClientFactory ist ein Vertrag, der von DefaultHttpClientFactory, einer Factory, die seit .NET Core 2.1 für das Erstellen von HttpClient-Instanzen verfügbar ist, die in Ihren Anwendungen verwendet werden sollen.IHttpClientFactory is a contract implemented by DefaultHttpClientFactory, an opinionated factory, available since .NET Core 2.1, for creating HttpClient instances to be used in your applications.

Probleme mit den ursprünglichen HttpClient-Klassen von .NETIssues with the original HttpClient class available in .NET

Die ursprüngliche und bekannte HttpClient-Klasse kann problemlos verwendet werden, allerdings wird sie von vielen Entwicklern in einigen Fällen nicht richtig verwendet.The original and well-known HttpClient class can be easily used, but in some cases, it isn't being properly used by many developers.

Obwohl diese Klasse IDisposable implementiert, wird die Deklaration und Instanziierung innerhalb einer using-Anweisung nicht bevorzugt, da beim Verwerfen des HttpClient-Objekts der zugrunde liegende Socket nicht sofort freigegeben wird, was zur Erschöpfung von Sockets führen kann.Though this class implements IDisposable, declaring and instantiating it within a using statement is not preferred because when the HttpClient object gets disposed of, the underlying socket is not immediately released, which can lead to a socket exhaustion problem. Weitere Informationen zu diesem Problem finden Sie im Blogbeitrag You're using HttpClient wrong and it is destabilizing your software (Sie verwenden HttpClient falsch und destabilisieren dadurch Ihre Software).For more information about this issue, see the blog post You're using HttpClient wrong and it's destabilizing your software.

Deshalb sollte HttpClient einmal instanziiert und während der Lebensdauer einer Anwendung wiederverwendet werden.Therefore, HttpClient is intended to be instantiated once and reused throughout the life of an application. Das Instanziieren einer HttpClient-Klasse für jede Anforderung erschöpft die Anzahl der verfügbaren Sockets und führt zu hoher Auslastung.Instantiating an HttpClient class for every request will exhaust the number of sockets available under heavy loads. Dieses Problem führt zu SocketException-Fehlern.That issue will result in SocketException errors. Mögliche Ansätze zur Lösung dieses Problems basieren auf der Erstellung des HttpClient-Objekts als Singleton-Objekt oder statisches Objekt. Dies wird in diesem Microsoft-Artikel zur Verwendung von HttpClient erläutert.Possible approaches to solve that problem are based on the creation of the HttpClient object as singleton or static, as explained in this Microsoft article on HttpClient usage. Das kann eine gute Lösung für kurzlebige Konsolen-Apps o. ä. sein, die mehrmals am Tag ausgeführt werden.This can be a good solution for short-lived console apps or similar, that run a few times a day.

Ein weiteres Problem, auf das Entwickler stoßen, ist die Verwendung einer gemeinsam genutzten Instanz von HttpClient in zeitintensiven Prozessen.Another issue that developers run into is when using a shared instance of HttpClient in long-running processes. In einer Situation, in der der HttpClient als Singleton oder statisches Objekt instanziiert wird, kann er die DNS-Änderungen nicht wie in dieser Ausgabe des GitHub-Repositorys „dotnet/runtime“ beschrieben behandeln.In a situation where the HttpClient is instantiated as a singleton or a static object, it fails to handle the DNS changes as described in this issue of the dotnet/runtime GitHub repository.

Es geht jedoch nicht wirklich um HttpClient an sich, sondern um den Standardkonstruktor für HttpClient, da er eine neue konkrete Instanz von HttpMessageHandler erstellt, die die oben erwähnten Probleme mit der Erschöpfung der Sockets und den DNS-Änderungen aufweist.However, the issue isn't really with HttpClient per se, but with the default constructor for HttpClient, because it creates a new concrete instance of HttpMessageHandler, which is the one that has sockets exhaustion and DNS changes issues mentioned above.

Um die oben genannten Probleme zu lösen und HttpClient-Instanzen verwaltbar zu gestalten, wurde in .NET Core 2.1 die IHttpClientFactory-Schnittstelle eingeführt, die zur Konfiguration und Erstellung von HttpClient-Instanzen in einer App durch Abhängigkeitsinjektion verwendet werden kann.To address the issues mentioned above and to make HttpClient instances manageable, .NET Core 2.1 introduced the IHttpClientFactory interface which can be used to configure and create HttpClient instances in an app through Dependency Injection (DI). Sie bietet auch Erweiterungen für auf Polly basierende Middleware, um die Vorteile der delegierenden Handler in HttpClient zu nutzen.It also provides extensions for Polly-based middleware to take advantage of delegating handlers in HttpClient.

Polly ist eine Bibliothek zur Behandlung vorübergehender Fehler, mit der Entwickler ihren Anwendungen Resilienz hinzufügen können, indem sie einige vordefinierte Richtlinien auf fließende und threadsichere Weise verwenden.Polly is a transient-fault-handling library that helps developers add resiliency to their applications, by using some pre-defined policies in a fluent and thread-safe manner.

Vorteile der Verwendung von IHttpClientFactoryBenefits of using IHttpClientFactory

Die derzeitige Implementierung von IHttpClientFactory, die auch IHttpMessageHandlerFactory implementiert, bietet die folgenden Vorteile:The current implementation of IHttpClientFactory, that also implements IHttpMessageHandlerFactory, offers the following benefits:

  • Bereitstellen eines zentralen Orts für das Benennen und Konfigurieren logischer HttpClient-Objekte.Provides a central location for naming and configuring logical HttpClient objects. Sie können beispielsweise einen Client (Dienst-Agent) konfigurieren, der für das Zugreifen auf einen bestimmten Microservice vorkonfiguriert ist.For example, you may configure a client (Service Agent) that's pre-configured to access a specific microservice.
  • Für das Umsetzen des Konzepts der ausgehenden Middleware über delegierende Handler in HttpClient in Code sowie für das Implementieren von Polly-basierter Middleware, um die Polly-Richtlinien für die Resilienz zu nutzen.Codify the concept of outgoing middleware via delegating handlers in HttpClient and implementing Polly-based middleware to take advantage of Polly's policies for resiliency.
  • HttpClient enthält bereits das Konzept, Handler zu delegieren, die für ausgehende HTTP-Anforderungen miteinander verknüpft werden könnten.HttpClient already has the concept of delegating handlers that could be linked together for outgoing HTTP requests. Sie können HTTP-Clients in der Factory registrieren und einen Polly-Handler verwenden, um Polly-Richtlinien für Wiederholungen, CircuitBreakers usw. verwenden zu können.You can register HTTP clients into the factory and you can use a Polly handler to use Polly policies for Retry, CircuitBreakers, and so on.
  • Für das Verwalten der Lebensdauer von HttpMessageHandler-Meldungshandlern, um die erwähnten Probleme zu vermeiden, die auftreten können, wenn Sie die HttpClient-Lebensdauer selbst verwalten.Manage the lifetime of HttpMessageHandler to avoid the mentioned problems/issues that can occur when managing HttpClient lifetimes yourself.

Tipp

Die von der Abhängigkeitsinjektion eingefügten HttpClient-Instanzen können sicher verworfen werden, da das zugehörige HttpMessageHandler von der Factory verwaltet wird.The HttpClient instances injected by DI, can be disposed of safely, because the associated HttpMessageHandler is managed by the factory. Tatsächlich werden eingefügte HttpClient-Instanzen aus Sicht der Abhängigkeitsinjektion auf einen Bereich bezogen.As a matter of fact, injected HttpClient instances are Scoped from a DI perspective.

Hinweis

Die Implementierung von IHttpClientFactory (DefaultHttpClientFactory) ist eng an die Implementierung der Abhängigkeitsinjektion im NuGet-Paket Microsoft.Extensions.DependencyInjection gebunden.The implementation of IHttpClientFactory (DefaultHttpClientFactory) is tightly tied to the DI implementation in the Microsoft.Extensions.DependencyInjection NuGet package. Weitere Informationen zur Verwendung anderer Container für die Abhängigkeitsinjektion finden Sie in dieser GitHub-Diskussion.For more information about using other DI containers, see this GitHub discussion.

Mehrere Verwendungsmöglichkeiten für IHttpClientFactoryMultiple ways to use IHttpClientFactory

Es gibt mehrere Methoden, um IHttpClientFactory in Ihrer Anwendung zu verwenden:There are several ways that you can use IHttpClientFactory in your application:

  • Grundlegende VerwendungBasic usage
  • Verwenden Sie benannte Clients.Use Named Clients
  • Verwenden Sie typisierte Clients.Use Typed Clients
  • Verwenden Sie generierte Clients.Use Generated Clients

Aus Gründen der Übersichtlichkeit zeigt diese Anleitung die strukturierteste Art der Verwendung von IHttpClientFactory, also die Verwendung von typisierten Clients (Dienst-Agent-Muster).For the sake of brevity, this guidance shows the most structured way to use IHttpClientFactory, which is to use Typed Clients (Service Agent pattern). Allerdings sind alle Optionen dokumentiert und werden zurzeit in diesem Artikel zur Verwendung von IHttpClientFactory aufgeführt.However, all options are documented and are currently listed in this article covering the IHttpClientFactory usage.

Verwenden von typisierten Clients mit IHttpClientFactoryHow to use Typed Clients with IHttpClientFactory

Was ist ein „typisierter Client“?So, what's a "Typed Client"? Es ist nur ein HttpClient, der für eine bestimmte Verwendung vorkonfiguriert ist.It's just an HttpClient that's pre-configured for some specific use. Diese Konfiguration kann bestimmte Werte wie den Basisserver, HTTP-Header oder Timeouts enthalten.This configuration can include specific values such as the base server, HTTP headers or time outs.

Im folgenden Diagramm wird veranschaulicht, wie typisierte Clients mit IHttpClientFactory verwendet werden:The following diagram shows how Typed Clients are used with IHttpClientFactory:

Diagramm, das zeigt, wie typisierte Clients mit IHttpClientFactory verwendet werden.

Abbildung 8-4.Figure 8-4. Verwenden von IHttpClientFactory mit typisierten Clientklassen.Using IHttpClientFactory with Typed Client classes.

In der obigen Abbildung verwendet ein (von einem Controller oder Clientcode verwendeter) ClientService einen HttpClient, der von der registrierten IHttpClientFactory erstellt wird.In the above image, a ClientService (used by a controller or client code) uses an HttpClient created by the registered IHttpClientFactory. Diese Factory weist einen HttpMessageHandler aus einem Pool dem HttpClient zu.This factory assigns an HttpMessageHandler from a pool to the HttpClient. Der HttpClient kann bei Registrierung der IHttpClientFactory im DI-Container mit der Erweiterungsmethode AddHttpClient mit Pollys Richtlinien konfiguriert werden.The HttpClient can be configured with Polly's policies when registering the IHttpClientFactory in the DI container with the extension method AddHttpClient.

Um die obige Struktur zu konfigurieren, fügen Sie IHttpClientFactory in Ihrer Anwendung hinzu, indem Sie das NuGet-Paket Microsoft.Extensions.Http installieren, das die AddHttpClient-Erweiterungsmethode für IServiceCollection beinhaltet.To configure the above structure, add IHttpClientFactory in your application by installing the Microsoft.Extensions.Http NuGet package that includes the AddHttpClient extension method for IServiceCollection. Diese Erweiterungsmethode registriert die interne DefaultHttpClientFactory-Klasse für die Verwendung als Singleton für die Schnittstelle IHttpClientFactory.This extension method registers the internal DefaultHttpClientFactory class to be used as a singleton for the interface IHttpClientFactory. Dadurch wird eine temporäre Konfiguration für HttpMessageHandlerBuilder definiert.It defines a transient configuration for the HttpMessageHandlerBuilder. Dieser Meldungshandler (HttpMessageHandler-Objekt), der aus einem Pool abgerufen wurde, wird von dem HttpClient-Objekt verwendet, das von der Factory zurückgegebenen wird.This message handler (HttpMessageHandler object), taken from a pool, is used by the HttpClient returned from the factory.

Im nächsten Codeausschnitt wird veranschaulicht, wie AddHttpClient() verwendet werden kann, um typisierte Clients (Dienst-Agents) zu registrieren, die HttpClient verwenden müssen.In the next code, you can see how AddHttpClient() can be used to register Typed Clients (Service Agents) that need to use HttpClient.

// Startup.cs
//Add http client services at ConfigureServices(IServiceCollection services)
services.AddHttpClient<ICatalogService, CatalogService>();
services.AddHttpClient<IBasketService, BasketService>();
services.AddHttpClient<IOrderingService, OrderingService>();

Wenn Sie die Clientdienste wie im vorherigen Code gezeigt registrieren, erstellt DefaultClientFactory einen Standard-HttpClient für jeden Dienst.Registering the client services as shown in the previous code, makes the DefaultClientFactory create a standard HttpClient for each service.

Sie können auch eine instanzspezifische Konfiguration in der Registrierung hinzufügen (beispielsweise die Basisadresse konfigurieren und einige Resilienzrichtlinien hinzufügen). Der folgende Code zeigt dies:You could also add instance-specific configuration in the registration to, for example, configure the base address, and add some resiliency policies, as shown in the following code:

services.AddHttpClient<ICatalogService, CatalogService>(client =>
{
    client.BaseAddress = new Uri(Configuration["BaseUrl"]);
})
    .AddPolicyHandler(GetRetryPolicy())
    .AddPolicyHandler(GetCircuitBreakerPolicy());

Als Beispiel wird eine der oben aufgeführten Richtlinien im nächsten Code gezeigt:Just for the example sake, you can see one of the above policies in the next code:

static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
{
    return HttpPolicyExtensions
        .HandleTransientHttpError()
        .OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.NotFound)
        .WaitAndRetryAsync(6, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
}

Weitere Informationen zum Verwenden von Polly finden Sie im nächsten Artikel.You can find more details about using Polly in the Next article.

HTTPClient-LebensdauerHttpClient lifetimes

Jedes Mal, wenn Sie ein HttpClient-Objekt von der IHttpClientFactory abrufen, wird eine neue Instanz zurückgegeben.Each time you get an HttpClient object from the IHttpClientFactory, a new instance is returned. Aber jeder HttpClient verwendet einen HttpMessageHandler, der zu einem Pool gehört und von der IHttpClientFactory wiederverwendet wird, um den Ressourcenverbrauch zu reduzieren, solange die Lebensdauer des HttpMessageHandler nicht abgelaufen ist.But each HttpClient uses an HttpMessageHandler that's pooled and reused by the IHttpClientFactory to reduce resource consumption, as long as the HttpMessageHandler's lifetime hasn't expired.

Das Zusammenlegen von Handlern ist wünschenswert, da jeder Handler in der Regel über seine eigenen HTTP-Verbindungen verfügt. Das Erstellen von mehr Handlern als notwendig kann zu Verzögerungen bei der Verbindung führen.Pooling of handlers is desirable as each handler typically manages its own underlying HTTP connections; creating more handlers than necessary can result in connection delays. Einige Handler halten Verbindungen auch unbegrenzt offen, was verhindert, dass der Handler auf DNS-Änderungen reagiert.Some handlers also keep connections open indefinitely, which can prevent the handler from reacting to DNS changes.

Die HttpMessageHandler-Objekte im Pool haben eine Lebensdauer, die der Zeitspanne entspricht, in der eine HttpMessageHandler-Instanz im Pool wiederverwendet werden kann.The HttpMessageHandler objects in the pool have a lifetime that's the length of time that an HttpMessageHandler instance in the pool can be reused. Der Standardwert beträgt zwei Minuten, kann jedoch für jeden typisierten Client überschrieben werden.The default value is two minutes, but it can be overridden per Typed Client. Rufen Sie SetHandlerLifetime() auf dem bei der Erstellung des Clients zurückgegebenen IHttpClientBuilder auf, um den Wert zu überschreiben, wie im folgenden Code gezeigt:To override it, call SetHandlerLifetime() on the IHttpClientBuilder that's returned when creating the client, as shown in the following code:

//Set 5 min as the lifetime for the HttpMessageHandler objects in the pool used for the Catalog Typed Client
services.AddHttpClient<ICatalogService, CatalogService>()
    .SetHandlerLifetime(TimeSpan.FromMinutes(5));

Für jeden typisierten Client kann ein eigener Wert für die Lebensdauer des Handlers konfiguriert werden.Each Typed Client can have its own configured handler lifetime value. Legen Sie die Lebensdauer auf InfiniteTimeSpan fest, um das Ablaufen des Handlers zu deaktivieren.Set the lifetime to InfiniteTimeSpan to disable handler expiry.

Implementieren von typisierten Clientklassen, die das injizierte und konfigurierte HttpClient-Objekt verwendenImplement your Typed Client classes that use the injected and configured HttpClient

Zuvor müssen Sie Ihre typisierten Clientklassen definieren, z. B. die Klassen im Beispielcode („BasketService“, „CatalogService“, „OrderingService“ usw.). Bei einem typisierten Client handelt es sich um eine Klasse, die ein HttpClient-Objekt akzeptiert (das über deren Konstruktor injiziert wird) und dieses verwendet, um HTTP-Remotedienste aufzurufen.As a previous step, you need to have your Typed Client classes defined, such as the classes in the sample code, like 'BasketService', 'CatalogService', 'OrderingService', etc. – A Typed Client is a class that accepts an HttpClient object (injected through its constructor) and uses it to call some remote HTTP service. Zum Beispiel:For example:

public class CatalogService : ICatalogService
{
    private readonly HttpClient _httpClient;
    private readonly string _remoteServiceBaseUrl;

    public CatalogService(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }

    public async Task<Catalog> GetCatalogItems(int page, int take,
                                               int? brand, int? type)
    {
        var uri = API.Catalog.GetAllCatalogItems(_remoteServiceBaseUrl,
                                                 page, take, brand, type);

        var responseString = await _httpClient.GetStringAsync(uri);

        var catalog = JsonConvert.DeserializeObject<Catalog>(responseString);
        return catalog;
    }
}

Der typisierte Client (CatalogService im Beispiel) wird von der Abhängigkeitsinjektion aktiviert. Das bedeutet, dass dieser alle registrierten Dienste (zusätzlich zu HttpClient) im Konstruktor akzeptieren kann.The Typed Client (CatalogService in the example) is activated by DI (Dependency Injection), that means it can accept any registered service in its constructor, in addition to HttpClient.

Ein typisierter Client ist ein temporäres Objekt. Das bedeutet, dass immer dann eine neue Instanz erstellt wird, wenn eine benötigt wird.A Typed Client is effectively a transient object, that means a new instance is created each time one is needed. Bei jeder Konstruktion wird also eine neue HttpClient-Instanz empfangen.It receives a new HttpClient instance each time it's constructed. Die HttpMessageHandler-Objekte im Pool sind jedoch die Objekte, die von mehreren HttpClient-Instanzen wiederverwendet werden.However, the HttpMessageHandler objects in the pool are the objects that are reused by multiple HttpClient instances.

Verwenden von typisierten ClientklassenUse your Typed Client classes

Nachdem Sie Ihre typisierten Klassen implementiert haben, können Sie sie registrieren und mit AddHttpClient() konfigurieren.Finally, once you have your typed classes implemented, you can have them registered and configured with AddHttpClient(). Anschließend können Sie sie überall dort verwenden, wo Dienste mithilfe von DI eingefügt werden.After that you can use them wherever have services injected by DI. Beispielsweise in Code einer Razor Page-App oder Controllern einer MVC-Web-App, wie im folgenden Code aus eShopOnContainers:For example, in a Razor page code or controller of an MVC web app, like in the following code from eShopOnContainers:

namespace Microsoft.eShopOnContainers.WebMVC.Controllers
{
    public class CatalogController : Controller
    {
        private ICatalogService _catalogSvc;

        public CatalogController(ICatalogService catalogSvc) =>
                                                           _catalogSvc = catalogSvc;

        public async Task<IActionResult> Index(int? BrandFilterApplied,
                                               int? TypesFilterApplied,
                                               int? page,
                                               [FromQuery]string errorMsg)
        {
            var itemsPage = 10;
            var catalog = await _catalogSvc.GetCatalogItems(page ?? 0,
                                                            itemsPage,
                                                            BrandFilterApplied,
                                                            TypesFilterApplied);
            //… Additional code
        }

        }
}

Bis zu diesem Punkt hat der obige Codeausschnitt nur das Beispiel zum Ausführen regulärer HTTP-Anforderungen gezeigt.Up to this point, the above code snippet has only shown the example of performing regular HTTP requests. In den folgenden Abschnitten wird jedoch gezeigt, wie alle HTTP-Anforderungen von HttpClient mit resilienten Richtlinien wie Wiederholungen mit exponentiellem Backoff, Circuit Breakern, Sicherheitsfeatures mit Authentifizierungstokens oder beliebigen benutzerdefinierten Features kombiniert werden können.But the 'magic' comes in the following sections where it shows how all the HTTP requests made by HttpClient, can have resilient policies such as retries with exponential backoff, circuit breakers, security features using auth tokens, or even any other custom feature. Hierfür müssen Sie nur Richtlinien hinzufügen und den registrierten typisierten Clients Handler zuweisen.And all of these can be done just by adding policies and delegating handlers to your registered Typed Clients.

Zusätzliche RessourcenAdditional resources