带有IoC的MVC3:基本控制器中的参数太多。还有其他选择

时间:2013-04-03 10:57:25

标签: asp.net-mvc-3 c#-4.0 controller inversion-of-control controller-factory

我正在使用MVC 3.假设我有一个基本控制器和几个派生控制器。 我正在使用IoC,我的基本控制器构造函数如下所示:

private readonly ICacheProvider _cacheProvider;
private readonly ILoggerProvider _loggerProvider
private readonly IAuditProvider _auditProvider
public abstract class MyControllerBase : Controller
{
  protected MyControllerBase(ICacheProvider cacheProvider, ILoggerProvider loggerProvider, IAuditProvider auditProvider, ...)
  {
    _cacheProvider = cacheProvider;
    _loggerProvider = loggerProvider;
    _auditProvider = auditProvider;
    ...
  }
}

到目前为止一切顺利?也许。 但是,我的每个派生控制器都需要定义一个与基类构造函数签名匹配的构造函数,例如:

public class MyDerivedController1 : MyControllerBase
{
        public MyDerivedController1(ICacheProvider cacheProvider, ILoggerProvider loggerProvider, IAuditProvider auditProvider, ...)
        : base(cacheProvider, loggerProvider, auditProvider, ...)
    { }
}

这是我的问题,因为我必须在所有派生控制器中维护“详细”构造函数。 如果我需要添加一个新的提供程序,我必须重构所有派生的控制器。

我以为我会创建一个ServiceProvider(或Service Locator ?)类(和IServiceProvider接口),它将有一个构造函数和所有提供程序作为参数(IoC可以完成其工作)并公开他们作为财产。 然后我的基础构造函数和派生构造函数将只有IServiceProvider作为参数。

但是我担心这种方法会产生一些负面影响,例如: 1-隐藏实现:除非我检查实现,否则我不知道我使用或需要哪个提供程序。 2-难以测试:当构造函数包含参数时,我可以轻松地测试它,我知道会发生什么(自动记录)。

有没有人有任何建议或意见?

1 个答案:

答案 0 :(得分:0)

经过一些研究后,我无法找到一个好的解决方案。正如Daniel Hilgarth所提到的,控制器有太多的依赖关系,违反了SRP。我同意。 鉴于这是一个现有的应用程序,我无法重构和重新设计整个事物。 例如,我想将依赖关系(如ICacheProvider,ILoggerProvider和IAuditProvider)移动到另一个层,该层负责从存储库中检索数据。

我不想在这里开始新的讨论,但我不喜欢在我的MVC Web项目中引用Entity Framework的想法。我更愿意创建一个数据访问层并完全删除对EF的引用。 所以,回到我的问题,我有一个带有X依赖关系的基本控制器,所有派生控制器都需要一个包含所有这些引用的构造函数(如我上面的问题所述)。

我考虑了几个选项:

1)使用服务定位器。在阅读了不少帖子之后,我决定忽略这个选项。

Is it bad to use servicelocation instead of constructor injection to avoid writing loads of factory classes

IoC.Resolve vs Constructor Injection

http://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/

2)使用聚合服务。这是一个好主意,但我无法对依赖项进行分组,我也决定忽略这一点。

How to avoid Dependency Injection constructor madness?

http://blog.ploeh.dk/2010/02/02/RefactoringtoAggregateServices/

3)创建一个新的Provider,它将所有其他依赖项公开为属性。 我对此选项并不完全满意,因为它以某种方式隐藏了实现细节。但经过所有考虑后,我决定实施它。 在下面找到新界面和创建的类:

    public interface IMyControllerProvider
    {
    ICacheProvider CacheProvider { get; }

    ILoggerProvider LoggerProvider { get; }

    IAuditProvider AuditProvider { get; }

     ...
    }


    public class MyControllerProvider : IMyControllerProvider
    {
    private readonly ICacheProvider _cacheProvider;
    private readonly ILoggerProvider _loggerProvider;
    private readonly IAuditProvider _auditProvider;
    .....

    public MyControllerProvider(ICacheProvider cacheProvider, ILoggerProvider loggerProvider, IAuditProvider auditProvider, ...)
    {
       _cacheProvider = cacheProvider;
       _loggerProvider = loggerProvider;
       _auditProvider = auditProvider;
       ...
    }

    public ICacheProvider CacheProvider { get { return _context; } }
    public ILoggerProvider LoggerProvider { get { return _context; } }
    public IAuditProvider AuditProvider { get { return _context; } }
}

之后我重构了我的基本控制器和所有派生控制器以改为使用IMyControllerProvider。 它工作正常,因为:

1-派生控制器现在具有一个具有单一依赖关系的构造函数。

2-虽然新依赖 IMyControllerProvider “隐藏”了真正的依赖关系,但在测试控制器时,仍然很容易理解还有其他依赖关系(构造函数文档)。

    private readonly ICacheProvider _cacheProvider;
private readonly ILoggerProvider _loggerProvider
private readonly IAuditProvider _auditProvider
public abstract class MyControllerBase : Controller
{
    protected MyControllerBase(IMyControllerProvider myControllerProvider)
    {
        _cacheProvider = myControllerProvider.CacheProvider;
        _loggerProvider = myControllerProvider.LoggerProvider;
        _auditProvider = myControllerProvider.AuditProvider;
    }
}

public class MyDerivedController1 : MyControllerBase
{
        public MyDerivedController1(IMyControllerProvider myControllerProvider)
          : base(myControllerProvider)
    { }
}

不是最好的解决方案,但考虑到重构整个应用程序的限制,我唯一能提出的解决方案。