如何使用RedirectToAction维护ModelState?


73

如果ModelState中存在错误,如何在不丢失我的ModelState信息的情况下返回不同操作的结果或将用户转移到其他操作?

场景是;删除动作接受由我的索引动作/视图呈现的DELETE表单中的POST。如果“删除”中存在错误,我想将用户移回“索引操作/视图”并在中显示“删除”操作存储的错误ViewData.ModelState。如何在ASP.NET MVC中完成此操作?

[AcceptVerbs(HttpVerbs.Post | HttpVerbs.Delete)]
public ActionResult Delete([ModelBinder(typeof(RdfUriBinder))] RdfUri graphUri)
{
    if (!ModelState.IsValid)
        return Index(); //this needs to be replaced with something that works :)

    return RedirectToAction("Index");
}

Answers:


100

将视图数据存储在TempData中,然后在Index动作中从那里检索它(如果存在)。

   ...
   if (!ModelState.IsValid)
       TempData["ViewData"] = ViewData;

   RedirectToAction( "Index" );
}

 public ActionResult Index()
 {
     if (TempData["ViewData"] != null)
     {
         ViewData = (ViewDataDictionary)TempData["ViewData"];
     }

     ...
 }

[编辑]我检查了MVC的在线源,并且看来Controller中的ViewData是可设置的,因此,将包括ModelState在内的所有ViewData都传输到Index操作可能是最简单的。


ViewData.ModelState没有设置器。
Eric Sc​​hoonover,

2
可行...谢谢!希望有一种更干净的方法可以执行此操作……也许这很干净,但似乎应该作为RedirectToAction覆盖或其他内容的一部分来执行一项常见任务。
Eric Sc​​hoonover,

6
你可以重构它而去actionfilters,这里建议(见子弹13):weblogs.asp.net/rashid/archive/2009/04/01/...
托马斯Aschan

5
注意事项:TempData默认使用Session。而且,如果您的会话由StateServer或SqlServer支持,则此技术在尝试序列化ViewData时会遇到问题,因为ViewDataDictionary类型未标记为Serializable。
乔恩·史宁

1
实际上,由于ViewDataDictionary不可序列化,因此如果“会话”不是“进程内”,则无法在ViewData中的请求之间保留ViewData,这使该解决方案与Web场,会话状态服务,cookie临时数据提供程序不兼容,等等:(
Novox

41

使用动作过滤器(PRG模式)(就像使用属性一样容易)

这里这里都提到。


3
IMO对这个问题的最佳答案。
Jean-Francois 2010年

2
是的,这就是答案。不确定为什么这些动作属性不在MVC框架中,因为这是很常见的情况
Chris S

我正在寻找ModelState.Merge()。很棒的链接。+1
ryanulit 2012年

到Rashid博客的链接已从ASP.NET中删除。好像很奇怪 但是,这里还讨论了他的解决方案和相关代码。
Marc L.

11

请注意,tvanfosson的解决方案并非总是可行,尽管在大多数情况下应该很好。

该特定解决方案的问题在于,如果您已经拥有任何ViewData或ModelState,最终将使用先前请求的状态将其全部覆盖。例如,新请求可能会有一些模型状态错误,这些错误与传递给操作的无效参数有关,但由于被覆盖,最终将被隐藏。

可能无法按预期运行的另一种情况是,如果您有一个操作筛选器初始化了一些ViewData或ModelState错误。同样,它们将被该代码覆盖。

我们正在寻找ASP.NET MVC的一些解决方案,这些解决方案将使您能够更轻松地合并两个请求的状态,因此请继续关注。

谢谢埃隆


Hey Eilon,@ bob的答案(来自Kazi Manzur Ra​​shid的博客)是否仍然是执行此操作的最佳方法,或者MVC团队目前是否建议其他方法?
帕特里克·麦克唐纳

1
@PatrickMcDonald我认为MVC中没有什么新功能可以解决此问题。但是,我要提醒您不要完全覆盖所有ViewData,而要对从先前请求复制到新请求的内容更加谨慎。
Eilon 2014年

ModelState具有一个Merge功能。
克里斯·海恩斯

5

如果这对我使用PRG的@bob推荐解决方案的任何人有用,则:

参见项目13->链接

我还有另一个问题,就是在VeiwBag中将消息传递给View,然后在执行操作时从控制器操作中的TempData手动写入和检查/加载该View RedirectToAction("Action")。为了简化(并使其可维护),我稍微扩展了此方法以检查和存储/加载其他数据。我的动作方法如下所示:

 [AcceptVerbs(HttpVerbs.Post)]
 [ExportModelStateToTempData]
 public ActionResult ChangePassword(ProfileViewModel pVM) {
      bool result = MyChangePasswordCode(pVM.ChangePasswordViewModel);
      if (result) {
           ViewBag.Message = "Password change success";
      else {
           ModelState.AddModelError("ChangePassword", "Some password error");
      }
      return RedirectToAction("Index");
    }

我的索引操作:

[ImportModelStateFromTempData]
public ActionResult Index() {
    ProfileViewModel pVM = new ProfileViewModel { //setup }
    return View(pVM);
}

动作过滤器中的代码:

// Following best practices as listed here for storing / restoring model data:
// http://weblogs.asp.net/rashid/archive/2009/04/01/asp-net-mvc-best-practices-part-1.aspx#prg
public abstract class ModelStateTempDataTransfer : ActionFilterAttribute {
    protected static readonly string Key = typeof(ModelStateTempDataTransfer).FullName;
}

public class ExportModelStateToTempData : ModelStateTempDataTransfer {
    public override void OnActionExecuted(ActionExecutedContext filterContext) {
        //Only export when ModelState is not valid
        if (!filterContext.Controller.ViewData.ModelState.IsValid) {
            //Export if we are redirecting
            if ((filterContext.Result is RedirectResult) || (filterContext.Result is RedirectToRouteResult)) {
                filterContext.Controller.TempData[Key] = filterContext.Controller.ViewData.ModelState;
            }
        }
        // Added to pull message from ViewBag
        if (!string.IsNullOrEmpty(filterContext.Controller.ViewBag.Message)) {
            filterContext.Controller.TempData["Message"] = filterContext.Controller.ViewBag.Message;
        }

        base.OnActionExecuted(filterContext);
    }
}

public class ImportModelStateFromTempData : ModelStateTempDataTransfer {
    public override void OnActionExecuted(ActionExecutedContext filterContext) {
        ModelStateDictionary modelState = filterContext.Controller.TempData[Key] as ModelStateDictionary;

        if (modelState != null) {
            //Only Import if we are viewing
            if (filterContext.Result is ViewResult) {
                filterContext.Controller.ViewData.ModelState.Merge(modelState);
            } else {
                //Otherwise remove it.
                filterContext.Controller.TempData.Remove(Key);
            }
        }
        // Restore Viewbag message
        if (!string.IsNullOrEmpty((string)filterContext.Controller.TempData["Message"])) {
            filterContext.Controller.ViewBag.Message = filterContext.Controller.TempData["Message"];
        }

        base.OnActionExecuted(filterContext);
    }
}

我意识到我的更改是对@State由@bob提供的链接@ModelState已经完成的工作的明显扩展-但我不得不偶然发现这个线程,甚至没有想到要以这种方式处理它。


谢谢,只需要编辑ImportModelStateFromTempData行即可获得部分视图>>如果(filterContext.Result是ViewResult || filterContext.Result是PartialViewResult)
k4st0r42

0

请不要让我for这个答案。这是一个合理的建议。

使用AJAX

用于管理ModelState的代码很复杂,并且(可能是?)指示代码中的其他问题。

您可以轻松滚动自己的AJAX javascript代码。这是我使用的脚本:

https://gist.github.com/jesslilly/5f646ef29367ad2b0228e1fa76d6bdcc#file-ajaxform

(function ($) {

    $(function () {

        // For forms marked with data-ajax="#container",
        // on submit,
        // post the form data via AJAX
        // and if #container is specified, replace the #container with the response.
        var postAjaxForm = function (event) {

            event.preventDefault(); // Prevent the actual submit of the form.

            var $this = $(this);
            var containerId = $this.attr("data-ajax");
            var $container = $(containerId);
            var url = $this.attr('action');

            console.log("Post ajax form to " + url + " and replace html in " + containerId);

            $.ajax({
                type: "POST",
                url: url,
                data: $this.serialize()
            })
                .done(function (result) {
                    if ($container) {
                        $container.html(result);
                        // re-apply this event since it would have been lost by the form getting recreated above.
                        var $newForm = $container.find("[data-ajax]");
                        $newForm.submit(postAjaxForm);
                        $newForm.trigger("data-ajax-done");
                    }
                })
                .fail(function (error) {
                    alert(error);
                });
        };
        $("[data-ajax]").submit(postAjaxForm);
    });

})(jQuery);

-3

也许尝试

return View("Index");

代替

return Index();

那是行不通的,因为它不执行索引动作中的逻辑。它所做的只是尝试使用“索引”视图渲染当前模型。
Eric Sc​​hoonover,

您是否只想在您发布的同一视图上显示模型错误?发生模型错误时,您需要在Index操作中执行什么操作?当出现错误时,我只返回View(“ ViewName”,model),并且工作正常。
Ty。

不,我想重定向到Index操作,将视图绑定到该操作生成的数据以及失败的Delete操作定义的ModelState。
Eric Sc​​hoonover,

我不理解反对票,因为这实际上是一个很好的答案,您可以将“逻辑”从索引带到为索引视图生成模型的服务。然后执行以下操作:返回View(“ Index”,modelObjectThatProducedByYourService)。将维护ModelState以便在Index视图中显示错误。
jannagy02 2013年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.