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 的属性不为空,否则会出现错误:传递到字典中的模型项的类型为“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 被重构,可能需要大量重构模型和代码以及视图。

我真的得到了一些替换默认模型绑定器的印象,以具有某种基于描述符的模型绑定,并使控制器操作作为某种命令处理器工作,这样它就会根据发布的部分触发正确的处理程序更好,并解决下面神秘人提到的重定向带来的刷新问题。

4

2 回答 2

3

不要使用重定向,因为您会丢失模型状态。我知道您正在尝试通过在 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>
}

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

于 2013-05-12T19:04:24.360 回答
0

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

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

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

于 2013-05-12T12:41:45.000 回答