asp.net-mvc 如何使用 RedirectToAction 维护 ModelState?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/279665/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-07 22:51:27  来源:igfitidea点击:

How can I maintain ModelState with RedirectToAction?

asp.net-mvc

提问by Eric Schoonover

How can I return the result of a different action or move the user to a different action if there is an error in my ModelState without losing my ModelState information?

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

The scenario is; Delete action accepts a POST from a DELETE form rendered by my Index Action/View. If there is an error in the Delete I want to move the user back to the Index Action/View and show the errors that are stored by the Delete action in the ViewData.ModelState. How can this be done in ASP.NET MVC?

场景是;删除操作接受来自我的索引操作/视图呈现的 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");
}

回答by tvanfosson

Store your view data in TempData and retrieve it from there in your Index action, if it exists.

将您的视图数据存储在 TempData 中,并在您的 Index 操作中从那里检索它(如果存在)。

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

   RedirectToAction( "Index" );
}

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

     ...
 }

[EDIT] I checked the on-line source for MVC and it appears that the ViewData in the Controller is settable, so it is probably easiest just to transfer all of the ViewData, including the ModelState, to the Index action.

[编辑] 我检查了 MVC 的在线源,似乎控制器中的 ViewData 是可设置的,因此将所有 ViewData(包括 ModelState)传输到 Index 操作可能是最简单的。

回答by bob

Use Action Filters (PRG pattern) (as easy as using attributes)

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

Mentioned hereand here.

这里这里提到。

回答by Eilon

Please note that tvanfosson's solution will not always work, though in most cases it should be just fine.

请注意,tvanfosson 的解决方案并不总是有效,但在大多数情况下应该没问题。

The problem with that particular solution is that if you already have any ViewData or ModelState you end up overwriting it all with the previous request's state. For example, the new request might have some model state errors related to invalid parameters being passed to the action, but those would end up being hidden because they are overwritten.

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

Another situation where it might not work as expected is if you had an Action Filter that initialized some ViewData or ModelState errors. Again, they would be overwritten by that code.

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

We're looking at some solutions for ASP.NET MVC that would allow you to more easily merge the state from the two requests, so stay tuned for that.

我们正在研究 ASP.NET MVC 的一些解决方案,它们可以让您更轻松地合并来自两个请求的状态,因此请继续关注。

Thanks, Eilon

谢谢,伊隆

回答by Matthew

In case this is useful to anyone I used @bob 's recommended solution using PRG:

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

see item 13 -> link.

参见第 13 项 ->链接

I had the additional issue of messages being passed in the VeiwBag to the View being written and checked / loaded manually from TempData in the controller actions when doing a RedirectToAction("Action"). In an attempt to simplify (and also make it maintainable) I slightly extended this approach to check and store/load other data as well. My action methods looked something like:

我有一个额外的问题,即在执行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");
    }

And my Index Action:

还有我的索引操作:

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

The code in the Action Filters:

动作过滤器中的代码:

// 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);
    }
}

I realize my changes here are a pretty obvious extension of what was already being done with the ModelState by the code @ the link provided by @bob - but I had to stumble on this thread before I even thought of handling it in this way.

我意识到我在这里的更改是对 ModelState 已经通过代码@@bob 提供的链接所做的事情的一个非常明显的扩展 - 但在我什至想到以这种方式处理它之前,我不得不偶然发现这个线程。

回答by Jess

Please don't skewer me for this answer. It is a legitimate suggestion.

请不要因为这个答案而质疑我。这是一个合理的建议。

Use AJAX

使用 AJAX

The code for managing ModelState is complicated and (probably?) indicative of other problems in your code.

用于管理 ModelState 的代码很复杂,并且(可能?)表明您的代码中存在其他问题。

You can pretty easily roll your own AJAX javascript code. Here is a script I use:

您可以很容易地推出自己的 AJAX javascript 代码。这是我使用的脚本:

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

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);

回答by Ty.

Maybe try

也许试试

return View("Index");

instead of

代替

return Index();