Service Locator Pattern in .NET

What is the service locator pattern?

We can inject any interface into the constructor and use it in dependency injection. The default dependency injection container in .NET will handle the instantiation and disposal of objects.

If, for some reason, we are unable to inject those interfaces and we need to resolve those dependencies, we can utilize the service locator pattern as a solution.

In this pattern, we inject our service provider into the class and we can retrieve the N number of services using that single provider.

When should we use this pattern?

There are situations where this pattern could be helpful, but it should be used wisely in the following cases:

  1. When dealing with services scope mismatch issues

  2. When working with an old code base that requires extensive changes for update

  3. Sometimes when creating generic classes or maybe when types are not clear until runtime

How to use it in .NET?

We need to inject IServiceProvider in our desired class and then retrieve the required service by calling GetRequiredService method

public class UsersService
{
    private readonly IServiceProvider _serviceProvider;
	
    public UsersService(IServiceProvider ServiceProvider) 
    {
        _serviceProvider = ServiceProvider;
    }
}

Then we can use it at the controller level or method level :

public class UsersService
{
    private readonly IServiceProvider _serviceProvider;
    
    private readonly IUserRolesRepository _userRolesRepository;
	
    public UsersService(IServiceProvider ServiceProvider) 
    {
        _serviceProvider = ServiceProvider;
		
       _userRolesRepository =_serviceProvider
             .GetRequiredService<IUserRolesRepository>();
    }
	
    public void Method1()
    {
        var userRepository = _serviceProvider
                .GetRequiredService<IUserRepository>();
    }	
}

This is a very simple example of how we can use IServiceProvider, let’s move one step next.

Using a global implementation for a service provider

Instead of putting IServiceProvider everywhere, we can use a more universal approach by using a single class responsible for the services like this :

public class CachedServiceProvider 
{
	protected IServiceProvider ServiceProvider { get; }
	
	protected ConcurrentDictionary<Type, Lazy<object>> Services { get; }

	public CachedServiceProvider(IServiceProvider serviceProvider)
	{
	   ServiceProvider = serviceProvider;
		
	   Services = new ConcurrentDictionary<Type, Lazy<object>>();
		
	   Services
             .TryAdd(typeof(IServiceProvider), new Lazy<object>(() => ServiceProvider));
	}

	public virtual object GetService(Type serviceType)
	{
		return Services.GetOrAdd(
		  serviceType,
		_ => new Lazy<object>(() => ServiceProvider.GetService(serviceType)!)
		).Value;
	}
}

Using this approach our single class is responsible for providing services, and then we are using a dictionary to cache the services so the same service is not created again and again.

By using a concurrent dictionary we are making sure that it can accessed by multiple threads concurrently.

Is it bad practice to use this pattern?

Going with direct dependency injection should always be the first option because that is much cleaner, manageable, and easy to unit test.

But if that is not possible then we can opt for this pattern, like every pattern this also has some cons. But we can not say that using this is bad because it depends on the situation and use case.

Disadvantages of this pattern?

These are the disadvantages of using this pattern:

  • The biggest issue is testing, mocking and testing can become more complex because the service locator is often a singleton or a global instance.

  • It can make dependencies less obvious, as they are not directly visible in the code where services are used.

  • Over-reliance on the service locator can lead to poor design choices, where everything is fetched from the locator, leading to an anemic domain model

This article was originally published at https://mwaseemzakir.substack.com/ on .

Whenever you're ready, there are 3 ways I can help you:

  1. Subscribe to my youtube channel : For in-depth tutorials, coding tips, and industry insights.
  2. Promote yourself to 9,000+ subscribers : By sponsoring this newsletter
  3. Patreon community : Get access to all of my blogs and articles at one place