如何保持您的MVC控制器DRY为编辑 - >保存 - > ValidateFail

时间:2013-08-20 21:13:38

标签: asp.net-mvc-4

我有一个Manage User事件,它带有一个可选的userID并显示一个用户编辑屏幕。这个屏幕有一个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 });

    }

5 个答案:

答案 0 :(得分:2)

我认为你误解了DRY。 DRY并不意味着“永远不要重复自己”,这意味着你不应该在没有意义的情况下重复自己。

不同的观点有不同的要求,创建一个复杂的结构只是为了避免重复自己违反了其他最佳实践,如KISS和SRP。

SOLID很有意思,因为单一责任原则经常与“不要重复自己”不一致,你必须想出一个平衡点。在大多数情况下,DRY会因为SRP更重要而失败。

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

我建议只为每个操作创建单独的控制器操作,视图和模型,特别是如果验证要求不同的话。您可以执行一些操作(例如使用部分视图或编辑器模板)来减少重复,但通常不会增加大量复杂性以避免重复。

答案 1 :(得分:1)

您可以将“Manager User”标题和“Save”OnSubmit字符串添加为ManageUserViewModel上的属性。这意味着每次调用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的类中,其实例可以使用Inversion of Control注入控制器。

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

答案 2 :(得分: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的一部分。

答案 3 :(得分:0)

如果您担心3行,可以使用RedirectToAction()然后导出并导入您的tempdata(以维护ModelState)。

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

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

答案 4 :(得分:0)

我们提出了另一种我认为可以分享的解决方案。

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

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

我们认为我们未来可能会为ajax甚至XML请求重用这些DTO - 它可以保持验证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 });
    }

模型绑定器会将任何URI变量设置为get动作中的dto。如果调用getUserByID(null),我的逻辑层将返回一个新的User对象。