使用ASP.NET MVC中的多个部分在复杂视图中处理模型状态和验证

时间:2013-05-12 10:32:42

标签: asp.net-mvc asp.net-mvc-4

我在解决如何将我的视图和操作分解为ASP.NET MVC中可管理的块时遇到了一些麻烦,我尝试过搜索,但我仍然没有更聪明。

为了尝试并且只是围绕这个特定的方面我已经创建了一个小测试项目,我尝试使用登录表单的示例并在同一页面上注册表单来了解情况。我的观点模型如下所示:

public class LoginOrRegisterModel
{
    public LoginModel Login { get; set; }
    public RegisterModel Register { get; set; }

    public LoginOrRegisterModel()
    {
        this.Login = new LoginModel();
        this.Register = new RegisterModel();
    }
}

public class LoginModel
{
    [Required]
    public string UserName { get; set; }
    [Required]
    public string Password { get; set; }
}

public class RegisterModel
{
    [Required]
    public string UserName { get; set; }
    [Required]
    public string Password { get; set; }
}

然后我开始考虑主要行动。

    public ActionResult Index()
    {
        return View(new LoginOrRegisterModel());
    }

...并查看...

@model MvcSandbox.Models.LoginOrRegisterModel
@{
    ViewBag.Title = "Index";
}

<h2>Login Or Register</h2>

@Html.Partial("Login", model: Model.Login)
@Html.Partial("Register", model: Model.Register)

......部分观点...

@model MvcSandbox.Models.LoginModel
@{
    ViewBag.Title = "Login";
}

<h2>Login</h2>
@using (Html.BeginForm())
{
    @Html.ValidationSummary(true)
    @Html.EditorFor(model => model)
    <button>Login</button>
}

@model MvcSandbox.Models.RegisterModel
@{
    ViewBag.Title = "Register";
}

<h2>Register</h2>
@using (Html.BeginForm())
{
    @Html.ValidationSummary(true)
    @Html.EditorFor(model => model)
    <button>Register</button>
}

我发现我必须确保LoginOrRegisterModel的属性不为null,否则我收到错误:传入字典的模型项的类型为'MvcSandbox.Models.LoginOrRegisterModel',但此字典需要一个模型“MvcSandbox.Models.LoginModel”类型的项目。

到目前为止这很好,虽然目前不是很有用,因为两个表单都有相同的字段名称和ID,并且都回发到一个什么都不做的索引页面。

HTML来源:

<h2>Login Or Register</h2>

<h2>Login</h2>
<form action="/Membership" method="post"><div class="editor-label"><label for="UserName">UserName</label></div>
<div class="editor-field"><input class="text-box single-line" data-val="true" data-val-required="The UserName field is required." id="UserName" name="UserName" type="text" value="" /> <span class="field-validation-valid" data-valmsg-for="UserName" data-valmsg-replace="true"></span></div>
<div class="editor-label"><label for="Password">Password</label></div>
<div class="editor-field"><input class="text-box single-line" data-val="true" data-val-required="The Password field is required." id="Password" name="Password" type="text" value="" /> <span class="field-validation-valid" data-valmsg-for="Password" data-valmsg-replace="true"></span></div>
    <button>Login</button>
</form>

<h2>Register</h2>
<form action="/Membership" method="post"><div class="editor-label"><label for="UserName">UserName</label></div>
<div class="editor-field"><input class="text-box single-line" data-val="true" data-val-required="The UserName field is required." id="UserName" name="UserName" type="text" value="" /> <span class="field-validation-valid" data-valmsg-for="UserName" data-valmsg-replace="true"></span></div>
<div class="editor-label"><label for="Password">Password</label></div>
<div class="editor-field"><input class="text-box single-line" data-val="true" data-val-required="The Password field is required." id="Password" name="Password" type="text" value="" /> <span class="field-validation-valid" data-valmsg-for="Password" data-valmsg-replace="true"></span></div>
    <button>Register</button>
</form>

无论如何我接下来想要做的事情,因为我想保持每个帖子的逻辑分离,是让每个表单发布到不同的操作。而这就是我认为我可怕的错误。

基本上如果验证失败,我认为我需要做一些事情来尝试并在加载页面时实际建立备份模型状态,但我有点得到我在下面的内容,我有点迷失方向我应该采取相反的行动。

public class MembershipController : Controller
{
    //
    // GET: /Membership/

    public ActionResult Index()
    {
        if (TempData["ModelState"] != null)
            ModelState.Merge((ModelStateDictionary)TempData["ModelState"]);

        return View(new LoginOrRegisterModel());
    }

    [HttpPost]
    public ActionResult Login(LoginModel model)
    {
        if (ModelState.IsValid)
        {
            // do something
        }

        TempData["ModelState"] = ModelState;

        return RedirectToAction("Index");
    }

    [HttpPost]
    public ActionResult Register(RegisterModel model)
    {
        if (ModelState.IsValid)
        {
            // do something
        }

        TempData["ModelState"] = ModelState;

        return RedirectToAction("Index");
    }
}

......与观点......

@model MvcSandbox.Models.LoginModel
@{
    ViewBag.Title = "Login";
}

<h2>Login</h2>
@using (Html.BeginForm("Login", "Membership"))
{
    @Html.ValidationSummary(true)
    @Html.EditorFor(model => model)
    <button>Login</button>
}

@model MvcSandbox.Models.RegisterModel
@{
    ViewBag.Title = "Register";
}

<h2>Register</h2>
@using (Html.BeginForm("Register", "Membership"))
{
    @Html.ValidationSummary(true)
    @Html.EditorFor(model => model)
    <button>Register</button>
}

问题是因为每个模型都有相同名称的字段验证在两个表单上显示,无论提交哪个,当我尝试使用HtmlFieldPrefix时,我似乎根本没有验证。

关于如何将我的行为和观点分解为可管理和可维护的块而不让自己对模型状态和验证感到头疼的任何建议将不胜感激。

更新

我稍微改变了方法以使用似乎可以改进的部分操作,代码如下:

    public ActionResult Index()
    {
        return View();
    }

    public ActionResult Login()
    {
        if (TempData["LoginModelState"] != null)
            ModelState.Merge((ModelStateDictionary)TempData["LoginModelState"]);

        return View(new LoginModel());
    }

    [HttpPost]
    public ActionResult Login(LoginModel model)
    {
        if (ModelState.IsValid)
        {
            // do something
        }

        TempData["LoginModelState"] = ModelState;

        return RedirectToAction("Index");
    }

    public ActionResult Register()
    {
        if (TempData["RegisterModelState"] != null)
            ModelState.Merge((ModelStateDictionary)TempData["RegisterModelState"]);

        return View(new RegisterModel());
    }

    [HttpPost]
    public ActionResult Register(RegisterModel model)
    {
        if (ModelState.IsValid)
        {
            // do something
        }

        TempData["RegisterModelState"] = ModelState;

        return RedirectToAction("Index");
    }

我的索引视图现在是:

@{
    ViewBag.Title = "Index";
}

<h2>Login Or Register</h2>

@Html.Action("Login")
@Html.Action("Register")

登录并注册:

@model MvcSandbox.Models.LoginModel

<h2>Login</h2>
@using (Html.BeginForm("Login", "Membership"))
{
    @Html.ValidationSummary(true)
    @Html.EditorFor(model => model)
    <button>Login</button>
}

@model MvcSandbox.Models.RegisterModel

<h2>Register</h2>
@using (Html.BeginForm("Register", "Membership"))
{
    @Html.ValidationSummary(true)
    @Html.EditorFor(model => model)
    <button>Register</button>
}

现在对我来说,这比以前的方法更有优势,它实际上似乎工作了一点,并且不需要对LoginOrRegisterModel这个相当坦率的非常混乱的想法,这可能对于这样一个简单的例子很好但是会得到随着事情变得越来越复杂,UI被重构,可能会重构模型和潜在的代码以及只是视图。

我真的得到了一些替换默认模型绑定器的印象,以便基于描述符进行某种模型绑定,并使控制器操作作为某种命令处理器工作,以便它将触发基于哪个部分的正确处理程序被发布会更好,并解决由Mystere Man在下面提到的重定向引起的刷新问题。

2 个答案:

答案 0 :(得分:2)

不要使用重定向,因为您丢失了模型状态。我知道你试图通过在TempData中传递模型状态来解决这个问题,但问题是TempData仅对之后的一次访问有效。如果用户按下F5或点击刷新按钮,模型状态就会消失,事情会更加混乱。

通常,仅将TempData用于向用户显示警报或消息的内容。

使用像这样的部分视图总是很痛苦,特别是在尝试将子模型发布到不同的表单时,如您所知。我这样做的方法是使用EditorTemplates而不是Partials,然后将复合视图模型发布到两个方法。

public ActionResult Login(LoginOrRegisterModel model)
{
   if (ModelState.IsValid) {
      //  access only the Login properties, do same for Register
   }

   return View("LoginOrRegister", model)
}

在你看来

...
@Html.EditorFor(m => m.LoginModel)
@Html.EditorFor(m => m.RegisterModel)
〜/ Views / Membership / EditorTemplates / LoginModel.cshtml(和RegisterModel.cshtml)中的

@model MvcSandbox.Models.LoginModel
// Not sure why you were setting the title in a partial view, 
// particularly when you had two of them on a single page

<h2>Login</h2>
@using (Html.BeginForm("Login", "Membership"))
{
    @Html.ValidationSummary(true)
    @Html.EditorFor(model => model)
    <button>Login</button>
}

这样做的好处是它可以正确地将父模型绑定到正确的子模型,并且您可以从该点向前访问任何您想要的内容。

答案 1 :(得分:0)

你当然可以在一个“页面”上有多个模型和部分,并发布到单独的操作 - 这是我在这种情况下使用的方法。我也用“弹出”对话框,它工作正常。

关于模型,您基本上需要在“父”视图中使用复合模型(LoginOrRegisterModel),然后在每个“子”视图中使用相同的模型(只需使用您需要的位)在每个中)或者您将单独的子模型作为父模型(LoginModel,RegisterModel)之外的属性。这些都是有效的方法,但我认为你可以通过第二个选项(2个不同的子模型)获得更好的分离。

关于发布,我会使用AJAX执行单独的表单帖子,然后在发生错误时从控制器的各个帖子操作返回部分视图。由于您已经找到的问题,我不会尝试使用重定向来尝试重新呈现整个页面。