我在思考如何在 ASP.NET MVC 中将我的 View 和操作分解为可管理的块时遇到了一些麻烦,我已经尝试过搜索,但我仍然不知道。

为了尝试了解这个特定方面,我创建了一个小测试项目,在该项目中,我尝试使用同一页面上的登录表单和注册表单示例来了解情况。我的 View 模型如下所示:

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)

...部分 View ...
@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>

无论如何,我当时想做的是,因为我有点想为每个帖子保持逻辑独立,是让每个表单发布到不同的 Action 。这就是我认为我大错特错的地方。

基本上,如果验证失败,我想我需要做一些事情来尝试并在加载页面时实际建立模型状态,但我有点得到了下面的内容,我有点迷失我应该采取什么方法反而。
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 时,我似乎根本没有得到验证。

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

更新:

我稍微改变了使用部分操作的方法,这似乎可以改善事情,代码如下:
    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");
    }

我的索引 View 现在是:
@{
    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 被重构,可能会进行大量的模型和代码重构以及 View 的重构。

我真的得到了一些替换默认模型绑定(bind)器的印象,以具有基于判别器的某种模型绑定(bind),并使 Controller Action 作为某种命令处理器工作,以便它会根据发布的部分触发正确的处理程序变得更好,并解决下面 Mystere Man 提到的重定向带来的刷新问题。

最佳答案

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

通常,仅将 TempData 用于诸如向用户显示一次警报或消息之类的事情。

使用像这样的局部 View 总是很痛苦,尤其是在尝试将子模型发布到不同的表单时,正如您所发现的那样。我这样做的方法是使用 EditorTemplates 而不是 Partials,然后将复合 View 模型发布到这两种方法。

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>
}

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

关于asp.net-mvc - 在 ASP.NET MVC 中使用多个部分处理复杂 View 中的模型状态和验证,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/16506454/

10-17 00:45