ASP.Net MVC部分视图如何保持其模型状态?

9

这可能又是一个新手问题。

当我创建一个ASP.NET MVC2应用程序时,会像这样创建一个带有LogIn操作的Account Controller:

[HttpPost]
public ActionResult LogOn(LogOnModel model, string returnUrl)
{
   if (ModelState.IsValid)
   {
      if (MembershipService.ValidateUser(model.UserName, model.Password))
      {
         FormsService.SignIn(model.UserName, model.RememberMe);
         if (!String.IsNullOrEmpty(returnUrl))
         {
            return Redirect(returnUrl);
         }
         else
         {
           return RedirectToAction("Index", "Home");
         }
       }
       else
       {
          ModelState.AddModelError("", "The user name or password provided is incorrect.");
       }
     }

        // If we got this far, something failed, redisplay form
        return View(model);
    }

现在,我不想要一个登录页面,我想要将登录控件作为更大页面的一部分。因此,我将Login.aspx更改为Login.ascx,并使用Html.RenderPartial或Html.RenderAction将其集成到我的主视图中。
如果登录成功,两种方法都能很好地工作。如果登录失败,将返回登录错误消息。
return View(model)

这真让我头疼。我希望回到我的主页(称之为Home/Index),但是要同时显示部分视图的错误信息。

return RedirectToAction("Index", "Home")

显然不起作用。
提示?
5个回答

3
这绝不是一个新手问题,我已经查遍了网络以寻找这个问题的答案,到目前为止,我发现最好的解决方案有点隐藏在这个教程这里。这就是Darin Dimitrov用Ajax刷新所建议的方法。我会总结一下那个链接的重要部分,以及为什么这不容易修复:/

基于奇怪的情人的Ajax刷新

使用Ajax刷新的解决方案几乎取决于以下函数(奇怪的情人使用ControllerContext,但对我来说它不存在,所以我使用了ControllerExtension):

ControllerExtension.RenderPartialViewToString(this,"mypartial", (object)model)

这个函数的作用是将您的模型+模型状态重新呈现为HTML字符串的部分视图。然后,您可以将该字符串以json对象的形式发送回一些JavaScript来刷新视图。我使用了jQuery,代码如下:

$(document).ready(function () {
    var partialViewUpdate = function (e) {
            e.preventDefault(); //no postback
            var partialDiv = $(this).parent(".partial");
            $.post($(this).attr("action"),
                   $(this).serialize(),
                   function (json) {
                   if (json.StatusCode != 0) {
                       // invalid model, return partial 
                       partialDiv.replaceWith(json.Content);
                   }
                   else if (json.Content != null && json.Content != "") {
                       window.location.replace(data.Content);
                   };
           });

    $(".partial").find("form")
                 .unbind('submit')
                 .live("submit", partialViewUpdate);
};

Jquery解释:

  1. 查找包含我的部分的div(class="partial"),并查找该div中的表单
  2. 取消该表单上的任何其他“提交”事件(在取消绑定之前,我遇到了一些奇怪的双重提交错误)。
  3. 使用“live”,以便一旦替换内容,它就会重新绑定
  4. 进入函数partialViewUpdate后...
  5. 防止表单完成提交,以便可以通过ajax处理所有内容。
  6. 获取包含我的部分的div(稍后将使用此内容)
  7. 通过从表单中获取$(this).attr("action")来设置jquery post url
  8. 将表单(即我们的模型)序列化为控制器函数$(this).serialize()
  9. 创建将处理ajax返回值的函数。
  10. 我使用自己的个人json对象,其中StatusCode 1是不好的。所以如果它不好,那么我就取Content中的内容,这是RenderPartialViewToString给我的字符串,并且我只需替换包含我的部分的div的内容。

为什么通常情况下它不能“正常工作”

因此,部分视图与模型状态验证不起作用的原因是,您无法使用POST返回View(model),因为MVC将其解析为部分视图(login.ascx)的路由地址,而不是嵌入部分的位置(index.aspx)。

您也不能使用RedirectAction(),因为它会将其发送到(index.aspx)控制器函数,这相当于清除所有内容并刷新index.aspx页面。但是,如果使用Chino和Thabaza建议的ActionFilter,则在刷新页面并再次触发login.ascx控制器函数时,它将拾取该tempdata。但是,如果刷新页面会导致客户端代码(例如弹出式模态框)出现问题,则此方法无效(即如果刷新后,您的弹出式窗口将会消失)。

请不要这样

我更喜欢它“正常工作”,因此,如果有人知道正确/更好的方法,请分享!我仍然认为Ajax刷新和ActionFilter解决方案不是一种干净的方法,因为它几乎使得似乎使用带有表单的部分视图是不可能的,除非进行某种“技巧”。


1

是的,使用RedirectToAction,但同时在TempData中提供一个错误消息,你应该像这样做:

TempData["errorMsg"] = "incorrect values provided";
return RedirectToAction("Index", "Home")

当然,在主索引中,您应该有一个 div 元素来显示消息。
<%= html.Encode(TempData["errorMsg"]) %>

编辑 我看到你想要维护modelstate,这可能会有问题,但你可以在index操作中传递modelstate或将modelstate对象传递给tempdata。然后,您可以检查对象中是否存在modelstate错误,如果存在,则检查字段并将错误添加到正确的字段。


嗯,也许我可以把整个LogOnModel放在TempData中。但对我来说,这似乎是一种hack方法。这里有什么更好的解决方案呢? - Sparhawk
是的,这是一个hack,干净的解决方案是编写一个过滤器。查看此问题和我的答案以获取示例 http://stackoverflow.com/questions/2503032/where-to-use-controller-httpcontext/2503085#2503085 在过滤器中,您检查模型状态是否有效,如果无效,则重新添加值,当然最好将其放在自定义属性中,因为我认为您不需要在控制器的所有操作中都使用它。然后,您可以在要维护模型状态的操作上使用该属性。 - Chino
这就是TempData的作用。我猜你不喜欢魔术字符串和缺乏强类型数据。这很公平,但这只是一种风格选择,而不是黑客行为。 - a7drew

0

您可以明确指定要返回的视图:

return View("~/Views/Home/Index.aspx", model);

这样可以保留错误信息并呈现正确的视图。


如果我这样做,我会将LogOnModel传递给Home/Index视图。那可能不起作用。也许这个问题并不是那么适合新手 :) - Sparhawk
1
@Sparhawk,你是正确的。如果你返回Home/Index视图的模型会怎样呢?LogOn视图模型应该是Home/Index视图模型的子集。作为另一种选择,你可以将LogOn表单ajax化,并异步调用此操作,在成功的情况下,重定向将在客户端上执行(window.location.href),而在错误的情况下,它将返回一个“PartialView”,调用此操作的脚本将刷新包含此表单的“div”。 - Darin Dimitrov
1
目前它不是Home/Index视图模型的子集。我有一个门户网站,每个页面(如Home/Index)上都有许多控件。我使用RenderAction将控件呈现到页面中。那么,为什么Home/Index视图现在需要了解LogonModel呢? 当然,我可以使用ajax。但那感觉像是绕过问题。难道没有带有验证逻辑的部分视图的清洁解决方案吗? - Sparhawk

0
我使用Ajax.BeginForm时遇到了同样的问题,需要返回一个部分视图,但所有模型状态错误都消失了。 解决方法是将Ajax.BeginForm部分隔离到单独的视图中,并在另一个包含视图中的UpdateTargetId div中RenderPartial此视图。
这样,当您拥有模型错误时,可以返回ViewModel和模型错误,否则只需显示一些成功消息(如果有)。 以下是一个很好的详细说明: http://xhalent.wordpress.com/2011/02/05/using-unobtrusive-ajax-forms-in-asp-net-mvc3/

0

在这个博客上看一下练习题#13。当你在PRG(Post-Redirect-Get)风格编码时,这种方法可以很好地传递modelstate信息。你只需要创建一些操作过滤器,并根据需要将它们应用于你的get和post操作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接