5

我有一个管理用户事件,它接受一个可选的用户 ID 并显示一个用户编辑屏幕。这个屏幕有一个 manageUserViewModel 。

我的管理页面有一些依赖项 - 例如,PageTitle、提交的方法等。

如果我验证失败,我需要再次显示管理屏幕,但这一次,使用传递给同一方法的视图模型。

在失败场景中提供这些依赖项并不是很干燥。

如何逐步重复依赖项?我尝试将它们放入单独的方法中,但感觉不对。

public ActionResult Manage(Guid? UserID)
    {
        User user = this._UserLogic.GetUser(UserID);

        ViewBag.Title = "User List";
        ViewBag.OnSubmit = "Save";
        ManageUserViewModel uvm = Mapper.Map<User, ManageUserViewModel>(user);

        return View("Manage", uvm);
    }


    [AcceptVerbs("POST")]
    public ActionResult Save(ManageUserViewModel uvm)
    {
        User user = this._UserLogic.GetUser(uvm.UserID);


        if (!ModelState.IsValid)

            // This is not very DRY!!!
            ViewBag.Title = "Manage User";
            ViewBag.OnSubmit = "Save";
            return View("Manage", uvm);
        }

        Mapper.Map<ManageUserViewModel, User>(uvm, user );

        this._UserLogic.SaveUser(user);

        return RedirectToAction("Manage", new { UserID = user.ID });

    }
4

5 回答 5

2

我认为您误解了 DRY。DRY 并不意味着“永远不要重复自己”,它意味着你不应该在有意义的时候重复自己。

不同的视图有不同的要求,为了避免重复自己而创建复杂的结构违反了其他最佳实践,如 KISS 和 SRP。

SOLID 很有趣,因为单一职责原则经常与 Don't Repeat Yourself 相矛盾,你必须找到一个平衡点。在大多数情况下,DRY 会失败,因为 SRP 更为重要。

在我看来,您这里的代码处理了多个职责,这样您就可以避免多次编写类似的代码。我不同意这样做,因为每个视图都有不同的职责和不同的要求。

我建议只为每个操作创建单独的控制器操作、视图和模型,特别是在它们的验证要求不同的情况下。您可以做一些事情(例如使用部分视图或编辑器模板)来减少重复,但通常不要为了避免重复而增加很多复杂性。

于 2013-08-24T19:42:22.610 回答
1

您可以在 ManageUserViewModel 上添加“经理用户”标题和“保存”OnSubmit 字符串作为属性。这意味着您不必在每次调用 Save 时都将它们添加到 ViewBag。

您还可以创建一个 ManageUserService 来负责 AutoMapper 映射和保存用户。

您的代码将如下所示:

public ActionResult Manage(Guid? UserID)
{
    var uvm = _userService.GetById(UserId);

    return View("Manage", uvm);
}


[AcceptVerbs("POST")]
public ActionResult Save(ManageUserViewModel uvm)
{
    if (!ModelState.IsValid)
    {
        return View("Save", uvm);
    }

    _userService.Save(uvm);

    return RedirectToAction("Manage", new { UserID = uvm.ID });

}

只需将 CRUD 逻辑和 AutoMapping 功能放在名为 UserService 的类中,并且可以使用控制反转将其实例注入到控制器中。

如果您不想将字符串值硬编码到视图模型本身,则可以将值添加到 ApplicationResources 文件并从视图模型中引用这些值。

于 2013-08-20T21:20:11.413 回答
0

我们提出了另一个我认为我会分享的解决方案。

这基于包含有关它可以执行哪些操作的信息的视图模型,但我们认为控制器应该指定这些(即控制不同链接路由到的操作)这些,因为我们有视图模型跨操作重用的情况. 例如,当您编辑时可以编辑模板或某物的实例 - UI 是相同的,唯一的区别是您发布/取消的操作。

我们抽象出包含数据绑定属性的视图模型部分和包含视图渲染所需的其他内容的视图模型。我们将仅属性对象称为 DTO——它不是真正的 dto,因为它包含验证属性。

我们认为将来我们可能能够将这些 DTO 重新用于 ajax 甚至 XML 请求——它可以保持验证 DRY。

无论如何 - 这是一个代码示例,我们对它(现在)感到满意,并希望它对其他人有所帮助。

 [HttpGet]
    [ValidateInput(false)]
    public virtual ActionResult ManageUser(ManageUserDTO dto, bool PopulateFromObject = true)
    {
        User user = this._UserLogic.GetUser(dto.UserID);

        if (PopulateFromObject)
            Mapper.Map<User, ManageUserDTO>(user, dto);

        ManageUserViewModel vm = new ManageUserViewModel()
        {
            DTO = dto,
            PageTitle = Captions.GetCaption("pageTitle_EditUser"),
            OnSubmit = GetSubmitEventData(this.ControllerName, "SaveUser"),
            OnCancel = GetCancelEventData(this.ControllerName, "ListUsers"),
        };

        return View("ManageUser", vm); 
    }


    [HttpPost]
    public virtual ActionResult SaveUser(ManageUserViewModel vm)
    {
        User user = this._UserLogic.GetUser(vm.DTO.UserID);

        if (!ModelState.IsValid)
        {
            return ManageUser(vm.DTO, false);
        }

        Mapper.Map<ManageUserDTO, User>(vm.DTO, user);

        this._UserLogic.SaveUser(user);

        TempData.AddSuccess(Captions.GetCaption("message_UserSavedSuccessfuly"));
        return RedirectToAction("ManageUser", new { UserID = user.ID });
    }

model-binder 会将任何 URI 变量设置到 get 操作中的 dto 中。如果调用 getUserByID(null),我的逻辑层将返回一个新的用户对象。

于 2013-08-24T19:13:15.097 回答
0

您必须找到某种方法在请求之间保留此信息,这意味着在客户端和服务器之间来回传递它或将其保存在服务器上。将其保存在服务器上意味着类似于会话,但这对我来说有点沉重。您可以按照@Ryan Spears 的建议将其添加到您的 ViewModel 中。对我来说,这感觉有点不对,用可能被认为是元数据的东西污染了 ViewModel。但这只是一种观点,我不会因为他的回答是有效的而抹黑他的回答。另一种可能性是将额外的字段添加到操作方法本身的参数列表中并使用隐藏字段。

[AcceptVerbs("POST")]
public ActionResult Save(ManageUserViewModel uvm, string title, string onSubmit)
{
    ...
}

在表格中添加:

<input type="hidden" name="title" value="@ViewBag.Title" />
<input type="hidden" name="onSubmit" value="@ViewBag.OnSubmit" />

这与将它们添加到 ViewModel 本质上是相同的概念和解决方案,除非在这种情况下它们实际上不是 ViewModel 的一部分。

于 2013-08-20T21:31:02.687 回答
0

RedirectToAction()如果您担心这 3 行,您可以使用然后导出和导入您的临时数据(以维护 ModelState)。

如果您将逻辑保留在方法的 POST 版本中,我个人会发现它更具可读性,因为您执行的操作与 GET 方法略有不同,因此不会真正重复自己。您可以将ViewBag您拥有的两个变量保留在视图中,然后根本就没有重复。

作为旁注:[HttpPost]现在取代[AcceptVerbs]

于 2013-08-20T21:33:00.093 回答