Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Consolidating ASP.NET MVC Controller Dependencies (StructureMap)

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?

like image 204
RPM1984 Avatar asked Dec 08 '11 04:12

RPM1984


2 Answers

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.

like image 181
Mark Seemann Avatar answered Nov 11 '22 09:11

Mark Seemann


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.

like image 3
RPM1984 Avatar answered Nov 11 '22 08:11

RPM1984