I'm looking at the controllers in my website, and most of their constructors look like this:
public SomeController(
IServiceOne serviceOne,
IServiceTwo serviceTwo,
ILoggingService loggingService,
IGeospatialService geoSpatialService)
{
// copy to class variables.
}
In other words, it's very hairy and makes refactoring difficult. Some controllers have around 8 dependencies.
Is there any way i can somehow "group" these dependencies into one of more buckets?
For example, ILoggingService
is required in every controller, IGeospatialService
is required by controllers who do spatial stuff, and IServiceOne
and IServiceTwo
is only required in certain cases.
I would like to see something like this:
public SomeController(
ICoreServicesGroup coreGroup,
ISomeNameForServicesGroup serviceGroup)
{
// copy to class variables.
}
I'm thinking it would be good to introduce some OO techniques, such as having a "base" depedency class, which takes a ILoggingService
in it's protected ctor. Then you might have another child dependency which inherits, etc.
Has anyone done this before? Is this something StructureMap can do for me, or is it simply me rolling my own basic code?
Logging
When a dependency is required in each and every Controller it's a pretty certain indicator that it's not a 'normal' dependency, but rather a Cross-cutting Concern. Logging is the archetypical example of a Cross-cutting Concern, so ILoggingService
should be dealt with like any other Cross-cutting Concern.
In SOLID OO the appropriate way to address a Cross-cutting concern would be to employ a Decorator (which can be generalized towards AOP). However, ASP.NET MVC Controller action methods aren't part of any interface, so that's a less ideal solution.
Instead, the MVC framework provides Action Filters for interception purposes. If you want to implement a loosely coupled filter, do yourself a favor and implement it as a global filter instead of an attribute.
Other dependencies
For other dependencies it makes sense to refactor them to Facade Services. This involves identifying natural clusters of related services, so exactly how this is done is specific to each code base.
I know i accepted @Mark Seeman's answer a while ago, but i only finally got time to implement this now, so thought i'd share what i actually did, for other's benefit.
Basically, i created wrapper interfaces for the "groups" of dependencies in my application.
Example:
public interface ICoreServicesDependencyGroup
{
IUnitOfWork UnitOfWork { get; }
IAspNetMvcLoggingService LoggingService { get; }
}
And the implementation:
public class CoreServicesDependencyGroup : ICoreServicesDependencyGroup
{
private readonly IAspNetMvcLoggingService _loggingService;
private readonly IUnitOfWork _unitOfWork;
public CoreServicesDependencyGroup(
IAspNetMvcLoggingService loggingService,
IUnitOfWork unitOfWork)
{
Condition.Requires(loggingService).IsNotNull();
Condition.Requires(unitOfWork).IsNotNull();
_loggingService = loggingService;
_unitOfWork = unitOfWork;
}
public IUnitOfWork UnitOfWork { get { return _unitOfWork; } }
public IAspNetMvcLoggingService LoggingService { get { return _loggingService; } }
}
Pretty simple really.
Then i updated my controllers.
Example ctor Before:
public LocationController(
IUnitOfWork unitOfWork,
IAspNetMvcLoggingService loggingService,
ILocationService locationService,
ICachedLocationService cachedLocationService)
{
_unitOfWork = unitOfWork;
_loggingService = loggingService;
_locationService = locationService;
_cachedLocationService = cachedLocationService;
}
After:
public LocationController(
ICoreServicesDependencyGroup coreServicesDependencyGroup,
ILocationDependencyGroup locationDependencyGroup)
{
_unitOfWork = coreServicesDependencyGroup.UnitOfWork;
_loggingService = coreServicesDependencyGroup.LoggingService;
_locationService = locationDependencyGroup.Service;
_cachedLocationService = locationDependencyGroup.CachedService;
}
Nothing special really, just set of wrappers. Under the hood, the controllers still utilise the same dependencies, but the ctor signature is much smaller, more readable, and it also makes unit testing easier.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With