ASP.NET Core service locator pattern

There are many posts out there which specifically say that we should not use service locator pattern (unless necessary), which we would not be able to determine the dependencies during compile time because the dependencies are hidden. However, I am going to point out an issue in case if you need to use it, in the context of ASP.NET Core projects.

I have a class Foo, which will be bind to IFoo, and I will need to configure a named HttpClient for that class via the following extension,

 services.AddHttpClient<IFoo, Foo>();

If we do the above, Foo will be treated as an object which is Singleton injected. In case you are not aware, we cannot inject services which has a smaller lifespan than the service itself. Hence, we cannot inject services which are transient or scoped into Foo. Here comes the problem, what should we do?

That's where service locator pattern comes to the rescue! There might be other way, but let's say we decide to use the service locator provided by the framework, IServiceProvider. When we want to utilize a scoped service in a Singleton injected class, we could do something like the following,

public class Foo
{
    private readonly IServiceProvider _serviceProvider;

    public Foo(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public async Task DoMethod()
    {
        using (var scope = _serviceScopeFactory.CreateScope())
        {
            var scopedService = scope.ServiceProvider.GetService<IMyScopedService>();
            scopedService.PerformSomething();
        }
    }
}

There is nothing wrong with the code above. However, in case if IMyScopedService is depending on other services which are living on scoped lifespan, then we might have an issue. Since I am creating a new scope to access IMyScopedService, the scoped service injected into IMyScopedService when a request comes in live in a different context.



The solution is.....IHttpContextAccessor! The idea is that HttpContext contains the information of a particular request, and this includes all the injected service. If we use IHttpContextAccessor to resolve a particular scoped service (in this case IMyScopedService), it would be the IMyScopedServiec shared throughout the request. So the above code could be modified to something like below,





public class Foo
{
    private readonly IHttpContextAccessor _httpContextAccessor;

    public Foo(IHttpContextAccessor httpContextAccessor;)
    {
        _httpContextAccessor = httpContextAccessor;
    }

    public async Task DoMethod()
    {
        var scopedService = _httpContextAccessor.HttpContext.RequestServices.GetRequiredService<IMyScopedService>();
        scopedService.PerformSomething();
    }
}

So I hope this article helps in some way, and just a friendly reminder, you would need to inject IHttpContextAccessor manually if you want to use it as a service.
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();

Comments

Popular posts from this blog

Ways to perform eager loading via NHibernate

Caching and generic query using NHibernate [.NET Core]