【问题标题】:Asp.Net MVC Actions - Separation of Concerns/Single Responsibility PrincipleAsp.Net MVC Actions - 关注点分离/单一责任原则
【发布时间】:2009-06-10 13:41:00
【问题描述】:

在计算机科学中,我们被教导说,每种方法都应该做一件事,而且只能做一件事。我有点困惑,我们看到像下面这样的 MVC 操作given as examples of good practice

    [AcceptVerbs(HttpVerbs.Post), Authorize]
    public ActionResult Edit(int id, FormCollection collection) {

        Dinner dinner = dinnerRepository.GetDinner(id);

        if (!dinner.IsHostedBy(User.Identity.Name))
            return View("InvalidOwner");

        try {
            UpdateModel(dinner);

            dinnerRepository.Save();

            return RedirectToAction("Details", new { id=dinner.DinnerID });
        }
        catch {
            ModelState.AddModelErrors(dinner.GetRuleViolations());

            return View(new DinnerFormViewModel(dinner));
        }
    }

基本上这段代码提供了很多功能:

  1. 定义如何访问操作 - 仅发布
  2. 定义谁可以访问操作 - 授权
  3. 访问持久性机制 -dinnerRepository
  4. 访问状态信息 - (User.Identity.Name)
  5. 将 NameValueCollection 转换为强类型对象 - UpdateModel()
  6. 为每个指定 3 个可能的 ActionResult 和内容 - InvalidOwner/Details/Edit 视图

对我来说,一种方法似乎承担了太多责任。这也是一个相当简单的操作,即它不处理常见的场景,例如:

  1. 检查业务规则 - “从不信任用户输入”
  2. 导航路径 - 成功保存后总是返回到“详细信息”
  3. 不同的返回类型 - 有人想从网格中调用“编辑”并需要 JsonResult?
  4. 更好的错误处理 - 如果在 GetDinner(id) 期间无法访问数据库,则会出现 YSOD
  5. 构建额外的视图数据 - 下拉列表的 SelectLists

更不用说围绕这种单一方法所需的测试量,即模拟/伪造 FormCollection/UserIdentity/Authorization Provider/Repository/等。

我的问题是我们如何避免在控制器操作中塞进这么多东西?

我倾向于认为"opinions" 是一个很棒的概念,尤其是“Thunderdome 原则”。虽然我非常尊重参与构建 FubuMVC 的人以及他们这样做的原因,但我需要一些我现在可以使用的东西。

编辑 - 看来我是在追求这样的东西 - Opinionated Controller。我需要进一步检查它,因为它适用于 MVC Preview 5,所以我可能需要自己更新它。

【问题讨论】:

    标签: asp.net-mvc separation-of-concerns


    【解决方案1】:

    对我来说,这个方法只做一件事:用从网络表单接收到的编辑值更新模型。

    很明显,这样做需要发生一些事情,但它们是原子的并且定义明确。如果您需要修改模型的这部分需要如何更新,这是要查找和更新的控制器操作。

    您可能会争辩说,由于此处检查了业务规则,因此不符合 Thunderdome 原则之一“控制器应该是轻量级的”。但是 NerdDinner 是一个非常琐碎的应用程序,将它放在额外的层中是没有意义的。

    如果你发现这个方法做的太多了,也许你应该找到一种语言,它禁止在一个方法中放置多个语句。

    【讨论】:

    • @Dave-我不同意,这个简单的例子至少做了两件事——更新模型并指定程序流程。如果这些要求中的任何一个发生变化,您需要更新此方法。我宁愿使用其他方法根据模型更新的结果确定程序流程。顺便说一句-让我们不要对 1 条语句 = 执行 1 件事太迂腐:)
    【解决方案2】:

    我对你的帖子有点困惑。首先你抱怨这个动作做得太多,然后你抱怨没有做得更多。

    编辑添加:

    老实说,这不是一个非常复杂的控制器动作。这是否是最佳实践示例尚有争议,但是,您可能不会比这更简单。我想你可以将其中的一些分解成单独的例程,但在某些时候你必须决定在哪里画这条线。最终,我们作为程序员必须编写软件。设计原则很棒,但如果我们对它们过于刻板,什么都不会建成。

    【讨论】:

    • @Chrisb - 不抱怨,我只是说在现实世界中,事情会比这个简单的例子复杂得多。我想知道处理这种额外复杂性的最佳方法是什么。
    • 实际上,这可能是您在此类操作中发现的复杂性的一个非常正常的示例。您确实提到了其他一些未解决的可能情况。如果您需要解决这些其他情况,您将重写操作来处理它们。我想你要考虑的是,这个动作应该完成什么?它负责从视图获取数据到模型,并向用户显示某种响应。如果你想做的是其中的一部分,那就太好了。如果不是,那么它可能应该去别的地方。
    • 现在,如果您在一堆动作中发生了共同的事情,例如在上面的示例中添加了 ModelErrors,您可以将其分离到一个单独的方法中。您发布的示例实际上就是这样做的。在示例应用程序中有一个名为 ControllerHelpers 的文件,其中包含用于此目的的代码。
    • @Chrisb 编辑 - 我知道这是一个简单的例子。恰恰相反,我写的大多数动作都复杂得多,我觉得好像我在一种方法中交织了太多的关注点。我知道我可以将事情下放到子例程/BaseClasses/ControllerHelpers 等中,但总的来说,我的 Actions 似乎“污染”了太多问题。因此测试一个动作变得更加困难。
    • 也许你可以发布一个例子来说明你的观点。当我在旧的 Jakarta Struts 中构建东西时,我想念的一件事是内置的动作方法。您对每个操作都有一个设置、验证和更新方法,因此如果需要,您可以更好地分离其中的一些内容。但大多数时候只使用其中一种方法。
    【解决方案3】:

    我认为它仍在执行所需的最少操作..因为这个 “操作” 可能不符合绝对单一责任 - 但它是单一操作

    1. 属性告诉 ASP.NET,此方法 仅适用于 HTTP.Post,并且尝试使用它的身份必须经过授权。 - 良好的安全性。所以在这一点上,实际上没有做任何事情。这些只是告诉服务器要检查什么。

      即如果不是 HTTP.post 方法将不起作用,如果您不是列表,不会起作用

    2. 有验证来检查用户身份是否与晚餐的身份匹配。 - 健全性检查。

    3. 这基于强类型检查 - 执行 UpdateModel(dinner) - 只是确保模型中的当前对象已使用新数据更新,然后调用存储库到 Save()。 - 这仍然是一个动作单元 - 更新模型以便我们可以调用保存和持久化。

    4. 在 Catch 中处理验证检查,该 Catch 将 RuleViolations 添加到模型中,并将用户返回到有问题的视图 - 即编辑/创建部分视图,该视图将责任传递给要处理的视图。

    5. 如果保存有效 - 它只是将用户的“工作流程”移动到详细信息 - 即从内存中清除表单并将工作移回详细信息。恕我直言,很棒 - 我们处于 POST 场景中,并且内存中没有的东西 - 很好。

    6. 不将流程移回细节并留在部分编辑视图上会更容易。

    【讨论】:

    • 我特别同意第六点。当然,大多数应用程序不会像 NerdDinner 那样以详细信息格式显示整个数据表,因此只需将用户返回到编辑视图即可。这种方法对于快速输入大量数据的情况会很方便。
    • @littlegeek - 您的回答对我提出的问题没有任何帮助。感谢您竭尽全力试图启发我了解此操作的工作方式/原因。问题是我仍然觉得对于这样一个微不足道的例子来说它混合了太多的问题,不管进一步的复杂性。我想我只是不同意“单一动作”应该处理请求/响应之间的这么多问题的概念。我宁愿将这些问题转移给特定的处理程序。
    【解决方案4】:

    我对“单一责任原则”的问题是人们并不总是对事件进行相同的细分——有些比其他的更细化。所以看起来,除了最琐碎的情况外,人们可以很容易地找到一个动作的更详细的视图。

    【讨论】:

      猜你喜欢
      • 2010-12-16
      • 1970-01-01
      • 1970-01-01
      • 2015-11-25
      • 1970-01-01
      • 2022-11-09
      • 1970-01-01
      • 2018-11-17
      • 1970-01-01
      相关资源
      最近更新 更多