Consolidating ASP.NET MVC Controller Dependencies (StructureMap)

拜拜、爱过 提交于 2019-12-04 03:14:10
Mark Seemann

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.

I see a few options:

  1. Fascade services like @32bitkid mentioned.
  2. Break your controllers into finer grained groups of action methods that have more common dependencies.
  3. Static entry points. (I know many people don't like them, but I find them quite useful for my core services that never change, even with DI.) Here's an example utilizing the Common Service Locator.

public class Logger
{
    public static Func<ILoggerService> Instance = () => ServiceLocator.Current.GetInstance<ILoggerService>();
}

Usage: Logger.Instance().Log(message);

Testing: Logger.Instance = () => new TestLogger();

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!