{"id":637,"date":"2025-04-05T11:44:15","date_gmt":"2025-04-05T03:44:15","guid":{"rendered":"https:\/\/www.hyy.net\/?p=637"},"modified":"2025-04-05T11:44:15","modified_gmt":"2025-04-05T03:44:15","slug":"asp-net-core-in-action-33-calling-remote-apis-with-ihttpclientfactory","status":"publish","type":"post","link":"https:\/\/diji.net\/?p=637","title":{"rendered":"ASP.NET Core in Action 33 Calling remote APIs with IHttpClientFactory"},"content":{"rendered":"<p>33 Calling remote APIs with IHttpClientFactory\u200c<\/p>\n<p>This chapter covers<br \/>\n\u2022  Seeing problems caused by using HttpClient incorrectly to call HTTP APIs<\/p>\n<p>\u2022  Using IHttpClientFactory to manage HttpClient lifetimes Encapsulating con\ufb01guration and handling transient errors with IHttpClientFactory<\/p>\n<p>So far in this book we\u2019ve focused on creating web pages and exposing APIs. Whether that\u2019s customers browsing a Razor Pages application or client-side SPAs and mobile apps consuming your APIs, we\u2019ve been writing the APIs for others to consume.<\/p>\n<p>However, it\u2019s common for your application to interact with third-party services by consuming their APIs as well as your own API apps. For example, an e-commerce site needs to take payments, send email and Short Message Service (SMS) messages, and retrieve exchange rates from a third-party service. The most common approach for interacting with services is using HTTP. So far in this book we\u2019ve looked at how you can expose HTTP services, using minimal APIs and API controllers, but we haven\u2019t looked at how you can consume HTTP services.<\/p>\n<p>In section 33.1 you\u2019ll learn the best way to interact with HTTP services using HttpClient. If you have any experience with C#, it\u2019s likely that you\u2019ve used this class to send HTTP requests, but there are two gotchas to think about; otherwise, your app could run into di\ufb03culties.<\/p>\n<p>IHttpClientFactory was introduced in .NET Core 2.1; it makes creating and managing HttpClient instances easier and avoids the common pitfalls. In section 33.2 you\u2019ll learn how IHttpClientFactory achieves this by managing the HttpClient handler pipeline. You\u2019ll learn how to create named clients to centralize the con\ufb01guration for calling remote APIs and how to use typed clients to encapsulate the remote service\u2019s behavior.\u200c<\/p>\n<p>Network glitches are a fact of life when you\u2019re working with HTTP APIs, so it\u2019s important for you to handle them gracefully. In section 33.3 you\u2019ll learn how to use the open- source resilience and fault-tolerance library Polly to handle common transient errors using simple retries, with the possibility for more complex policies.<\/p>\n<p>Finally, in section 33.4 you\u2019ll see how you can create your own custom HttpMessageHandler handlers managed by IHttpClientFactory. You can use custom handlers to implement cross-cutting concerns such as logging, metrics, and authentication, whenever a function needs to execute every time you call an HTTP API. You\u2019ll also see how to create a handler that automatically adds an API key to all outgoing requests to an API.<\/p>\n<p>To misquote John Donne, no app is an island, and the most common way of interacting with other apps and services is over HTTP. In .NET, that means using HttpClient.<\/p>\n<h2>33.1 Calling HTTP APIs: The problem with HttpClient\u200c<\/h2>\n<p>In this section you\u2019ll learn how to use HttpClient to call HTTP APIs. I\u2019ll focus on two common pitfalls in using HttpClient\u2014socket exhaustion and DNS rotation problems \u2014and show why they occur. In section 33.2 you\u2019ll see how to avoid these problems by using IHttpClientFactory.<\/p>\n<p>It\u2019s common for an application to interact with other services to ful\ufb01ll its duty. Take a typical e-commerce store, for example. In even the most basic version of the application, you will likely need to send emails and take payments using credit cards or other services. You could try to build that functionality yourself, but it probably wouldn\u2019t be worth the e\ufb00ort.<\/p>\n<p>Instead, it makes far more sense to delegate those responsibilities to third-party services that specialize in that functionality. Whichever service you use, they will almost certainly expose an HTTP API for interacting with the service. For many services, that will be the only way.<\/p>\n<blockquote>\n<p>RESTful HTTP vs. gRPC vs. GraphQL<br \/>\nThere are many ways to interact with third-party services, but HTTP RESTful services are still the king, decades after HTTP was \ufb01rst proposed. Every platform and programming language you can think of includes support for making HTTP requests and handling responses. That ubiquity makes it the go-to option for most services.<\/p>\n<p>Despite their ubiquity, RESTful services are not perfect. They are relatively verbose, which means that more data ends up being sent and received than with some other protocols. It can also be di\ufb03cult to evolve RESTful APIs after you have deployed them. These limitations have spurred interest in two alternative protocols in particular: gRPC and GraphQL.<\/p>\n<p>gRPC is intended to be an e\ufb03cient mechanism for server-to-server communication. It builds on top of HTTP\/2 but typically provides much higher performance than traditional RESTful APIs. gRPC support was added in .NET Core 3.0 and is receiving many performance and feature updates. For a comprehensive view of .NET support, see the documentation at <a href=\"https:\/\/learn.microsoft.com\/aspnet\/core\/grpc\">https:\/\/learn.microsoft.com\/aspnet\/core\/grpc<\/a>.<\/p>\n<p>Whereas gRPC works best with server-to-server communication and nonbrowser clients, GraphQL is best used to provide evolvable APIs to mobile and single-page application (SPA) apps. It has become popular among frontend developers, as it can reduce the friction involved in deploying and using new APIs. For details, I recommend GraphQL in Action, by Samer Buna (Manning, 2021).\u200c\u200c<\/p>\n<p>Despite the bene\ufb01ts and improvements gRPC and GraphQL can bring, RESTful HTTP services are here to stay for the foreseeable future, so it\u2019s worth making sure that you understand how to use them with HttpClient.<\/p>\n<\/blockquote>\n<p>In .NET we use the HttpClient class for calling HTTP APIs. You can use it to make HTTP calls to APIs, providing all the headers and body to send in a request, and reading the response headers and data you get back. Unfortunately, it\u2019s hard to use correctly, and even when you do, it has limitations.<\/p>\n<p>The source of the di\ufb03culty with HttpClient stems partly from the fact that it implements the IDisposable interface. In general, when you use a class that implements IDisposable, you should wrap the class with a using statement whenever you create a new instance to ensure that unmanaged resources used by the type are cleaned up when the class is removed, as in this example:\u200c<\/p>\n<pre><code>using (var myInstance = new MyDisposableClass())\n{\n\/\/ use myInstance\n}<\/code><\/pre>\n<p><b>TIP<\/b>  C# also includes a simpli\ufb01ed version of the using statement called a using declaration, which omits the curly braces, as shown in listing 33.1. You can read more about the syntax at <a href=\"http:\/\/mng.bz\/nW12\">http:\/\/mng.bz\/nW12<\/a>.<\/p>\n<p>That might lead you to think that the correct way to create an HttpClient is shown in listing 33.1. This listing shows a simple example where a minimal API endpoint calls an external API to fetch the latest currency exchange rates, and returns them as the response.<\/p>\n<p><img decoding=\"async\" src=\"\/images\/aspnetcoreinaction\/3301.jpg\" alt=\"alt text\" \/><\/p>\n<p>Figure 33.1 To create a connection, a client selects a random port and connects to the HTTP server\u2019s port and IP address. The client can then send HTTP requests to the server.<\/p>\n<p><b>WARNING<\/b> Do not use HttpClient as it\u2019s shown in listing 33.1. Using it this way could cause your application to become unstable, as you\u2019ll see shortly.<\/p>\n<p>Listing 33.1 The incorrect way to use HttpClient<\/p>\n<pre><code>WebApplicationBuilder builder = WebApplication.CreateBuilder(args);\nWebApplication app = builder.Build();\napp.MapGet(&quot;\/&quot;, async () =&gt;\n{\nusing HttpClient client = new HttpClient(); \u2776\nclient.BaseAddress = new Uri(&quot;https:\/\/example.com\/rates\/&quot;); \u2777\nvar response = await client.GetAsync(&quot;latest&quot;); \u2778\nresponse.EnsureSuccessStatusCode(); \u2779\nreturn await response.Content.ReadAsStringAsync(); \u277a\n});\napp.Run();<\/code><\/pre>\n<p>\u2776 Wrapping the HttpClient in a using declaration means it is disposed at the end of the scope.<br \/>\n\u2777 Configures the base URL used to make requests using the HttpClient<br \/>\n\u2778 Makes a GET request to the exchange rates API<br \/>\n\u2779 Throws an exception if the request was not successful<br \/>\n\u277a Reads the result as a string and returns it from the action method<\/p>\n<p>HttpClient is special, and you shouldn\u2019t use it like this! The problem is due primarily to the way the underlying protocol implementation works. Whenever your computer needs to send a request to an HTTP server, you must create a connection between your computer and the server. To create a connection, your computer opens a port, which has a random number between 0 and 65,535, and connects to the HTTP server\u2019s IP address and port, as shown in \ufb01gure 33.1. Your computer can then send HTTP requests to the server.<\/p>\n<p><b>DEFINITION<\/b> The combination of IP address and port is called a socket.<\/p>\n<p>The main problem with the using statement\/declaration and HttpClient is that it can lead to a problem called socket exhaustion, illustrated in \ufb01gure 33.2. This happens when all the ports on your computer have been used up making other HTTP connections, so your computer can\u2019t make any more requests. At that point, your application will hang, waiting for a socket to become free\u2014a bad experience!\u200c<\/p>\n<p><img decoding=\"async\" src=\"\/images\/aspnetcoreinaction\/3302.jpg\" alt=\"alt text\" \/><\/p>\n<p>Figure 33.2 Disposing of HttpClient can lead to socket exhaustion. Each new connection requires the operating system to assign a new socket, and closing a socket doesn\u2019t make it available until the TIME_WAIT period of 240 seconds has elapsed. Eventually you can run out of sockets, at which point you can\u2019t make any outgoing HTTP requests.<\/p>\n<p>Given that I said there are 65,536 di\ufb00erent port numbers, you might think that\u2019s an unlikely situation. It\u2019s true that you will likely run into this problem only on a server that is making a lot of connections, but it\u2019s not as rare as you might think.<\/p>\n<p>The problem is that when you dispose of an HttpClient, it doesn\u2019t close the socket immediately. The design of the TCP\/IP protocol used for HTTP requests means that after trying to close a connection, the connection moves to a state called TIME_WAIT. The connection then waits for a speci\ufb01c period (240 seconds in Windows) before closing the socket.<\/p>\n<p>Until the TIME_WAIT period has elapsed, you can\u2019t reuse the socket in another HttpClient to make HTTP requests. If you\u2019re making a lot of requests, that can quickly lead to socket exhaustion, as shown in \ufb01gure 33.2.<\/p>\n<p><b>TIP<\/b>  You can view the state of active ports\/sockets in Windows and Linux by running the command netstat from the command line or a terminal window. Be sure to run netstat -n in Windows to skip Domain Name System (DNS) resolution.<\/p>\n<p>Instead of disposing of HttpClient, the general advice (before the introduction of IHttpClientFactory) was to use a single instance of HttpClient, as shown in the following listing.<\/p>\n<p>Listing 33.2 Using a singleton HttpClient to avoid socket exhaustion<\/p>\n<pre><code>WebApplicationBuilder builder = WebApplication.CreateBuilder(args);\nWebApplication app = builder.Build();\nHttpClient client = new HttpClient \u2776\n{ \u2776\nBaseAddress = new Uri(&quot;https:\/\/example.com\/rates\/&quot;), \u2776\n}; \u2776\napp.MapGet(&quot;\/&quot;, async () =&gt;\n{\nvar response = await client.GetAsync(&quot;latest&quot;); \u2777\nresponse.EnsureSuccessStatusCode();\nreturn await response.Content.ReadAsStringAsync();\n});\napp.Run();<\/code><\/pre>\n<p>\u2776 A single instance of HttpClient is created for the lifetime of the app.<br \/>\n\u2777 Multiple requests use the same instance of HttpClient.<\/p>\n<p>This solves the problem of socket exhaustion. As you\u2019re not disposing of the HttpClient, the socket is not disposed of, so you can reuse the same port for multiple requests. No matter how many times you call the API in the preceding example, you will use only a single socket. Problem solved!<\/p>\n<p>Unfortunately, this introduces a di\ufb00erent problem, primarily related to DNS. DNS is how the friendly hostnames we use, such as manning.com, are converted to the Internet Protocol (IP) addresses that computers need. When a new connection is required, the HttpClient \ufb01rst checks the DNS record for a host to \ufb01nd the IP address and then makes the connection. For subsequent requests, the connection is already established, so it doesn\u2019t make another DNS call.<\/p>\n<p>For singleton HttpClient instances, this can be a problem because the HttpClient won\u2019t detect DNS changes. DNS is often used in cloud environments for load balancing to do graceful rollouts of deployments.1 If the DNS record of a service you\u2019re calling changes during the lifetime of your application, a singleton HttpClient will keep calling the old service, as shown in \ufb01gure 33.3.<\/p>\n<p><img decoding=\"async\" src=\"\/images\/aspnetcoreinaction\/3303.jpg\" alt=\"alt text\" \/><\/p>\n<p>Figure 33.3 HttpClient does a DNS lookup before establishing a connection to determine the IP address associated with a hostname. If the DNS record for a hostname changes, a singleton HttpClient will not detect it and will continue sending requests to the original server it connected to.<\/p>\n<p><b>NOTE<\/b>  HttpClient won\u2019t respect a DNS change while the original connection exists. If the original connection is closed (for example, if the original server goes o\ufb04ine), it will respect the DNS change, as it must establish a new connection.<\/p>\n<p>It seems that you\u2019re damned if you do and damned if you don\u2019t! Luckily, IHttpClientFactory can take care of all this for you.<\/p>\n<h2>33.2 Creating HttpClients with IHttpClientFactory\u200c<\/h2>\n<p>In this section you\u2019ll learn how you can use IHttpClientFactory to avoid the common pitfalls of HttpClient. I\u2019ll show several patterns you can use to create an HttpClient:<\/p>\n<p>\u2022  Using CreateClient() as a drop-in replacement for HttpClient<\/p>\n<p>\u2022  Using named clients to centralize the con\ufb01guration of an HttpClient used to call a speci\ufb01c third- party API<\/p>\n<p>\u2022  Using typed clients to encapsulate the interaction with a third-party API for easier consumption by your code<\/p>\n<p>IHttpClientFactory makes it easier to create HttpClient instances correctly instead of relying on either of the faulty approaches I discussed in section 33.1. It also makes it easier to con\ufb01gure multiple HttpClients and allows you to create a middleware pipeline for outgoing requests.<\/p>\n<p>Before we look at how IHttpClientFactory achieves all that, we will look at how HttpClient works under the hood.<\/p>\n<h3>33.2.1 Using IHttpClientFactory to manage HttpClientHandler lifetime\u200c<\/h3>\n<p>In this section we\u2019ll look at the handler pipeline used by HttpClient. You\u2019ll see how IHttpClientFactory manages the lifetime of this pipeline and how this enables the factory to avoid both socket exhaustion and DNS problems.<\/p>\n<p>The HttpClient class you typically use to make HTTP requests is responsible for orchestrating requests, but it isn\u2019t responsible for making the raw connection itself. Instead, the HttpClient calls into a pipeline of HttpMessageHandler, at the end of which is an HttpClientHandler, which makes the actual connection and sends the HTTP request, as shown in \ufb01gure 33.4.<\/p>\n<p><img decoding=\"async\" src=\"\/images\/aspnetcoreinaction\/3304.jpg\" alt=\"alt text\" \/><\/p>\n<p>Figure 33.4 Each HttpClient contains a pipeline of HttpMessageHandlers. The \ufb01nal handler is an HttpClientHandler, which makes the connection to the remote server and sends the HTTP request. This con\ufb01guration is similar to the ASP.NET Core middleware pipeline, and it allows you to make cross- cutting adjustments to outgoing requests.<\/p>\n<p>This con\ufb01guration is reminiscent of the middleware pipeline used by ASP.NET Core applications, but this is an outbound pipeline. When an HttpClient makes a request, each handler gets a chance to modify the request before the \ufb01nal HttpClientHandler makes the real HTTP request. Each handler in turn then gets a chance to view the response after it\u2019s received.<\/p>\n<p><b>TIP<\/b>  You\u2019ll see an example of using this handler pipeline for cross-cutting concerns in section 33.3 when we add a transient error handler.<\/p>\n<p>The problems of socket exhaustion and DNS I described in section 33.1 are related to the disposal of the HttpClientHandler at the end of the handler pipeline. By default, when you dispose of an HttpClient, you dispose of the handler pipeline too. IHttpClientFactory separates the lifetime of the HttpClient from the underlying HttpClientHandler.<\/p>\n<p>Separating the lifetime of these two components enables the IHttpClientFactory to solve the problems of socket exhaustion and DNS rotation. It achieves this in two ways:<\/p>\n<p>\u2022  By creating a pool of available handlers\u2014Socket exhaustion occurs when you dispose of an HttpClientHandler, due to the TIME_WAIT problem described previously.<\/p>\n<p>\u2022  IHttpClientFactory solves this by creating a pool of handlers.<\/p>\n<p>IHttpClientFactory maintains an active handler that it uses to create all HttpClients for two minutes. When the HttpClient is disposed of, the underlying handler isn\u2019t disposed of, so the connection isn\u2019t closed. As a result, socket exhaustion isn\u2019t a problem.<\/p>\n<p>\u2022  By periodically disposing of handlers\u2014Sharing handler pipelines solves the socket exhaustion problem, but it doesn\u2019t solve the DNS problem. To work around this, the IHttpClientFactory periodically (every two minutes) creates a new active HttpClientHandler that it uses for each HttpClient created subsequently. As these HttpClients are using a new handler, they make a new TCP\/IP connection, so DNS changes are respected.<\/p>\n<p>IHttpClientFactory disposes of expired handlers periodically in the background once they are no longer used by an HttpClient. This ensures that your application\u2019s HttpClients use a limited number of connections.<\/p>\n<p><b>TIP<\/b>  I wrote a blog post that looks in depth at how IHttpClientFactory achieves its handler rotation. This is a detailed post, but it may be of interest to those who like to know how things are implemented behind the scenes. See \u201cExploring the code behind IHttpClientFactory in depth\u201d at <a href=\"http:\/\/mng.bz\/8NRK\">http:\/\/mng.bz\/8NRK<\/a>.<\/p>\n<p>Rotating handlers with IHttpClientFactory solves both the problems we\u2019ve discussed. Another bonus is that it\u2019s easy to replace existing uses of HttpClient with IHttpClientFactory.<\/p>\n<p>IHttpClientFactory is included by default in ASP.NET Core. You simply add it to your application\u2019s services in Program.cs:<\/p>\n<pre><code>builder.Services.AddHttpClient();<\/code><\/pre>\n<p>This registers the IHttpClientFactory as a singleton in your application, so you can inject it into any other service. The following listing shows how you can replace the HttpClient approach from listing 33.2 with a version that uses IHttpClientFactory.<\/p>\n<p>Listing 33.3 Using IHttpClientFactory to create an HttpClient<\/p>\n<pre><code>WebApplicationBuilder builder = WebApplication.CreateBuilder(args);\nbuilder.Services.AddHttpClient(); \u2776\nWebApplication app = builder.Build();\napp.MapGet(&quot;\/&quot;, async (IHttpClientFactory factory) =&gt; \u2777\n{\nHttpClient client = factory.CreateClient(); \u2778\nclient.BaseAddress = \u2779\nnew Uri(&quot;https:\/\/example.com\/rates\/&quot;); \u2779\nvar response = await client.GetAsync(&quot;latest&quot;); \u277a\nresponse.EnsureSuccessStatusCode(); \u277a\nreturn await response.Content.ReadAsStringAsync(); \u277a\n});\napp.Run();<\/code><\/pre>\n<p>\u2776 Registers the IHttpClientFactory service in DI<br \/>\n\u2777 Injects the IHttpClientFactory using DI<br \/>\n\u2778 Creates an HttpClient instance with an HttpClientHandler managed by the factory<br \/>\n\u2779 Configures the HttpClient for calling the API as before<br \/>\n\u277a Uses the HttpClient in exactly the same way you would otherwise<\/p>\n<p>The immediate bene\ufb01t of using IHttpClientFactory in this way is e\ufb03cient socket and DNS handling. When you create an HttpClient using CreateClient(), IHttpClientFactory uses a pooled HttpClientHandler to create a new instance of an HttpClient, pooling and disposing the handlers as necessary to \ufb01nd a balance between the tradeo\ufb00s described in section 33.1.<\/p>\n<p>Minimal changes should be required to take advantage of this pattern, as the bulk of your code stays the same. Only the code where you\u2019re creating an HttpClient instance changes. This makes it a good option if you\u2019re refactoring an existing app.<\/p>\n<blockquote>\n<p>SocketsHttpHandler vs. IHttpClientFactory<\/p>\n<p>The limitations of HttpClient described in section 33.1 apply speci\ufb01cally to the HttpClientHandler at the end of the HttpClient handler pipeline in older versions of .NET Core. IHttpClientFactory provides a mechanism for managing the lifetime and reuse of HttpClientHandler instances.\u200c<\/p>\n<p>From .NET 5 onward, the legacy HttpClientHandler has been replaced by SocketsHttpHandler. This handler has several advantages, most notably performance bene\ufb01ts and consistency across platforms. The SocketsHttpHandler can also be con\ufb01gured to use connection pooling and recycling, like IHttpClientFactory.<\/p>\n<p>So if HttpClient can already use connection pooling, is it worth using IHttpClientFactory? In most cases, I would say yes. You must manually con\ufb01gure connection pooling with SocketsHttpHandler, and IHttpClientFactory has additional features such as named clients and typed clients. In any situations where you\u2019re using dependency injection (DI), which is every ASP.NET Core app and most .NET 7 apps, I recommend using IHttpClientFactory to take advantage of these bene\ufb01ts.<\/p>\n<p>Nevertheless, if you\u2019re working in a non-DI scenario and can\u2019t use IHttpClientFactory, be sure to enable the SocketsHttpHandler connection pooling as described in this post by Steve Gordon, titled \u201cHttpClient connection pooling in .NET Core\u201d: <a href=\"http:\/\/mng.bz\/E27q\">http:\/\/mng.bz\/E27q<\/a>.<\/p>\n<\/blockquote>\n<p>Managing the socket problem is one big advantage of using IHttpClientFactory over HttpClient, but it\u2019s not the only bene\ufb01t. You can also use IHttpClientFactory to clean up the client con\ufb01guration, as you\u2019ll see in the next section.<\/p>\n<h3>33.2.2 Con\ufb01guring named clients at registration time\u200c<\/h3>\n<p>In this section you\u2019ll learn how to use the Named Client pattern with IHttpClientFactory. This pattern encapsulates the logic for calling a third-party API in a single location, making it easier to use the HttpClient in your consuming code.<\/p>\n<p><b>NOTE<\/b>  IHttpClientFactory uses the same HttpClient type you\u2019re familiar with if you\u2019re coming from .NET Framework. The big di\ufb00erence is that IHttpClientFactory solves the DNS and socket exhaustion problem by managing the underlying message handlers.<\/p>\n<p>Using IHttpClientFactory solves the technical problems I described in section 33.1, but the code in listing 33.3 is still pretty messy in my eyes, primarily because you must con\ufb01gure the HttpClient to point to your service before you use it. If you need to create an HttpClient to call the API in more than one place in your application, you must con\ufb01gure it in more than one place too.<\/p>\n<p>IHttpClientFactory provides a convenient solution to this problem by allowing you to centrally con\ufb01gure named clients, which have a string name and a con\ufb01guration function that runs whenever an instance of the named client is requested. You can de\ufb01ne multiple con\ufb01guration functions that run in sequence to con\ufb01gure your new HttpClient.<\/p>\n<p>The following listing shows how to register a named client called &quot;rates&quot;. This client is con\ufb01gured with the correct BaseAddress and sets default headers that are to be sent with each outbound request. Once you have con\ufb01gured this named client, you can create it from an IHttpClientFactory instance using the name of the client, &quot;rates&quot;.<\/p>\n<p>Listing 33.4 Using IHttpClientFactory to create a named HttpClient<\/p>\n<pre><code>WebApplicationBuilder builder = WebApplication.CreateBuilder(args);\nbuilder.Services.AddHttpClient(&quot;rates&quot;, (HttpClient client) =&gt; \u2776\n{\nclient.BaseAddress = \u2777\nnew Uri(&quot;https:\/\/example.com\/rates\/&quot;); \u2777\nclient.DefaultRequestHeaders.Add( \u2777\nHeaderNames.UserAgent, &quot;ExchangeRateViewer&quot;); \u2777\n})\n.ConfigureHttpClient((HttpClient client) =&gt; {}) \u2778\n.ConfigureHttpClient(\n(IServiceProvider provider, HttpClient client) =&gt; {}); \u2779\nWebApplication app = builder.Build();\napp.MapGet(&quot;\/&quot;, async (IHttpClientFactory factory) =&gt; \u277a\n{\nHttpClient client = factory.CreateClient(&quot;rates&quot;); \u277b\nvar response = await client.GetAsync(&quot;latest&quot;); \u277c\n\u277c\nresponse.EnsureSuccessStatusCode();\nreturn await response.Content.ReadAsStringAsync();\n});\napp.Run();<\/code><\/pre>\n<p>\u2776 Provides a name for the client and a configuration function<br \/>\n\u2777 The configuration function runs every time the named HttpClient is requested.<br \/>\n\u2778 You can add more configuration functions for the named client, which run in sequence.<br \/>\n\u2779 Additional overloads exist that allow access to the DI container when creating a named client.<br \/>\n\u277a Injects the IHttpClientFactory using DI<br \/>\n\u277b Requests the configured named client called \u201crates\u201d<br \/>\n\u277c Uses the HttpClient the same way as before<\/p>\n<p><b>NOTE<\/b>  You can still create uncon\ufb01gured clients using CreateClient() without a name. Be aware that if you pass an uncon\ufb01gured name, such as CreateClient (&quot;MyRates&quot;), the client returned will be uncon\ufb01gured. Take care\u2014client names are case-sensitive, so &quot;rates&quot; is a di\ufb00erent client from &quot;Rates&quot;.<\/p>\n<p>Named clients help centralize your HttpClient con\ufb01guration in one place, removing the responsibility for con\ufb01guring the client from your consuming code. But you\u2019re still working with raw HTTP calls at this point, such as providing the relative URL to call (&quot;\/latest&quot;) and parsing the response. IHttpClientFactory includes a feature that makes it easier to clean up this code.<\/p>\n<h3>33.2.3 Using typed clients to encapsulate HTTP calls\u200c<\/h3>\n<p>A common pattern when you need to interact with an API is to encapsulate the mechanics of that interaction in a separate service. You could easily do this with the IHttpClientFactory features you\u2019ve already seen by extracting the body of the GetRates() function from listing 33.4 into a separate service. But IHttpClientFactory has deeper support for this pattern.<\/p>\n<p>IHttpClientFactory supports typed clients. A typed client is a class that accepts a con\ufb01gured HttpClient in its constructor. It uses the HttpClient to interact with the remote API and exposes a clean interface for consumers to call. All the logic for interacting with the remote API is encapsulated in the typed client, such as which URL paths to call, which HTTP verbs to use, and the types of responses the API returns. This encapsulation makes it easier to call the third-party API from multiple places in your app by using the typed client.<\/p>\n<p>The following listing shows an example typed client for the exchange rates API shown in previous listings. It accepts an HttpClient in its constructor and exposes a GetLatestRates() method that encapsulates the logic for interacting with the third-party API.<\/p>\n<p>Listing 33.5 Creating a typed client for the exchange rates API<\/p>\n<pre><code>public class ExchangeRatesClient\n{\nprivate readonly HttpClient _client; \u2776\npublic ExchangeRatesClient(HttpClient client) \u2776\n{\n_client = client;\n}\npublic async Task&lt;string&gt; GetLatestRates() \u2777\n{\nvar response = await _client.GetAsync(&quot;latest&quot;); \u2778\nresponse.EnsureSuccessStatusCode(); \u2778\nreturn await response.Content.ReadAsStringAsync(); \u2778\n}\n}<\/code><\/pre>\n<p>\u2776 Injects an HttpClient using DI instead of an IHttpClientFactory<br \/>\n\u2777 The GetLatestRates() logic encapsulates the logic for interacting with the API.<br \/>\n\u2778 Uses the HttpClient the same way as before<\/p>\n<p>We can then inject this ExchangeRatesClient into consuming services, and they don\u2019t need to know anything about how to make HTTP requests to the remote service; they need only to interact with the typed client. We can update listing 33.3 to use the typed client as shown in the following listing, at which point the API endpoint method becomes trivial.<\/p>\n<p>Listing 33.6 Consuming a typed client to encapsulate calls to a remote HTTP server<\/p>\n<pre><code>app.MapGet(&quot;\/&quot;, async (ExchangeRatesClient ratesClient) =&gt; \u2776\nawait ratesClient.GetLatestRates());<\/code><\/pre>\n<p>\u2776 Injects the typed client using DI<br \/>\n\u2777 Calls the typed client\u2019s API. The typed client handles making the correct HTTP requests.<\/p>\n<p>You may be a little confused at this point. I haven\u2019t mentioned how IHttpClientFactory is involved yet!<\/p>\n<p>The ExchangeRatesClient takes an HttpClient in its constructor. IHttpClientFactory is responsible for creating the HttpClient, con\ufb01guring it to call the remote service and injecting it into a new instance of the typed client.<\/p>\n<p>You can register the ExchangeRatesClient as a typed client and con\ufb01gure the HttpClient that is injected in ConfigureServices, as shown in the following listing. This is similar to con\ufb01guring a named client, so you can register additional con\ufb01guration for the HttpClient that will be injected into the typed client.<\/p>\n<p>Listing 33.7 Registering a typed client with HttpClientFactory in Startup.cs<\/p>\n<pre><code>WebApplicationBuilder builder = WebApplication.CreateBuilder(args);\nbuilder.Services.AddHttpClient&lt;ExchangeRatesClient&gt; \u2776\n(HttpClient client) =&gt; \u2777\n{ \u2777\nclient.BaseAddress = \u2777\nnew Uri(&quot;https:\/\/example.com\/rates\/&quot;); \u2777\nclient.DefaultRequestHeaders.Add( \u2777\nHeaderNames.UserAgent, &quot;ExchangeRateViewer&quot;); \u2777\n})\n.ConfigureHttpClient((HttpClient client) =&gt; {}); \u2778\n}\nWebApplication app = builder.Build();\napp.MapGet(&quot;\/&quot;, async (ExchangeRatesClient ratesClient) =&gt;\nawait ratesClient.GetLatestRates());\napp.Run();<\/code><\/pre>\n<p>\u2776 Registers a typed client using the generic AddHttpClient method<br \/>\n\u2777 You can provide an additional configuration function for the HttpClient that will be injected.<br \/>\n\u2778 As for named clients, you can provide multiple configuration methods.<\/p>\n<p>Behind the scenes, the call to<br \/>\nAddHttpClient<ExchangeRatesClient> does several things:<\/p>\n<p>\u2022  Registers HttpClient as a transient service in DI. That means you can accept an HttpClient in the constructor of any service in your app and IHttpClientFactory will inject a default pooled instance, which has no additional con\ufb01guration.<\/p>\n<p>\u2022  Registers ExchangeRatesClient as a transient service in DI.<\/p>\n<p>\u2022  Controls the creation of ExchangeRatesClient so that whenever a new instance is required, a pooled HttpClient is con\ufb01gured as de\ufb01ned in the AddHttpClient<T> lambda method.<\/p>\n<p><b>TIP<\/b>  You can think of a typed client as a wrapper around a named client. I\u2019m a big fan of this approach, as it encapsulates all the logic for interacting with a remote service in one place. It also avoids the magic strings that you use with named clients, removing the possibility of typos.<\/p>\n<p>Another option when registering typed clients is to register an interface in addition to the implementation. This is often good practice, as it makes it much easier to test consuming code. If the typed client in listing 33.5 implemented the interface IExchangeRatesClient, you could register the interface and typed client implementation using<\/p>\n<pre><code>builder.Services.AddHttpClient&lt;IExchangeRatesClient, ExchangeRatesClient&gt;()<\/code><\/pre>\n<p>You could then inject this into consuming code using the interface type<\/p>\n<pre><code>app.MapGet(&quot;\/&quot;, async (IExchangeRatesClient ratesClient) =&gt;\nawait ratesClient.GetLatestRates());<\/code><\/pre>\n<p>Another common pattern is to not provide any con\ufb01guration for the typed client in the AddHttpClient() call. Instead, you could place that logic in the constructor of your ExchangeRatesClient using the injected HttpClient:<\/p>\n<pre><code>public class ExchangeRatesClient\n{\nprivate readonly HttpClient _client;\npublic ExchangeRatesClient(HttpClient client)\n{\n_client = client;\n_client.BaseAddress = new Uri(&quot;https:\/\/example.com\/rates\/&quot;);\n}\n}<\/code><\/pre>\n<p>This is functionally equivalent to the approach shown in listing 33.7. It\u2019s a matter of taste where you\u2019d rather put the con\ufb01guration for your HttpClient. If you take this approach, you don\u2019t need to provide a con\ufb01guration lambda in AddHttpClient():<\/p>\n<pre><code>builder.Services.AddHttpClient&lt;ExchangeRatesClient&gt;();<\/code><\/pre>\n<p>Named clients and typed clients are convenient for managing and encapsulating HttpClient con\ufb01guration, but IHttpClientFactory has another advantage we haven\u2019t looked at yet: it\u2019s easier to extend the HttpClient handler pipeline.\u200c\u200c<\/p>\n<h2>33.3 Handling transient HTTP errors with Polly\u200c<\/h2>\n<p>In this section you\u2019ll learn how to handle a common scenario: transient errors when you make calls to a remote service, caused by an error in the remote server or temporary network problems. You\u2019ll see how to use IHttpClientFactory to handle cross-cutting concerns like this by adding handlers to the HttpClient handler pipeline.<\/p>\n<p>In section 33.2.1 I described HttpClient as consisting of a pipeline of handlers. The big advantage of this pipeline, much like the middleware pipeline of your application, is that it allows you to add cross-cutting concerns to all requests.For example, IHttpClientFactory automatically adds a<br \/>\nhandler to each HttpClient that logs the status code and duration of each outgoing request.<\/p>\n<p>In addition to logging, another common requirement is to handle transient errors when calling an external API. Transient errors can happen when the network drops out, or if a remote API goes o\ufb04ine temporarily. For transient errors, simply trying the request again can often succeed, but having to write the code to do so manually is cumbersome.<\/p>\n<p>ASP.NET Core includes a library called Microsoft.Extensions.Http.Polly that makes handling transient errors easier. It uses the popular open-source library Polly (<a href=\"https:\/\/github.com\/App-vNext\/Polly\">https:\/\/github.com\/App-vNext\/Polly<\/a>) to automatically retry requests that fail due to transient network errors.<\/p>\n<p>Polly is a mature library for handling transient errors that includes a variety of error-handling strategies, such as simple retries, exponential backo\ufb00, circuit breaking, and bulkhead isolation. Each strategy is explained in detail at <a href=\"https:\/\/github.com\/App-vNext\/Polly\">https:\/\/github.com\/App-vNext\/Polly<\/a>, so be sure to read about the bene\ufb01ts and trade-o\ufb00s when selecting a strategy.<\/p>\n<p>To provide a taste of what\u2019s available, we\u2019ll add a simple retry policy to the ExchangeRatesClient shown in section 33.2. If a request fails due to a network problem, such as a timeout or a server error, we\u2019ll con\ufb01gure Polly to automatically retry the request as part of the handler pipeline, as shown in \ufb01gure 33.5.<\/p>\n<p><img decoding=\"async\" src=\"\/images\/aspnetcoreinaction\/3305-1.jpg\" alt=\"alt text\" \/><br \/>\n<img decoding=\"async\" src=\"\/images\/aspnetcoreinaction\/3305-2.jpg\" alt=\"alt text\" \/><\/p>\n<p>Figure 33.5 Using the PolicyHttpMessageHandler to handle transient errors. If an error occurs when calling the remote API, the Polly handler will automatically retry the request. If the request then succeeds, the result is passed back to the caller. The caller didn\u2019t have to handle the error, making it simpler to use the HttpClient while remaining resilient to transient errors.<\/p>\n<p>To add transient error handling to a named client or<\/p>\n<ol>\n<li>HttpClient, follow these steps:<\/li>\n<\/ol>\n<p>Install the Microsoft.Extensions.Http.Polly NuGet package in your project by running dotnet add package Microsoft.Extensions.Http.Polly, by using the NuGet explorer in Visual Studio, or by adding a <code>&lt;PackageReference&gt;<\/code> element to your project \ufb01le as follows:<\/p>\n<pre><code>&lt;PackageReference Include=&quot;Microsoft.Extensions.Http.Polly&quot; Version=&quot;7.0.0&quot; \/&gt;<\/code><\/pre>\n<ol start=\"2\">\n<li>\n<p>Con\ufb01gure a named or typed client as shown in listings 33.4 and 33.7.<\/p>\n<\/li>\n<li>\n<p>Con\ufb01gure a transient error-handling policy for your client as shown in list- ing 33.8.<\/p>\n<\/li>\n<\/ol>\n<p>Listing 33.8 Con\ufb01guring a transient error-handling policy for a typed client<\/p>\n<pre><code>WebApplicationBuilder builder = WebApplication.CreateBuilder(args);\nbuilder.services.AddHttpClient&lt;ExchangeRatesClient&gt;() \u2776\n.AddTransientHttpErrorPolicy(policy =&gt; \u2777\npolicy.WaitAndRetryAsync(new[] { \u2778\nTimeSpan.FromMilliseconds(200), \u2779\nTimeSpan.FromMilliseconds(500), \u2779\nTimeSpan.FromSeconds(1) \u2779\n})\n);<\/code><\/pre>\n<p>\u2776 You can add transient error handlers to named or typed clients.<br \/>\n\u2777 Uses the extension methods provided by the NuGet package to add transient<br \/>\nerror handlers<br \/>\n\u2778 Configures the retry policy used by the handler. There are many types of<br \/>\npolicies to choose among.<br \/>\n\u2779 Configures a policy that waits and retries three times if an error occurs<\/p>\n<p>In the preceding listing we con\ufb01gure the error handler to catch transient errors and retry three times, waiting an increasing amount of time between requests. If the request fails on the third try, the handler ignores the error and pass it back to the client, as though there was no error handler at all. By default, the handler retries any request that<\/p>\n<p>\u2022  Throws an HttpRequestException, indicating an error at the protocol level, such as a closed connection<\/p>\n<p>\u2022  Returns an HTTP 5xx status code, indicating a server error at the API<\/p>\n<p>\u2022  Returns an HTTP 408 status code, indicating a timeout<\/p>\n<p><b>TIP<\/b>  If you want to handle more cases automatically or to restrict the responses that will be automatically retried, you can customize the selection logic as described in the \u201cPolly and HttpClientFactory\u201d documentation on GitHub: <a href=\"http:\/\/mng.bz\/NY7E\">http:\/\/mng.bz\/NY7E<\/a>.<\/p>\n<p>Using standard handlers like the transient error handler allows you to apply the same logic across all requests made by a given HttpClient. The exact strategy you choose will depend on the characteristics of both the service and the request, but a good retry strategy is a must whenever you interact with potentially unreliable HTTP APIs.<\/p>\n<p><b>WARNING<\/b> When designing a policy, be sure to consider the e\ufb00ect of your policy. In some circumstances it may be better to fail quickly instead of retrying a request that is never going to succeed. Polly includes additional policies such as circuit-breakers to create more advanced approaches.<\/p>\n<p>The Polly error handler is an example of an optional HttpMessageHandler that you can plug in to your HttpClient, but you can also create your own custom handler. In the next section you\u2019ll see how to create a handler that adds a header to all outgoing requests.<\/p>\n<h2>33.4 Creating a custom HttpMessageHandler\u200c<\/h2>\n<p>Most third-party APIs require some form of authentication when you\u2019re calling them. For example, many services require you to attach an API key to an outgoing request, so that the request can be tied to your account. Instead of having to remember to add this header manually for every request to the API, you could con\ufb01gure a custom HttpMessageHandler to attach the header automatically for you.<\/p>\n<p><b>NOTE<\/b>  More complex APIs may use JSON Web Tokens (JWT) obtained from an identity provider. If that\u2019s the case, consider using the open source IdentityModel library (<a href=\"https:\/\/identitymodel.readthedocs.io\">https:\/\/identitymodel.readthedocs.io<\/a>), which provides integration points for ASP.NET Core Identity and HttpClientFactory.<\/p>\n<p>You can con\ufb01gure a named or typed client using IHttpClientFactory to use your API-key handler as part of the HttpClient\u2019s handler pipeline, as shown in \ufb01gure 33.6. When you use the HttpClient to send a message, the HttpRequestMesssage is passed through each handler in turn. The API-key handler adds the extra header and passes the request to the next handler in the pipeline. Eventually, the HttpClientHandler makes the network request to send the HTTP request. After the response is received, each handler gets a chance to inspect (and potentially modify) the response.<\/p>\n<p><img decoding=\"async\" src=\"\/images\/aspnetcoreinaction\/3306.jpg\" alt=\"alt text\" \/><\/p>\n<p>Figure 33.6 You can use a custom HttpMessageHandler to modify requests before they\u2019re sent to third-party APIs. Every request passes through the custom handler before the \ufb01nal handler (the HttpClientHandler) sends the request to the HTTP API. After the response is received, each handler gets a chance to inspect and modify the response.<\/p>\n<p>To create a custom HttpMessageHandler and add it to a typed or named client\u2019s pipeline, follow these steps:<\/p>\n<p>\u2022  Create a custom handler by deriving from the DelegatingHandler base class.<\/p>\n<p>\u2022  Override the SendAsync() method to provide your custom behavior. Call base.SendAsync() to execute the remainder of the handler pipeline.<\/p>\n<p>\u2022  Register your handler with the DI container. If your handler does not require state, you can register it as a singleton service; otherwise, you should register it as a transient service.<\/p>\n<p>\u2022  Add the handler to one or more of your named or typed clients by calling <code>AddHttpMessageHandler&lt;T&gt;()<\/code> on an IHttpClientBuilder, where T is your handler type. The order in which you register handlers dictates the order in which they are added to the HttpClient handler pipeline. You can add the same handler type more than once in a pipeline if you wish and to multiple typed or named clients.<\/p>\n<p>The following listing shows an example of a custom HttpMessageHandler that adds a header to every outgoing request. We use the custom &quot;API-KEY&quot; header in this example, but the header you need will vary depending on the third-party API you\u2019re calling. This example uses strongly typed con\ufb01guration to inject the secret API key, as you saw in chapter 10.<\/p>\n<p>Listing 33.9 Creating a custom HttpMessageHandler<\/p>\n<pre><code>public class ApiKeyMessageHandler : DelegatingHandler \u2776\n{\nprivate readonly ExchangeRateApiSettings _settings; \u2777\npublic ApiKeyMessageHandler( \u2777\nIOptions&lt;ExchangeRateApiSettings&gt; settings) \u2777\n{ \u2777\n_settings = settings.Value; \u2777\n} \u2777\nprotected override async Task&lt;HttpResponseMessage&gt; SendAsync( \u2778\nHttpRequestMessage request, \u2778\nCancellationToken cancellationToken) \u2778\n{\nrequest.Headers.Add(&quot;API-KEY&quot;, _settings.ApiKey); \u2779\nHttpResponseMessage response = \u277a\nawait base.SendAsync(request, cancellationToken); \u277a\nreturn response; \u277b\n}\n}<\/code><\/pre>\n<p>\u2776 Custom HttpMessageHandlers should derive from DelegatingHandler.<br \/>\n\u2777 Injects the strongly typed configuration values using DI<br \/>\n\u2778 Overrides the SendAsync method to implement the custom behavior<br \/>\n\u2779 Adds the extra header to all outgoing requests<br \/>\n\u277a Calls the remainder of the pipeline and receives the response<br \/>\n\u277b You could inspect or modify the response before returning it.<\/p>\n<p>To use the handler, you must register it with the DI container and add it to a named or typed client. In the following listing, we add it to the ExchangeRatesClient, along with the transient error handler we registered in listing 33.7. This creates a pipeline similar to that shown in \ufb01gure 33.6.<\/p>\n<p>Listing 33.10 Registering a custom handler in Startup.ConfigureServices<\/p>\n<pre><code>WebApplicationBuilder builder = WebApplication.CreateBuilder(args);\nbuilder.Services.AddTransient&lt;ApiKeyMessageHandler&gt;(); \u2776\nbuilder.Services.AddHttpClient&lt;ExchangeRatesClient&gt;()\n.AddHttpMessageHandler&lt;ApiKeyMessageHandler&gt;() \u2777\n.AddTransientHttpErrorPolicy(policy =&gt; \u2778\npolicy.WaitAndRetryAsync(new[] {\nTimeSpan.FromMilliseconds(200),\nTimeSpan.FromMilliseconds(500),\nTimeSpan.FromSeconds(1)\n})\n);<\/code><\/pre>\n<p>\u2776 Registers the custom handler with the DI container<br \/>\n\u2777 Configures the typed client to use the custom handler<br \/>\n\u2778 Adds the transient error handler. The order in which the handlers are registered dictates their order in the pipeline.<\/p>\n<p>Whenever you make a request using the typed client ExchangeRatesClient, you can be sure that the API key will be added and that transient errors will be handled automatically for you.<\/p>\n<p>That brings us to the end of this chapter on IHttpClientFactory. Given the di\ufb03culties in using HttpClient correctly that I showed in section 33.1, you should always favor IHttpClientFactory where possible. As a bonus, IHttpClientFactory allows you to easily centralize your API con\ufb01guration using named clients and to encapsulate your API interactions using typed clients.<\/p>\n<h2>Summary<\/h2>\n<p>Use the HttpClient class for calling HTTP APIs. You can use it to make HTTP calls to APIs, providing all the headers and body to send in a request, and reading the response headers and data you get back.<\/p>\n<p>HttpClient uses a pipeline of handlers, consisting of multiple HttpMessageHandlers connected in a similar way to the middleware pipeline used in ASP.NET Core. The \ufb01nal handler is the HttpClientHandler, which is responsible for making the network connection and sending the request.<\/p>\n<p>HttpClient implements IDisposable, but typically you shouldn\u2019t dispose of it. When the HttpClientHandler that makes the TCP\/IP connection is disposed of, it keeps a connection open for the TIME_WAIT period. Disposing of many HttpClients in a short period of time can lead to socket exhaustion, preventing a machine from handling any more requests.<\/p>\n<p>Before .NET Core 2.1, the advice was to use a single HttpClient for the lifetime of your application. Unfortunately, a singleton HttpClient will not respect DNS changes, which are commonly used for tra\ufb03c management in cloud environments.<\/p>\n<p>IHttpClientFactory solves both these problems by managing the lifetime of the HttpMessageHandler pipeline. You can create a new HttpClient by calling CreateClient(), and IHttpClientFactory takes care of disposing of the handler pipeline when it is no longer in use.<\/p>\n<p>You can centralize the con\ufb01guration of an HttpClient in ConfigureServices() using named clients by calling AddHttpClient(&quot;test&quot;, c =&gt; {}). You can then retrieve a con\ufb01gured instance of the client in your services by calling IHttpClientFactory.CreateClient(&quot;test &quot;).<\/p>\n<p>You can create a typed client by injecting an HttpClient into a service, T, and con\ufb01guring the client using <code>AddHttpClient&lt;T&gt;(c =&gt; {})<\/code>.<\/p>\n<p>Typed clients are great for abstracting the HTTP mechanics away from consumers of your client.<\/p>\n<p>You can use the Microsoft.Extensions.Http.Polly library to add transient HTTP error handling to your HttpClients. Call AddTransientHttpErrorPolicy() when con\ufb01guring your IHttpClientFactory, and provide a Polly policy to control when errors should be automatically handled and retried.<\/p>\n<p>It\u2019s common to use a simple retry policy to try making a request multiple times before giving up and returning an error. When designing a policy, be sure to consider the e\ufb00ect of your policy; in some circumstances it may be better to fail quickly instead of retrying a request that is never going to succeed. Polly includes additional policies such as circuit-breakers to create more advanced approaches.<\/p>\n<p>By default, the transient error-handling middleware will handle connection errors, server errors that return a 5xx error code, and 408 (timeout) errors. You can customize this if you want to handle additional error types but ensure that you retry only requests that are safe to do so.<\/p>\n<p>You can create a custom HttpMessageHandler to modify each request made through a named or typed client. Custom handlers are good for implementing cross-cutting concerns such as logging, metrics, and authentication.<\/p>\n<p>To create a custom HttpMessageHandler, derive from DelegatingHandler and override the SendAsync() method. Call base.SendAsync() to send the request to the next handler in the pipeline and \ufb01nally to the HttpClientHandler, which makes the HTTP request.<\/p>\n<p>Register your custom handler in the DI container as either a transient or a singleton. Add it to a named or typed client using <code>AddHttpMessageHandler&lt;T&gt;()<\/code>. The order in which you register the handler in the IHttpClientBuilder is the order in which the handler will appear in the HttpClient handler pipeline.<\/p>\n<ol>\n<li>Azure Tra\ufb03c Manager, for example, uses DNS to route requests. You can read more about how it works at <a href=\"http:\/\/mng.bz\/vnP4\">http:\/\/mng.bz\/vnP4<\/a>.<\/li>\n<\/ol>\n","protected":false},"excerpt":{"rendered":"<p>33 Calling remote APIs with IHttpClientFactory\u200c This chapter covers \u2022 Seeing problems caused by using HttpClient incorrectly to call HTTP APIs \u2022 Using IHttpClientFactory to manage HttpClient lifetimes Encapsulating con\ufb01guration and handling transient errors with IHttpClientFactory So far in this book we\u2019ve focused on creating web pages and exposing APIs. Whether that\u2019s customers browsing a [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3],"tags":[],"class_list":["post-637","post","type-post","status-publish","format-standard","hentry","category-csharp"],"_links":{"self":[{"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts\/637","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=637"}],"version-history":[{"count":0,"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts\/637\/revisions"}],"wp:attachment":[{"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=637"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=637"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=637"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}