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

时间:2013-01-24 22:15:36

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

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

public abcController(
        ILogger logger,
        IabcRepository abcRepository,
        IabcService abcService,
        IMapper<AbcDomain, AbcViewModel> abcMapper,
        ...
        )

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

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

修改

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

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

或者这种模式:

  • 从帖子操作中接收模型
  • 使用mapper将其转换为适当的域对象
  • 使用域对象调用适当的服务或存储库

4 个答案:

答案 0 :(得分:8)

我无法真正谈论你应该如何重新架构你的控制器,尽管我同意大多数其他答案 - 60个传入参数很多。

可能有助于减少参数数量而非依赖数量的东西是Autofac所具有的the Aggregate Service support

您可以采用一个包含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<IMyAggregateService>();
builder.Register(/*...*/).As<IFirstService>();
builder.Register(/*...*/).As<ISecondService>();
builder.Register(/*...*/).As<IThirdService>();
builder.Register(/*...*/).As<IFourthService>();
builder.RegisterType<SomeController>();
var container = builder.Build();

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

Check out the wiki page for more details.

答案 1 :(得分:6)

60个或更多参数很多。

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

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

我寻找的平衡是Fat Model瘦身控制器。 Ian Cooper在blog post

中谈到了这一点

您还可以查看哪些参数实际上是交叉问题。

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

答案 2 :(得分:3)

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

MVC - Controller with multiple select lists

另外,我会看看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<ExaminationCreateModel>(new Examination ( _assetRepository.Find(assetId)));

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

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

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

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

答案 3 :(得分:2)

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

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

例如:

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

然后您的构造函数变为:

private ILogger _logger;

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

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