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.

ASP.NET Core Anatomy – How does UseStartup work? Exploring how UseStartup results in your Startup methods being registered and executed.

I was recently explaining to someone the basics of the program flow for an ASP.NET Core application. One of the things included in the templates for ASP.NET Core and used very often is the UseStartup<T> extension method on the IWebHostBuilder. This gets called from our Program.cs when initialising the application. UseStartup allows us to set the Startup class which defines the services and middleware pipeline for an ASP.NET Core application.

During my explanation I realised that while I know the result of calling this method, I didn’t know how things are wired up under the hood; so I decided to investigate!

NOTE 1: This content is valid as at the 2.0.0 release codebase. I don’t expect that the fundamentals will change dramatically in the future but I have seen some commits which tweak the code a little for 2.1!

NOTE 2: This is a deep dive blog post looking at internal ASP.NET Core code. You don’t need to know this to use ASP.NET Core to build applications – please don’t let this scare you off! This is intended for those of you, who like me, have a curious mind about the internals of ASP.NET Core. I’m conscious that this may get quite hard to follow as we get deep into the guts of the code as there’s a lot of use of delegates that makes explaining the flow quite challenging. I’ll try my best to make it clear!

How are Startup methods registered and executed?

The generic UseStartup<TStartup> method calls down to the main IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, Type startupType) extension, passing in the Type for the Startup class. That method looks like this (full source on GitHub):

The ConfigureServices method on the IWebHostBuilder is called which expects an Action<IServiceCollection> parameter. In this case it’s defined as a lambda expression which acts on the IServiceCollection. The WebHostBuilder class has a private List<Action<WebHostBuilderContext, IServiceCollection>> field named _configureServicesDelegates. The call to ConfigureServices in the code above will add the lambda as a new item to this list which will be used later to construct an instance of IStartup.

At this stage a list of delegates will have been registered within the WebHostBuilder. The main process begins when Build() is called on the WebHostBuilder. I won’t cover everything that Build does, since it’s not all relevant to the scope of this post. Here’s the code of that method (full source on GitHub):

The Build method calls a private method BuildCommonServices (full source on GitHub) which as the name suggests will add some common framework services into the current ServiceCollection. I’ll skip over most of the code in this method. Unless we’ve changed the WebHostOptions to define different assemblies to load Startup from, the execution flow will eventually hit code which looks over the _configureServicesDelegates List, calling each delegate in turn. That piece of code looks like this:

In the sample application which I used in order to debug through the Hosting codebase, I have two delegates registered, one from a FakeServer (needed so that the WebHostBuilder doesn’t throw an exception) and one from the UseStartup call. The first delegate simply registers the FakeServer as the implementation for IServer inside the ServiceCollection. The second delegate will now execute the lambda expression which was registered in the UseStartup method. Let’s remind ourselves what that looked like:

If our Startup class implements IStartup directly, it can and will be registered as the implementation type for IStartup directly. In my sample (which is based on the default ASP.NET Core templates) our Startup class does not implement IStartup and will rely on conventions instead. In this case an AddSingleton overload is used which takes the Func<IServiceProvider, object) as it’s implementation factory. This Func delegate will be called when the first concrete IStartup implementation is requested from the DI container.

At this point the registration of IStartup is included in the ServiceCollection and ready to be called by the framework. The WebHostBuilder.Build method continues to execute and constructs a new WebHost instance, which includes passing in an IServiceCollection (a clone of the current hostingServices variable). It also passes in a ServiceProvider, built using the current state of the hostingServices ServiceCollection. This represents the application services which have been registered so far by the framework.

Once we have a WebHost instance, its Initialize method is called. This calls down to a private BuildApplication method (full source on GitHub). Hold tight, lots of stuff starts to happen at this stage. I’ll try to pick out the parts I think are relevant to the use of our Startup class.

The BuildApplication method does some basic checks to make sure the relevant services are available for the application to start. One of the checks is that the ServiceProvider which was passed in includes an implementation for IStartup. This particular check happens inside a helper method called EnsureStartup()

The call to _hostingServiceProvider.GetRequiredService<IStartup>(); will trigger the DI framework to construct an instance of IStartup as per its registration. Due to the use of delegates, we need to head back to the UseStartup method to look at the lambda we passed in for the Func<IServiceProvider, object) implementationFactory. As a reminder, here’s the service registration that was used:

We can see that we’re going to get returned a newly constructed ConventionBasedStartup instance as the implementation for IStartup. The ConventionBasedStartup constructor accepts a StartupMethods object (full source on GitHub) as its parameter. A static StartupLoader.LoadMethods method (full source on GitHub) is used to generate a StartupMethods instance. This object acts as a holder for three properties.

The most important of these properties for this discussion are the delegates for the ConfigureServices and Configure. These will be setup with the code which should execute when the framework calls these methods further down in the WebHost initialisation. Ultimately the code for these delegates is expected to execute methods on our our Startup class.

The first delegate it will try to find is the ConfigureDelegate. This will be used to build the middleware pipeline for the application. Internally the StartupLoader uses a helper method called FindMethod to do most of the work. This is called from a FindConfigureDelegate method (full source on GitHub). The FindMethod is as follows (full source on GitHub):

This method will first work out the method name(s) it should be looking for on the Startup class based on the methodName parameter passed to it. The convention is that the method which defines the middleware pipeline should be called Configure. There is a lesser known convention in the Startup class which in addition to providing the standard “Configure” method, you can choose to include environment specific version(s) in your Startup class too. By convention, if a Configure{EnvironmentName} is found (e.g. “ConfigureProduction”) for the current environment, that method will be used in preference to the general Configure method.

In our sample, we only have the standard Configure method defined. Reflection is used to find a method matching the expected name on our Startup type. There are various checks in place to ensure that the expected members on our class are valid (i.e. we don’t have more than one Configure method defined with the same name) and that it has the expected Void return type.

Once we have the MethodInfo for the matching method, it is passed as the parameter into the constructor for a new ConfigureBuilder instance (full source on GitHub). This is stored in a variable in the LoadMethods method to be used a little later on.

A very similar process occurs to find and store the MethodInfo for ConfigureServices from our Startup class which is stored in a local variable called servicesMethod. Finally, the same approach is used looking for a ConfigureContainerDelegate. This is  an optional method which we can include on our Startup class to interact with 3rd party dependency injection containers such as AutoFac. We won’t look at this here.

Next, inside LoadMethods, a static ActivatorUtilities.GetServiceOrCreateInstance is called to get or create an instance of our Startup class. Here’s a compressed version of that LoadMethods method for reference (full source on GitHub):

As an implementation instance of IStartup is not currently stored in the DI container, GetServiceOrCreateInstance will create an instance of our Startup class by calling it’s constructor. In my sample (which matches the default Startup class in a new ASP.NET Core application template) it expects an IConfiguration object be passed in. The DI framework will have access to an implementation for this and will inject it in for us. Here’s my Startup constructor for reference:

Next the method on the ConfigureServicesBuilder is set as a callback variable. It gets passed the newly created Startup instance as its parameter. The same occurs to store a callback for ConfigureContainer. Next a Func<IServiceCollection, IServiceProvider> is setup using a lambda expression (which I’ve excluded from the code above for now). We’ll look at this when we see how this gets called a little later.

At this point, the callback delegates are passed into the constructor for a new StartupMethods instance which is then returned as the result of LoadMethods. This is then passed into the constructor for the new ConventionBasedStartup instance. At this point we have a concrete implementation of IStartup registered with the DI framework. Back inside the WebHost.EnsureApplicationServices method, the ConfigureServices method from the IStartup interface is called. Ready to start navigating some delegates!?

The ConfigureServices method on the ConventionBasedStartup instance calls the ConfigureServicesDelegate property on its StartupMethods member.

This executes the lambda defined code from the StartupMethods.LoadMethods method which in-turn invokes the Func<IServiceCollection, IServiceProvider> delegate returned from the ConfigureServicesBuilder.Build method, which ultimately calls the private ConfigureServicesBuilder.Invoke method (full source on GitHub).

The Invoke method uses reflection to get and inspect the parameters required by the ConfigureServices method defined on our Startup class. By convention this method can be either parameterless or take a single parameter of type IServiceCollection.

If the ConfigureServices method on our Startup class expects the IServiceCollection parameter, this is set using the IServiceCollection which was passed into the Invoke method. Once the method is configured via reflection it is invoked and the returned value will either be Void or an IServiceProvider. It’s at this point that we are actually executing the code contained in our ConfigureServices method on our Startup class. Our class can use the IServiceCollection extensions to register services and their implementations with the DI container.

At this point the lambda expression (in StartupLoader) wants to return a ServiceProvider. If our Startup.ConfigureServices method returned an IServiceProvider directly, this then gets returned immediately. If not, an IServiceProviderFactory is requested from the hostingServiceProvider and used to construct the ServiceProvider. This is the application level ServiceProvider that will be used to resolve dependencies in our code base.

The final point I’d like to show within WebHost.BuildApplication is how the final RequestDelegate is built. We won’t cover this in depth here, but in short, the RequestDelegate is defined as “A function that can process an HTTP request.” This is what the framework will actually use to process each request through our application. This will be setup to include all of the middleware as defined in our applications pipeline.

The relevant code in side of WebHost.BuildApplication is (full source on GitHub):

An IApplicationBuilderFactory is used to build up and finally surface our RequestDelegate. This is one of the services registered earlier in the WebHostBuilder.BuildCommonServices method.

The ApplicationServices property on the builder is set with the ServiceProvider that was just created. The next detail is something I’ll gloss over slightly as it goes a bit too far off the flow I want to explore. In short an IEnumerable of IStartupFilters may have been registered with the DI framework. In my sample I haven’t registered any so only the default AutoRequestServicesStartupFilter (full source on GitHub) will be returned from the ServiceProvider.

An Action<IApplicationBuilder> delegate variable is created holding a wrapped set of Configure methods from each IStartupFilter, the final one being the delegate for our Startup.Configure method. At this point, the Configuration chain is called which first hits the AutoRequestServicesStartupFilter.Configure method. This holds our delegate chain as its next action and so this will call down into the ConventionBasedStartup.Configure method. This will call the ConfigureDelegate on its local StartupMethods object.

Invoking that Action will call the private ConfigureBuilder.Invoke method (full source on GitHub) which looks like this:

This will prepare to call our Startup.Configure method sending in the appropriate parameters which get resolved from the ServiceProvider. Our Configure method can add middleware into the application pipeline using the IApplicationBuilder. The final RequestDelegate is built and returned from the IApplicationBuilder and the WebHost initialisation then completes.

Summary

This has been a quite long and deeply technical post. If you’ve stuck with it; well done! I hope I’ve interpreted everything correctly and lifted the curtain on some of the “magic” behind the scenes that makes ASP.NET Core work. It was quite difficult to explain everything due to the layers of delegates involved here. Hopefully I did a good enough job for you to get the gist of things. I find it really useful to dig into the code like this and gain a better understanding of the internals. If you want to explore the code yourself, check out the ASP.NET Core Hosting repository on GitHub.

Other posts in this series

Visit the ASP.NET Core Anatomy Index post to see the other deep dives covered in this series.

ASP.NET Core Gotchas – No. 4 Value cannot be null. Parameter name: connectionString when running dotnet ef migrations add

I was upgrading a quite mature ASP.NET Core 1.0 project to ASP.NET Core 2.0 today and ran into an odd issue which took me a good half hour to track down. After working my way through the various breaking changes to things such as authorisation I was pretty much at the point where my project was compiling again.

My last change was to fix-up the migrations in the project due to changes in the NpgSql project as detailed in their migration documentation. The last step there is to run in a dummy migration to the project. I attempted to run in the following command…

dotnet ef migrations add "dummy migration"

This started to run and then threw an error and dumped out a stack trace. I won’t include it all here but the higher frames are as follows…

StructureMap.Building.StructureMapBuildException: Failure while building 'Lambda: Invoke(value(StructureMap.ContainerExtensions+>c__DisplayClass9_0).descriptor.ImplementationFactory, IContext.GetInstance())', check the inner exception for details
1.) Lambda: Invoke(value(StructureMap.ContainerExtensions+<>c__DisplayClass9_0).descriptor.ImplementationFactory, IContext.GetInstance())
2.) Instance of DbContextOptions<ReportingDbContext> (System.Object)
3.) Container.GetInstance(DbContextOptions<ReportingDbContext>)
4.) Lambda: Invoke(value(StructureMap.ContainerExtensions+<>c__DisplayClass9_0).descriptor.ImplementationFactory, IContext.GetInstance())
5.) Instance of Microsoft.EntityFrameworkCore.DbContextOptions (System.Object)
6.) All registered children for IEnumerable<DbContextOptions>
7.) Instance of IEnumerable<DbContextOptions>
8.) Container.GetInstance(IEnumerable<DbContextOptions>)
---> System.ArgumentNullException: Value cannot be null. Parameter name: connectionString at Microsoft.EntityFrameworkCore.Utilities.Check.NotEmpty(String value, String parameterName) at Microsoft.EntityFrameworkCore.NpgsqlDbContextOptionsExtensions.UseNpgsql(DbContextOptionsBuilder optionsBuilder, String connectionString, Action`1 NpgsqlOptionsAction)

The key part of the error is “Value cannot be null. Parameter name: connectionString”.

In principle this would seem like a fairly obvious problem. I must have messed something up when performing the configuration changes as part of the upgrade to ASP.NET Core 2.0. However, if I ran the application from Visual Studio, I could see the database being created and migrated (we have a Database.Migrate() call in our code). So this would suggest that in fact the configuration was working and the connection string was being read in. Odd!

At this stage I resorted to Google and found a few similar sounding issues and StackOverflow posts. However they all seemed to suggest that indeed it was due to bad configuration of some form or another. Eventually I decided to double check my csproj file reference. During the migration I’d used the NuGet package manager to upgrade the packages after first editing my csproj to use netcoreapp2.0.

Here’s an abridged example of the csproj file:

Do you see the issue?

It took me a couple of parses to spot something suspicious. At the bottom the file includes a DotNetCliToolReference to bring in the Entity Framework command line tooling. It was set to version 1.0.0 which didn’t seem quite right given that I was now on 2.0.x of most other Microsoft packages. A quick check on Nuget for the latest version showed that there was a 2.0.1 available.

I edited my csproj to change the version, saved it and tried running my migration command again. Success! This time the migration process ran as expected.

It’s easily missed (or at least it was for me) since I’d relied on the Nuget Package Manager to save me some time when upgrading my packages. This tool reference however is not shown there and is something I needed to manually update. Hopefully this saves other people scratching there heads over this error message in the future!

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