HttpClientFactory in ASP.NET Core 2.1 (Part 3) Outgoing request middleware with handlers.

In my previous posts in this series (An Introduction to HttpClientFactory and Defining Named and Typed Clients) I introduced some core concepts and then showed some examples of using the new IHttpClientFactory feature in ASP.NET Core 2.1. It’s been a while since those first two posts but I’d like to continue this series by looking at the concept of outgoing request middleware with handlers.

IMPORTANT NOTE: the features shown here require the current preview build of the SDK and the .NET Core and ASP.NET Core libraries. I won’t cover how to get those in this post. At the time of writing we’re in preview 2 of .NET Core and ASP.NET Core 2.1. This preview should be reasonably feature complete but things may still change. If you want to try this out today you can get the preview 2 installers but I recommend waiting until at least the RC before producing any production code.

DelegatingHandlers

To be clear from the outset; many of the pieces involved in this part of the feature have existed for a long time. HttpClientFactory simply makes the consumption of these building blocks easier, through a more composable and clear API.

When making HTTP requests, there are often cross cutting concerns that you may want to apply to all requests through a given HttpClient. This includes things such as handling errors by retrying failed requests, logging diagnostic information or perhaps implementing a caching layer to reduce the number of HTTP calls on heavily used flows.

For those familiar with ASP.NET Core, you will also likely be familiar with the middleware concept. DelegatingHandlers offer an almost identical concept but in reverse; when making outgoing requests.

You can define a chain of handlers as a pipeline, which will all have the chance to process an outgoing HTTP request before it is sent. These handlers may choose to modify headers programmatically, inspect the body of the request or perhaps log some information about the request.

The HttpRequestMessage flows through each handler in turn under it reaches the final inner handler. This handler is what will actually dispatch the HTTP request across the wire. This inner handler will also be the first to receive the response. At this point that response passes back through the pieline of handlers in the reverse order. Again, each handler can inspect, modify or use the response as necessary. Perhaps for certain request paths you want to apply caching of the returned data for example.

 

IHttpClientFactory - DelegatingHandler outgoing middleware pipeline flow

In the diagram above you can see this pipeline visualised.

 

Much like ASP.NET Core middleware, it also possible for a handler to short-circuit the flow and return a response immediately. One example where this might be useful is to enforce certain rules you may have in place. For example, you could create a handler which checks if an API key header is present on outgoing requests. If this is missing, then it doesn’t pass the request along to the next handler (avoiding an actual HTTP call) and instead generates a failure response which it returns to the caller.

Before IHttpClientFactory and its extensions you would need to manually pass a handler instance (or chain of handlers) into the constructor for your HttpClient instance. That instance will then process any outgoing requests through the handlers it has been supplied.

With IHttpClientFactory we can more quickly apply one or more handlers by defining them when registering our named or typed clients. Now, anytime we get an instance of that named or typed client from the HttpClientFactory, it will be configured with the required handlers. The easier way to show this is with some code.

Creating a Handler

We’ll start by defining two handlers. In order to keep this code simple these aren’t going to be particularly realistic in terms of function. They will however show the key concepts. As we’ll see in future posts, there are ways to achieve similar results without having to write our own handlers.

To create a handler we can simply create a class which inherits from the DelegatingHandler abstract class. We can the override the SendAsync method to add our own functionality.

In our example this will be our outer request. A StopWatch will be started before calling and awaiting the base handler’s SendAsync method which will return a HttpResponseMessage. At this point the external request has completed. We can log out the total time taken for the request to flow through any other handlers, out to the endpoint over HTTP and for the response to be received.

Just to keep things interesting let’s create a second handler. This one will check for the existence of a header and if it is missing, will return an immediate response, short-circuiting the handler pipeline and avoiding an unnecessary HTTP call.

Registering Handlers

Now that we have created the handlers we wish to use, the final step is to register them with the dependency injection container and define a client. We perform this work in the ConfigureServices method of the Startup class.

The first two lines register each handler with the service collection which will be used to build the final service provider. These need to be transient so that a new instance is provided each time a new HttpClient is created.

Next, we define a client. In this example I’m using a named client for simplicity. Check out my previous post in this series for more detail about named and typed clients. The AddHttpClient method in this case returns an IHttpClientBuilder. We can call additional extension methods on this builder. Here we are calling the generic AddHttpMessageHandler method. This method takes the type for the handler as its generic parameter.

The order of registration matters here. We start by registering the outer most handler. This handler will be the first to inspect the request and the last to see the response. In this case we want our timing handler to record complete time taken for the whole request flow, including time spent in any inner handlers, so we have added it first. We can call AddHttpMessageHandler again, this time with our ValidateHeaderHandler handler. This will be our final custom handler before the inner HttpClientHandler is passed the request to send the request over the network.

At this point we now have an outgoing middleware pipeline defined on our named ‘github’ client. When a request comes through this client it will first pass into the TimingHandler, then into the ValidateHeaderHandler. Assuming the header is found the request it will be passed on and sent out to the URI in the request. When the response comes back it first returns through the ValidateHeaderHandler which does nothing with the response. It then passes onto the TimingHandler where the total elapsed time is logged and then finally is returned to the calling code.

Summary

While I have shown how easy it is to create a DelegatingHandler and then add it to your HttpClient outgoing pipeline using the new extensions; the team hope that for most cases you will not find yourself needing to craft your own handlers. Common concerns such as logging are taken care of for you within IHttpClientFactory (we’ll look at logging in a future post). For more complex but common requirements such as retrying failed requests and caching responses a much better option is to use a third party library called Polly. The team at Microsoft have made a great decision integrate with Polly.

In my next post I’ll investigate the options for adding Polly based handlers with IHttpClientFactory. In the mean time I suggest you check out this post by Scott Hanselman where he covers the Polly extensions. You can also check out the Polly wiki for more information.

Other Posts in this Series

Part 1 – An introduction to HttpClientFactory
Part 2 – Defining Named and Typed Clients
Part 3 – This post

Upgrading an ASP.NET Core 2.0 application to ASP.NET Core 2.1 (preview 1)

The first official preview of ASP.NET Core 2.1 was released yesterday. I’ve been playing around with the nightly builds for various parts of ASP.NET Core 2.1 for the last few months and finally I was able to move to the official preview 1 packages on NuGet rather than the MyGet feeds.

I wanted to test things out by upgrading the Humanitarian Toolbox allReady project from ASP.NET Core 2.0 to ASP.NET Core 2.1. This is a minor release of the framework so it shouldn’t require too many complex changes.

I made most of these changes prior to the official blog post being released and having now read that I’m pleased to see that I covered the right update steps.

Upgrading the Project

The first set of changes to be made are in the project file for the main web application. I decided to do these manually so that I had full control over the versions.

First I changed the TargetFramework from 2.0 to 2.1 at the top of the project file.

The relevant line of code in 2.0 was

<TargetFramework>netcoreapp2.0</TargetFramework>

and became

<TargetFramework>netcoreapp2.1</TargetFramework>

The next step was to pull in the ASP.NET Core 2.1 packages. In 2.0, this was accomplished with the Microsoft.AspNetCore.All meta-package. In 2.1 a new meta package called Microsoft.AspNetCore.App is available. This reduces the number of 3rd party (and some 1st party) dependencies which are pulled into your application by default. You can read more about this change in the announcement issue.

<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />

became

<PackageReference Include="Microsoft.AspNetCore.App" Version="2.1.0-preview1-final" />

Our project includes BrowserLink and as a result of the change to the App meta-package this is no longer included for us by default. To fix this I added an explicit package reference for it.

<PackageReference Include="Microsoft.VisualStudio.Web.BrowserLink" Version="2.1.0-preview1-final" />

Finally I updated the CLI tooling references. These allow you to bring in and use command line tools against your project. In 2.1, some of these will move over to global tools, a new feature which will allow these to be installed on your device once, and then used by any project that needs them. You can read more about Global Tools in the .NET Core 2.1 preview 1 announcement post. This will avoid the need to reference and maintain the versions of them.

Global tools are likely to get more stable in the preview 2 timeframe and so for now I simply updated the version numbers where necessary.

<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.0" />
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.0" />

changes to

<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.1.0-preview1-final" />
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.1.0-preview1-final" />

This completed the changes that were needed in the main project file for the 2.1 upgrade.

Upgrading the Unit Test Project

Similar changes were needed in our unit test project too. I updated to target netcoreapp2.1 and were we reference specific AspNetCore packages, I updated those to point to the preview1 packages. For example

<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.0.0" />

Became…

<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.1.0-preview1-final" />

Other Changes

At this point, for a basic upgrade I was actually almost done with the required changes. My code was restoring and compiling correctly. I did hit one issue at runtime that I’ll briefly touch on.

ModelBinderProvider Issue

Our code includes and adds a custom IModelBinderProvider After the upgrade, some requests began throwing NullReferenceExceptions. Tracking down the issue was actually quite interesting, since the exception occurred for me after the page had started to render in the browser. At first I couldn’t work out why. It was actually the result of a missing JavaScript file that was causing a 404 error. We have an error handling flow that re-executes the request to a custom error Action and View.

It was actually this flow that was highlighting the model binding issue. That error action includes a route parameter and after the 2.1 upgrade I noticed that when running through the IModelBinderProvider, a change in the 2.1 code was causing our existing code to break.

I put together a small solution that reproduced and confirmed the behaviour changes. I was then able to raise an issue with the ASP.NET team to highlight this. To their credit, they got onto it very quickly and provided a workaround for our code and confirmation that they would aim to fix the behavior in preview 2.

You can read more about that in the issue on GitHub.

As per the suggestion from Doug in the issue, I updated our code, from…

if (!context.Metadata.IsComplexType && !string.IsNullOrEmpty(context.Metadata.PropertyName) && context.Metadata.ContainerType != null)

to this…

if (!context.Metadata.IsComplexType && context.Metadata.MetadataKind == ModelMetadataKind.Property)

At this point, the site functioned as expected during my basic testing. As per the issue, this change should hopefully not be required in 2.1 preview 2.

Further Enhancements

At this stage, all I’ve done here is to re-target the application to .NET Core 2.1 and update it to point at the relevant ASP.NET Core 2.1 packages. To take advantage of some of the new features in ASP.NET Core 2.1, some areas of our code could be updated to make use of those features. For example, I didn’t add in the new middleware for the HttpsRedirection features. New ASP.NET Core 2.1 projects will use SSL by default and redirect to HTTPS. Making such a change in the allReady project would require additional consideration as it could impact how we build, deploy and run the application.

There are other things such as the new IHttpClientFactory feature which I’ve blogged about already that we could look to utilise in the project. I consider those enhancements, beyond a basic upgrade and so each change would first be assessed and then implemented where necessary.

Summary

That concludes the upgrade process of a real application from ASP.NET Core 2.0 to ASP.NET Core 2.1 preview 1. Note that we don’t intend to move to 2.1 for allReady just yet. At this point it’s not go-live ready and is for preview purposes only. I’m pleased that for the most part, upgrading was pretty painless. Hopefully the breaking issue I did run into will be fixed or improved by the time preview 2 is released.

If you care strongly about the direction of ASP.NET Core it’s well worth you testing this upgrade on your own applications to see if you run into any issues. By testing preview 1 early it’s possible to give feedback that as in my example, will be raised early enough for inclusion in the next preview. Preview 2 is anticipated in around 4 to 5 weeks’ time.

HttpClientFactory in ASP.NET Core 2.1 (Part 2) Defining Named and Typed Clients

In my last post – An introduction to HttpClientFactory – I explained some of the reasons behind the creation of the feature. We looked at what problems it helps solve and then followed a very basic example showing how it can be used in a WebAPI application. In this post I want to dive into two other ways we can make use of it; returning named clients and typed clients.

IMPORTANT NOTE: the features shown here require the current nightly builds of the SDK and the .NET Core and ASP.NET Core libraries. I won’t cover how to get those in this post. Treat this as an early preview of how the feature will work so that you can begin planning where and how you will use it once 2.1 is publicly available. Unless you have an urgent need to try this out today, I’d recommend waiting until the 2.1 previews are released, hopefully within the next month or so.

Named Clients

In the first post I demonstrated how we could use the HttpClientFactory to get a basic HttpClient instance. That’s fine when you just need to make a quick request from a single place in your code. Often though you’ll want to make multiple requests to the same service, from multiple places in your code.

HttpClientFactory makes this slightly easier by providing the concept of named clients. With named clients you can create a registration which includes some specific configuration that will be applied when creating the HttpClient. You can register multiple named clients which can each come pre-configured with different settings.

To make this slightly more concrete, let’s look at an example. In my Startup.ConfigureServices method I’ll use a different overload of the AddHttpClient extension method which accepts two additional parameters. A name and an Action delegate taking a HttpClient. My ConfigureServices looks like this:

The first string parameter is the name used for this client registration. The Action<HttpClient> delegate allows us to configure our HttpClient when it is constructed for us. This is pretty handy as we can predefine a base address and some known request headers for example. When we ask for a named client, a new one is created for us and it’ll have this configuration applied each time.

To use this we can ask for a client by name when calling CreateClient as follows:

In this example we now have an instance of a HttpClient which has the base address set, so our GetStringAsync method can pass in the relative URI to follow the base address.

This named approach gives us some control over the configuration applied to the HttpClient which we receive. I’m not a huge fan of the magic strings here so if I were using named clients I’d likely have a static class containing string constants for the names of the clients. Something like this:

When registering (or requesting) a client we can then use the static class values, instead of the magic string:

This is pretty nice, but we can go a step further and look at using a custom typed client instead.

Typed Clients

Typed clients allow us to define custom classes which expect a HttpClient to be injected in via the constructor. These can be wired up within the DI system using extension methods on the IHttpClientBuilder or using the generic AddHttpClient method which accepts the custom type. Once we have our custom class, we can either expose the HttpClient directly or encapsulate the HTTP calls inside specific methods which better define the use of our external service. This approach also means we no longer have magic strings and seems quite reasonable.

Let’s look at a basic example. We’ll start by defining our custom typed client class:

This class needs to accept a HttpClient as a parameter on it’s constructor. For now we’ve set a public property with the instance of the HttpClient.

We then need to register this in ConfigureServices as follows:

Here we pass our MyGitHubClient type to the generic argument. As our custom typed class accepts a HttpClient this will be wired up within the factory to create us an instance with the appropriately configured HttpClient injected in. We can now update our controller to accept our typed client instead of an IHttpClientFactory:

Since our custom typed client exposes its HttpClient as a property we can use that to make HTTP calls directly.

Encapsulating the HttpClient

The final example I want to look at in this post is a case where we want to encapsulate the HttpClient entirely. This approach is most likely useful when we want to define methods which handle specific calls to our endpoint. At this point we could also encapsulate the validation of the response and deserialisation within each method so that it is handled in a single place.

In this case we’ve stored the HttpClient that gets injected at construction in a private readonly field. Instead of dependants of this class accessing the HttpClient directly, we have provided a GetRootDataLength method which performs the HTTP call and returns the length of the response. A trivial example but you get the idea!

I also updated the typed client to inherit from an interface. We can now update the controller to accept and consume the interface as follows:

We can now call the GetRootDataLength method as defined on our interface, without needing to interact with a HttpClient directly. Where this really shines is testing, we can now easily mock our IMyGitHubClient when we want to test this controller. Testing HttpClient in the past was a bit of a pain and took more lines of code than I generally like to provide a suitable mock.

To register this in our DI container our call in ConfigureServices becomes:

The AddHttpClient has a signature which accepts two generic arguments and wires up DI appropriately.

Summary

In this post we’ve explored some of the more advanced ways we can use the HttpClientFactory feature which allows us to create different HttpClient instances with specific named configurations. We then looked at the option of using typed clients which extends this to further support implementing our own classes, which accept a HttpClient instance. We can either expose that HttpClient directly or encapsulate the calls to the remote endpoint within this class.

In the next post we’ll take a look at another pattern we can use to apply an “outgoing request middleware” approach using DelegatingHandlers.

Other Posts in this Series

Part 1 – An introduction to HttpClientFactory
Part 2 – This post
Part 3 – The Outgoing Request Middleware Pipeline with Handlers

HttpClientFactory in ASP.NET Core 2.1 (Part 1) An Introduction to HttpClientFactory

TL;DR;

A new HttpClientFactory feature is coming in ASP.NET Core 2.1 which helps to solve some common problems that developers may run into when using HttpClient instances to make external web requests from their applications.

Introduction

This blog post has been in the works since mid-October 2017, which was when I first noticed the new HttpClientFactory repository appear on GitHub. I was intrigued by its appearance and wondered what the ASP.NET team were up to, so I went diving into the available code that the repo contained at the time. I’ve then kept an eye on it ever since, watching as the team evolved the feature by reading the commits, issues and pull request discussions.

Recently the feature has started to be talked about more openly and was included in a recent talk by Damian Edwards and David Fowler at NDC London. In fact on the day of writing this introduction it’s been shown on both Jeff Fritz’s livestream show and the ASP.NET Community Standup. The opinion of Ryan Nowak, one of the main ASP.NET developers for the feature, is that it’s reasonably stable to begin writing about it now.

NOTE: Please bear in mind that this post is written prior to the official preview release of .NET Core 2.1 by using the nightly builds of ASP.NET Core 2.1 and the .NET Core SDK. Therefore, things may change before and during the public previews (hopefully we’ll get these within the next month) and also before the final release of 2.1 based on feedback received from those previews.

What is HttpClientFactory?

In the words of the ASP.NET Team it is “an opinionated factory for creating HttpClient instances” and is a new feature coming with the release of ASP.NET Core 2.1. Depending on your past experience using HttpClient, you may or may not be aware of some of the pitfalls that can be encountered, sometimes without even being aware that you have a problem.

The first issue is when you create too many HttpClients within your code which can in turn create two problems…

  1. It’s inefficient as each one will have its own connection pool for the remote server. This means you pay the cost of reconnecting to that remote server for every client you create.
  2. The bigger problem you can have if you create a lot of them is that you can run into socket exhaustion where you have basically used up too many sockets too fast. There is a limit on how many sockets you can have open at one time. When you dispose of the HttpClient, the connection it had open remains open for up to 240 seconds in a TIME_WAIT state (in case any packets from the remote server still come through).

HttpClient implements IDisposable and this often leads developers to follow the normal pattern when using an IDisposable object, creating it within a using block. This ensures that the object is properly disposed of once you’re done with it and it has gone out of scope. If you want to read more about this, it is well documented by the ASP.NET Monsters in their post “You’re using HttpClient wrong and it’s destablizing your software”.

A preferred approach therefore it to reuse HttpClient instances so that connections can also be reused. HttpClient is a mutable object but as long as you are not mutating it, it is actually thread safe and can be shared. A common approach is therefore to register it as a singleton with a DI framework or to create a wrapper around it which holds a static instance.

However, this creates a new problem. Using a single HttpClient in this way will keep connections open and not respect the DNS Time To Live (TTL) setting. Now the connections will never get DNS updates so the server you are talking to will never have its address updated. This is entirely possible in some situations where you are balancing over many hosts that may go away over time or perhaps rolling out new services using blue/green deployments. If the server is gone, the IP your connection is using may no longer respond to requests that you make through the single HttpClient. You can read more about this issue at “Singleton HttpClient? Beware of this serious behaviour and how to fix it” and “Singleton HttpClient doesn’t respect DNS changes”.

HttpClientFactory is designed to help start solving these problems and provides a new mechanism to create HttpClient instances that are properly managed for us behind the scenes. It will “do the right thing” for us and we can focus on other things! While the above problems are mentioned in reference to HttpClient, in fact the source of the issues actually occurs on the HttpClientHandler, which is used by HttpClient. The HttpClientFactory manages the lifetime of the handlers so that we have a pool of them which can be reused, while also rotating them so that DNS doesn’t get stale.

The expensive part of using HttpClient is actually creating the HttpClientHandler and the connection. Having these pooled in this manner means we can get more efficient use of the connections on our system. When you use the HttpClientFactory to request a HttpClient, you do in fact get a new instance each time, which means we don’t have to worry about mutating it’s state. This HttpClient may (or may not) use an existing HttpClientHandler from the pool and therefore use an existing open connection.

By default, each new HttpClientHandler (which derives from HttpMessageHandler) will be created with an active lifetime of 2 minutes. This can be controlled on a per named client basis when creating it’s handler chain. Once the lifetime is reached, the handler will not be immediately be disposed of and will instead be placed into the expired pool. Any clients depending on the original handler chain can continue using it without any issues. There is a background job checking the expired pool to see if all references for the handler have gone out of scope, at which point it can then be disposed of. Any new requests for a new client once the handler chain has been expired will get a new handler chain.

This works reasonably well, but there are other things underway on the .NET Core side which might improve the situation further. The .NET Core team are working on a new ManagedHandler which should manage DNS more correctly and in principle can be kept around for longer, meaning connections can be shared even more efficiently. This new handler is also being designed to function more consistently across the different operating systems. Until that work is completed (which might be in the 2.1 time frame) the pooling of handlers above is a reasonable workaround.

How to use HttpClientFactory

IMPORTANT NOTE: The features and code samples shown here require the current nightly builds of the SDK and the .NET Core and ASP.NET Core libraries. I won’t cover how to get setup to use those in this post. Treat this as an early preview of how the feature will work so that you can begin planning why, where and how you will use it once 2.1 is publicly available. Unless you have an urgent need to try this out today, I’d recommend waiting until the 2.1 previews are released, hopefully within the next month or so.

In this post I’ll concentrate on one of the most basic ways to get started with the HttpClientFactory. For this example, we’ll start by creating a simple WebAPI project and then edit the csproj file to upgrade it to use the new .NET Core and ASP.NET Core 2.1 bits. First we need to set it to be based on netcoreapp2.1 (not yet in official preview) and then include two packages which we’ll need. For this post I’m pinning those to specific preview nightly build versions available on the ‘dev’ MyGet feeds. After doing this our project file looks like this:

Next we need to head over to our Startup.cs file and register a service. The HttpClientFactory includes various ServiceCollection extensions. The one we’ll use for this example is:

services.AddHttpClient();

Behind the scenes this will register a few required services, one of which will be an implementation of IHttpClientFactory. Next we’ll update the default ValuesController to make use of this feature:

Here we are first adding a dependency on IHttpClientFactory which will be injected into our controller by the DI system. The IHttpClientFactory allows us to ask for and receive a HttpClient instance.

Within our Get action we are then using the HttpClientFactory to create a client. Behind the scenes the HttpClientFactory will create a new HttpClient for us. But wait, didn’t I say earlier that using a new HttpClient for each request is bad? Indeed I did; but in fact that was a little bit of misdirection. The HttpClient itself is not really the problem, it’s the HttpClientHandler which it uses to make the HTTP calls that is the actual issue. It’s this which opens the connections to the external services that will then remain open and block sockets, even in the main HttpClient is disposed of.

HttpClientFactory pools these HttpClientHandler instances and manages their lifetime in order to solve some of the issues I mentioned earlier. Each time we ask for a HttpClient, we get a new instance, which may (or may not) use an existing HttpClientHandler. The HttpClient itself it not too heavy to construct so this is okay.

Once created the HttpClientHandlers are pooled and held around for around 2 minutes by default. This means that any new requests for CreateClient may share a handler and therefore the connections also. While a HttpClient lives, it’s handler will remain available and again this will share the connection.

After the two minutes, each HttpClientHandler is marked as expired. The expired state simply marks them so that they are no longer used when creating any new HttpClient instances. They are not immediately disposed however, as other HttpClient instances may be using them. The HttpClientFactory uses a background service which monitors the expired handlers and once they are no longer referenced, can then dispose of them properly, allowing their connections to be closed also.

This pooling feature helps reduce the risk of socket exhaustion and the refreshing process helps solve the DNS update problem by ensuring we don’t have long lived instances of HttpClientHandlers and connections hanging around. It’s a reasonable compromise which is managed for us by making use of the HttpClientFactory feature.

Summary

I’ll leave it there for this introductory post. In future posts I’ll dive into some of the more advanced ways we can use HttpClientFactory as there’s some nice features so show off. We’ll look at how we can create named HttpClient instances with configuration and also creating our own typed clients. This is where the feature will really begin to shine. Hopefully you’ll have seen, even in this basic example, how it improves use cases where you have a requirement to make HTTP calls in the most correct and efficient way. We don’t need to think about how we manage the lifetime of the clients or worry about running into DNS issues. I’m looking forward to using this in production once ASP.NET Core 2.1 is released.

Other Posts in this Series

Part 1 – This post
Part 2 – Defining Named and Typed Clients
Part 3 – The Outgoing Request Middleware Pipeline with Handlers