简体   繁体   English

如何使用 RedirectToAction 维护 ModelState?

[英]How can I maintain ModelState with RedirectToAction?

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. Delete操作接受来自我的Index操作/视图呈现的 DELETE 表单的 POST。 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 .如果Delete有错误,我想将用户移回Index Action/View 并显示由Delete操作存储在ViewData.ModelState How can this be done in ASP.NET MVC?如何在 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");
}

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 操作可能是最简单的。

Use Action Filters (PRG pattern) (as easy as using attributes)使用动作过滤器(PRG 模式)(就像使用属性一样简单)

Mentioned here and here .这里这里提到。

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

Another situation where it might not work as expected is if you had an Action Filter that initialized some ViewData or ModelState errors.它可能无法按预期工作的另一种情况是,如果您有一个 Action Filter 初始化了一些 ViewData 或 ModelState 错误。 Again, they would be overwritten by that code.同样,它们将被该代码覆盖。

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谢谢,伊隆

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") .在执行RedirectToAction("Action")时,我在控制器操作中从 TempData 手动写入和检查/加载了消息在 VeiwBag 中传递到视图的附加问题。 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:我的操作方法看起来像:

 [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 提供的链接所做的事情的一个非常明显的扩展 - 但在我什至想到以这种方式处理它之前,我不得不偶然发现这个线程。

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.你可以很容易地推出你自己的 AJAX javascript 代码。 Here is a script I use:这是我使用的脚本:

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

Maybe try也许试试

return View("Index");

instead of代替

return Index();

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM