Service Locator Pattern to provide services to an abstract base class (ASP.NET Core)
Today I refactored a base class of service classes in an ASP.NET Core project, so that instead of two services (IHttpContextAccessor and IAuthorizationService in this example) being injected, an IServiceProviderWrapper service is injected instead, and that is used to resolve the two services inside the base class. This is an implementation of the Service Locator Pattern as I understand it in this context.
This refactor was motivated by wanting to simplify the interfaces of all of the classes derived from this base class. Each derived class previously had to have a constructor signature with the two services so that it could pass them to the parent constructor. With the Service Locator Pattern, only the IServiceProvider is needed in the constructor signature of all the service classes.
Here is what my base service class had previously:
So then every class derived from it had to pass the two arguments to the parent constructor, for example:
The child class is instantiated with those provided from dependency injection.
In unit tests, the system-under-test service had to have both supplied as well:
The change I made to try to make this interface that is needed everywhere for the derived services less inconvenient was the “service locator pattern.” The Wikipedia article suggests that this can be seen as an anti-pattern due to it obscuring dependencies, but also that this could be desirable:
[In some cases], the disadvantages may actually be considered as an advantage (e.g., no need to supply various dependencies to every class and maintain dependency configurations).
The implementation in my project was to use dependency injection to provide the IServiceProvider to the service base class, which is then used to access the dependency injection services to get the two services IHttpContextAccessor and IAuthorizationService which were being provided with dependency injection before.
Due to the Moq API not allowing mocking the extension methods GetService<T>, GetRequiredService<T> of IServiceProvider, I had to define an interface IServiceProviderWrapper to wrap that class to provide a non-extension method that could be mocked.
Here is the base service class, now receiving that as the constructor parameter instead of IHttpContextAccessor and IAuthorizationService parameters:
This of course requires registering an IServiceProviderWrapper implementation for dependency injection in Program.cs: builder.Services.AddScoped<IServiceProviderWrapper, ServiceProviderWrapper>();
Now all of the derived service classes have a smaller constructor signature:
The base class for service tests was refactored as well with this change to use the new service provider wrapper:
A unit test in a test class derived from that:
This does illustrate how dependencies are obscured by the pattern. Previously the dependency every derived service class had to IAuthorizationService and IHttpContextAccessor was explicit as they were explicitly constructor method parameters. Now with the IServiceProviderWrapper dependency being injected instead, it’s necessary to look into the base class to see that service provider is being used to access those services.
I think that simplifying the service interface in this way is worth obscuring those dependencies, and agree that doing so in this case is desirable as I would say it is hiding that complexity in the base class. If further work requires the base class have access to another service from the dependency injection container, with this design, that change can be made in the base class without changing the signature of every derived class.