IHttpClientFactory Patterns: Using Typed Clients from Singleton Services Exploring an approach to reuse transient typed clients within singleton services

I’ve been following IHttpClientFactory for some time and have created a number of blog posts on the various features based on sample applications. Since then; with the release of ASP.NET Core 2.1 now available, I’ve begun using IHttpClientFactory in real production applications. In this post, I want to start covering some patterns I’ve begun to apply as I develop my applications.

This time I want to look at an issue regarding the scope of typed clients. This is based on something I’ve now directly encountered and that was raised as a comment on an earlier post by a reader named Yair.

When defining typed clients in your ConfigureServices method, the typed service is registered with transient scope. This means that a new instance is created by the DI container every time one is needed. The reason this occurs is that a HttpClient instance is injected into the typed client instance. That HttpClient instance is intended to be short lived so that the HttpClientFactory can ensure that the underlying handlers (and connections) are released and recycled.

This works in cases where you plan to consume the typed service from another transient service. A common place to use these in ASP.NET Core will be places such as Controllers for example. That works as expected since the controller is created by the framework for each request.

However, what if you want to use the typed client within a singleton service? That presents a problem. If this was allowed it would be created and injected once, and then held onto by the singleton service. This is not the behaviour we want.

Originally, when asked by Yair about how to use HttpClientFactory in singleton services I suggested instead using the named client approach and then injecting the IHttpClientFactory directly to your singleton service. From there, you can call CreateClient on the factory within methods on that singleton service, so that for each invocation, a new HttpClient is created for only a short lifetime.

The problem is that if we don’t get the typed client behaviour where we can encapsulate the work necessary to interact with a third party API as a service. Instead, we would have to make a service that depends on the IHttpClientFactory as I suggested above and then pass that into the necessary places in our code.

Having now tackled this in a real project where I am essentially building an SDK around an internal API that I’ve developed I have reassessed the options. What I ended up doing was using the typed client approach, but also providing my own factory which can return instances of that typed client. This is actually very simple to do by leveraging the DI service provider directly.

Here is an example of a typed client and its interface:

I won’t dive deeply into how this service works. It’s a typed client and you can read my earlier post about named and typed clients for more information.

In short; this service expects to have a HttpClient instance injected when it is created by the DI container. It then wraps the logic needed to call various endpoints of a remote API. Within this service, we can include the code needed to validate the response and deserialise the returned content from the request.

We can register the typed client in our ConfigureServices method as follows:

At this point, we have a typed client which can be consumed from other transient services and controllers. To make this accessible to singleton services in our application we can add a basic factory.

Here we have created a basic interface for an IConfigurationServiceFactory which defines a single GetConfigurationService method.

The implementation takes an IServiceProvider in its constructor, which will be injected by DI. With access to the service provider, we can use it to return an instance of IConfigurationService from the GetConfigurationService method. As this is a transient typed client, a new instance will be returned each time this method is called.

In ConfigureServices, we can register the factory with DI as a singleton:

As this is a singleton, we can consume this from any class where we need access to an instance of the typed client, even if that class is registered with singleton scope in DI.

The important thing here is that we don’t create an instance of the IConfigurationService and hold onto it for the lifetime of the singleton service. We can hold the IConfigurationServiceFactory and then we must use that whenever a method needs to get access to the IConfigurationService.

I’m sure there may be other ways to achieve similar results but I’m fairly happy with this approach for now.

Related Posts

Part 1 – An introduction to HttpClientFactory
Part 2 – IHttpClientFactory – Defining Named and Typed Clients
Part 3 – IHttpClientFactory – Outgoing request middleware with handlers
Part 4 – IHttpClientFactory – Integrating with Polly for transient fault handling

A Reminder to Take Care when Registering Dependencies

I looked into a “fun” little problem yesterday where we were seeing occasional errors in some of our ASP.NET Core code which calls down into the ASP.NET Core Identity UserManager. We were getting a range of NullReferenceException and ObjectDisposedExceptions as well as various exceptions from Npgsql.NpgsqlConnection (we use Postgres rather than SQL in this project) stating things such as “Connection already open”.

The issue was presenting itself within some authentication and authorisation code we have which wraps and extends the ASP.NET Core Identity UserManager and SignInManager functionality. We use ASP.NET Identity over a PostGres database for our user store but include some application specific functionality with our own code. We have our own AuthenticationManager and UserManager classes, both of which take dependencies on the underlying Microsoft.AspNetCore.Identity classes.

These originally got registered in in the ConfigureServices method of the Startup.cs class as follows:

services.AddSingleton<IAuthenticationManager, AuthenticationManager>();
services.AddSingleton<IUserManager, UserManager>();

The constructor for our AuthenticationManager looks a bit like this:

public AuthenticationManager(UserManager<ApplicationUser> userManager, SignInManager<ApplicationUser> signInManager)
   // setup our authentication manager here

Do you see the problem?

The issue here is the singleton registration. While our classes themselves have no state and could be shared between requests, the dependencies on Microsoft.AspNetCore.Identity.UserManager<T> and Microsoft.AspNetCore.Identity.SignInManager<T> have to be considered.

If we take a look at where these are registered within the Microsoft.AspNetCore.Identity source we can see the following:

services.TryAddScoped<UserManager<TUser>, UserManager<TUser>>();
services.TryAddScoped<SignInManager<TUser>, SignInManager<TUser>>();

They are added using the scoped lifetime which means they expect to be created once per request. They themselves depend on an IUserStore which is registered with the scoped lifetime as well.

As a result, the singleton registration of our AuthenticationManager was trying to hang onto dependencies to objects for the entire application lifetime, where those dependencies only expected to live for the request scope. Sometimes they seemed to get a different database context and hence the “Connection already open” errors we saw. Sometimes the dependencies had been disposed of by the time they got called by our code and as such we saw the various exceptions being thrown. In some cases we seemed to still have access to the UserManager but the context underneath was null. I won’t dive into this tool deeply, but it was apparent that we should really make the registration of our classes scoped as well. This way, they are created once per request, the same as their dependencies. We changed the registration code to the following:

services.AddScoped<IAuthenticationManager, AuthenticationManager>();
services.AddScoped<IUserManager, UserManager>();

This resolved the errors we were seeing immediately. It was an annoying oversight although fortunately it was fairly easy to guess at the cause. The various errors all suggested that we having some issues with the lifetime of the  dependencies. This case has been a reminder to carefully consider the lifetimes of service registrations as it can produce some unexpected errors and behaviour.

The Microsoft documentation even includes a large warning about scoped services – “The main danger to be wary of is resolving a Scoped service from a singleton. It’s likely in such a case that the service will have incorrect state when processing subsequent requests.” – Oh how true this is!

Happy dependency injecting!