在MVC中使用DI时的大规模控制器构造函数参数列表

我正在使用ASP.NET MVC3解决方案,该解决方案使用dependency injection与autofac。 我们的控制器是由autofac正确创建的,所有必需的对象都被正确传入。这些对象通常包括将域对象转换为MVC(视图)模型的服务,存储库和映射器。 所以控制器构造函数看起来有点像:

public abcController( ILogger logger, IabcRepository abcRepository, IabcService abcService, IMapper abcMapper, ... ) 

不幸的是,随着时间的推移,这些构造函数参数列表往往会很快增长。 我们的一些控制器现在需要60个或更多参数。

我们在这里创造了一些反模式吗?

编辑

我应该提到我们试图遵循薄的控制器模式。 此外,大多数参数往往是映射器 – 大约66%。 控制方法通常非常简单,并遵循以下模式:

  • 基于参数调用适当的服务或存储库
  • 使用mapper将结果转换为适当的视图模型
  • 传递视图模型进行查看

或者这种模式:

  • 从后期行动中接收模型
  • 使用mapper将其转换为适当的域对象
  • 使用域对象调用适当的服务或存储库

60个或更多参数很多。

在您的问题中,您说“..这些对象通常包括将域对象转换为MVC(视图)模型的服务,存储库和映射器……”

你有一个胖控制器(不是托马斯的任务引擎类),而是一个做得太多的控制器。

我寻找的平衡是Fat Model瘦的控制器。 Ian Cooper在这篇博文中谈到了这一点

您还可以查看哪些参数实际上是跨领域问题。

例如,我认为Mapping和Logging是跨领域的问题,因此您可以使用Action Filters来清理控制器。

我真的不能说你应该如何重新架构你的控制器,虽然我同意大多数其他答案 – 60个传入的参数是很多。

可能有助于减少参数数量而不是依赖数量的东西是Autofac具有的Aggregate Service支持 。

您可以采用一个包含60个属性的聚合参数,而不是直接获取60个参数。

您可以使用依赖项创建一个接口(只是接口,您实际上不必实现它):

 public interface IMyAggregateService { IFirstService FirstService { get; } ISecondService SecondService { get; } IThirdService ThirdService { get; } IFourthService FourthService { get; } } 

然后修改您的控制器以获取该聚合接口:

 public class SomeController { private readonly IMyAggregateService _aggregateService; public SomeController( IMyAggregateService aggregateService) { _aggregateService = aggregateService; } } 

您可以注册聚合服务接口,依赖项和控制器,当您解析控制器时,将自动为您实施和解决聚合服务接口。

 var builder = new ContainerBuilder(); builder.RegisterAggregateService(); builder.Register(/*...*/).As(); builder.Register(/*...*/).As(); builder.Register(/*...*/).As(); builder.Register(/*...*/).As(); builder.RegisterType(); var container = builder.Build(); 

同样,它并没有涉及要求许多依赖项的更大问题,但如果您只是想简化构造函数和控制器上的属性数量以使其更易于管理,这是Autofac提供的一个策略。 。

查看维基页面了解更多详情。

如果这很大程度上取决于创建视图模型,那么这个问题和答案可能有所帮助。

MVC – 具有多个选择列表的控制器

另外我还会看看Manning的MVC 4 in Action。 它包括创建一个自动化映射的ActionResult。

在我的应用程序中,我的大多数控制器操作都是一行。 拉动实体并将其传递给Automapping并丰富viewresult或接受命令并将其传递给处理它的操作结果

这篇来自Jimmy的博客文章涵盖了一些POST方面http://lostechies.com/jimmybogard/2011/06/22/cleaning-up-posts-in-asp-net-mvc/

基本上我得到一个域对象(来自repo或其他方法)并返回一个自动映射结果,该结果映射到适当的VM。

 return AutoMappedView(new Examination ( _assetRepository.Find(assetId))); 

映射器ViewResult然后将它传递给一个更丰富的(如果找到一个实现IModelEnricher。请参阅其他堆栈问题。

返回时它作为命令回发,然后命令处理有点像Bogard post。

  public virtual ActionResult Create(AddAssetExaminationCommand addAssetExaminationCommand, ICommandHandler addExaminationHandler) { return ProcessForm( addAssetExaminationCommand, addExaminationHandler, RedirectToAction(MVC.OnboardAsset.Examinations.Create()), RedirectToAction(MVC.OnboardAsset.Examinations.Index(addAssetExaminationCommand.AssetId))); } 

如果validation失败,那么它会重定向到GET并合并Modelstate(PRG模式使用类似的东西),因此错误仍然存​​在。 如果它是有效的命令处理程序处理它,我们重定向到成功页面

(免责声明:这个答案是关于参数列表的大小。它不会减少控制器内的依赖关系)

在这种情况下,您将注入一个工厂。

例如:

 interface IABCFactory { ILogger CreateLogger(); IABCRepository CreateRepo(); // .. etc } 

然后你的构造函数变为:

 private ILogger _logger; public abcController(IABCFactory factory) { _logger = factory.CreateLogger(); // .. etc } 

请注意,您可以注入公共属性..但是您是否想要将其暴露给外部世界取决于您。 如果你不想破坏封装,那么你会去工厂。