【问题标题】:Consolidating ASP.NET MVC Controller Dependencies (StructureMap)整合 ASP.NET MVC 控制器依赖项 (StructureMap)
【发布时间】:2011-12-08 04:23:27
【问题描述】:

我正在查看我网站中的控制器,它们的大多数构造函数看起来像这样:

public SomeController(
   IServiceOne serviceOne, 
   IServiceTwo serviceTwo, 
   ILoggingService loggingService, 
   IGeospatialService geoSpatialService)
{
    // copy to class variables.
}

换句话说,它非常繁琐,使重构变得困难。一些控制器有大约 8 个依赖项。

有什么方法可以让我以某种方式将这些依赖项“分组”到多个存储桶之一中?

例如,每个控制器都需要ILoggingService,执行空间操作的控制器需要IGeospatialService,而IServiceOneIServiceTwo 仅在某些情况下需要。

我希望看到这样的东西:

public SomeController(
       ICoreServicesGroup coreGroup,
       ISomeNameForServicesGroup serviceGroup)
    {
        // copy to class variables.
    }

我认为引入一些 OO 技术会很好,例如有一个“基础”依赖类,它在受保护的 ctor 中采用 ILoggingService。然后你可能有另一个继承的子依赖等等。

以前有人做过吗?这是 StructureMap 可以为我做的事情,还是只是我在滚动自己的基本代码?

【问题讨论】:

标签: c# asp.net-mvc dependency-injection dependency-management


【解决方案1】:

记录

每个控制器都需要一个依赖项时,这是一个非常确定的指标,表明它不是一个“正常”的依赖项,而是一个交叉关注。日志记录是横切关注点的典型示例,因此ILoggingService 应该像处理任何其他横切关注点一样处理。

SOLID OO 中,解决横切关注点的适当方法是使用Decoratorcan be generalized towards AOP)。但是,ASP.NET MVC 控制器操作方法不是任何接口的一部分,因此这是一个不太理想的解决方案。

相反,MVC 框架提供Action Filters 用于拦截目的。如果你想实现一个松耦合的过滤器,请帮自己一个忙,implement it as a global filter instead of an attribute

其他依赖项

对于其他依赖项,refactor them to Facade Services 是有意义的。这涉及识别相关服务的自然集群,因此具体如何完成取决于每个代码库。

【讨论】:

    【解决方案2】:

    我知道我不久前接受了@Mark Seeman 的回答,但我现在才终于有时间来实现它,所以我想我会分享我实际所做的事情,以造福他人。

    基本上,我为我的应用程序中的依赖“组”创建了包装接口。

    例子:

    public interface ICoreServicesDependencyGroup
    {
       IUnitOfWork UnitOfWork { get; }
       IAspNetMvcLoggingService LoggingService { get; }
    }
    

    以及实现:

    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; } }
    }
    

    真的很简单。

    然后我更新了我的控制器。

    之前的示例:

    public LocationController(
        IUnitOfWork unitOfWork,
        IAspNetMvcLoggingService loggingService, 
        ILocationService locationService, 
        ICachedLocationService cachedLocationService)
    {
        _unitOfWork = unitOfWork;
        _loggingService = loggingService;
        _locationService = locationService;
        _cachedLocationService = cachedLocationService;
    }
    

    之后:

    public LocationController(
        ICoreServicesDependencyGroup coreServicesDependencyGroup,
        ILocationDependencyGroup locationDependencyGroup)
    {
        _unitOfWork = coreServicesDependencyGroup.UnitOfWork;
        _loggingService = coreServicesDependencyGroup.LoggingService;
        _locationService = locationDependencyGroup.Service;
        _cachedLocationService = locationDependencyGroup.CachedService;
    }
    

    真的没有什么特别的,只是一组包装。在底层,控制器仍然使用相同的依赖项,但 ctor 签名更小,更易读,而且它还使单元测试更容易。

    【讨论】:

      【解决方案3】:

      我看到了几个选项:

      1. 提到了像 @32bitkid 这样的 Fascade 服务。
      2. 将控制器分解为更细粒度的操作方法组,这些操作方法具有更常见的依赖关系。
      3. 静态入口点。 (我知道很多人不喜欢它们,但我发现它们对于我的核心服务非常有用,即使使用 DI 也不会改变。)这是一个使用 Common Service Locator 的示例。

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

      用法:Logger.Instance().Log(message);

      测试:Logger.Instance = () =&gt; new TestLogger();

      【讨论】:

        猜你喜欢
        • 2010-12-03
        • 1970-01-01
        • 2013-02-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多