【问题标题】:Massive controller constructor argument list when using DI in MVC在 MVC 中使用 DI 时的大量控制器构造函数参数列表
【发布时间】:2013-01-26 19:14:04
【问题描述】:

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

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

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

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

编辑

我应该提到我们尝试遵循瘦控制器模式。此外,这些参数中的大多数往往是映射器——大约 66%。控制方法通常很简单,遵循以下任一模式:

  • 根据参数调用适当的服务或存储库
  • 使用映射器将结果转换为适当的视图模型
  • 将视图模型传递给视图

或者这个模式:

  • 从发布操作接收模型
  • 使用映射器将其转换为适当的域对象
  • 使用域对象调用适当的服务或存储库

【问题讨论】:

  • 你可以用injection by property代替injection by constructor
  • @HamletHakobyan - 嗯......这可能有效,我将不得不尝试看看它如何与 autofac 一起工作。在单元测试方面,我可以看到这个解决方案的好处。你为什么不把它作为一个答案。
  • Autofac 还支持动作注入,请参见此处stackoverflow.com/questions/5411881/…。因此,仅在一个操作中需要的参数可以这样完成。 60 还是很多 :)
  • 作为一般经验法则,构造函数应该有不超过 5 个依赖项。 60 个依赖项是依赖项的 12 倍。你如何测试一个有 60 个依赖项的类?您可能会从 business operations inside their own class 分组中受益。
  • Stevens 代码是我构建大量架构的基础。它确实清理了我的代码 + Jimmy Bogard 的一些映射内容、各种自定义 ActionResult 和一些自定义 ModelEnricher 工作。

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


【解决方案1】:

虽然我同意大多数其他答案 - 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.

【讨论】:

    【解决方案2】:

    60 个或更多的参数很多。

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

    你有一个胖控制器(不是 Thomas The Task Engine 那种),但控制器做得太多了。

    我寻找的平衡是 Fat Model 瘦控制器。伊恩·库珀在blog post

    中谈得很好

    您还可以查看诸如哪些参数实际上是横切关注点。

    例如,在我看来,映射和日志记录是横切关注点,因此您可能会使用操作过滤器来清理您的控制器。

    【讨论】:

    • 是的,我们实际上试图遵循瘦控制器原则,我想问题出在我们每个模型类都有单独的映射器这一事实,而不是少数存储库,我们有许多少方法存储库。但是使用映射器作为横切关注点似乎很有希望(日志记录不是问题)。您将如何使用动作过滤器进行映射?
    • 另见此问题stackoverflow.com/questions/2420193/… 并回答 Mark Seemann 和 Jimmy Bogard mvc conf2 关于瘦控制器的演示 youtube.com/watch?v=8TYJjRxXnXs
    • 就我个人而言,我将大部分业务逻辑移出控制器以保持它们的纤细。我通常使用简单的消息传递架构,其中控制器将命令发送到消息传递总线,命令处理程序执行命令。这样,每个用例都被很好地封装,可以自己测试,并且处理程序只有必要的依赖项来完成它的工作。您不必为此使用完全成熟的异步服务总线框架,只需一个简单的注册表消息类型 => 处理程序类型。您可以使用 DI 容器来解析处理程序以注入依赖项。
    • @GraemeMiller 我会检查这些链接 - Mark Seemann 写了“.NET 中的依赖注入”,所以他应该知道这个主题;)
    • 他确实做到了:)。正如我在回答中所说,我会考虑将您的映射包装在视图结果中。这会简化事情。
    【解决方案3】:

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

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

    例如:

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

    然后你的构造函数变成:

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

    注意,您可以注入公共属性。但是否要将其公开给外界取决于您。如果您不想破坏封装,那么您将使用工厂。

    【讨论】:

    • 您不是刚刚将长长的参数列表从控制器移到了工厂吗?它没有减少,它只是被移动了。
    • 所以您的实际问题不在于控制器的构造函数参数列表的大小.. 而是您的控制器具有的依赖项?
    • 另外,它也被减少了。您的工厂正在减少重复代码的数量。您的构造函数将有每个控制器一个参数和一个工厂中的 60 个属性 - 而不是每个控制器 60 个参数。
    • 这不是我的问题 :) 但我确实有同样的担忧:控制器构造函数中的依赖项太多
    • 对不起,我理解这个“不幸的是,随着时间的推移,那些构造函数参数列表往往会很快增长。我们的一些控制器现在希望有 60 个或更多参数”以表示对参数数量的关注。很抱歉没有回答您的问题。
    【解决方案4】:

    如果其中大部分归结为创建视图模型,这个问题和答案可能会有所帮助。

    MVC - Controller with multiple select lists

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

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

    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 并合并模型状态(PRG 模式使用类似 this 的东西),因此错误仍然存​​在。如果它是有效的命令处理程序处理它,我们将重定向到成功页面

    【讨论】:

      猜你喜欢
      • 2010-09-12
      • 1970-01-01
      • 1970-01-01
      • 2014-09-29
      • 2022-11-15
      • 1970-01-01
      • 1970-01-01
      • 2011-05-27
      • 2018-04-04
      相关资源
      最近更新 更多