How can I maintain ModelState with RedirectToAction?

前端 未结 6 937
日久生厌
日久生厌 2020-12-02 05:57

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?

相关标签:
6条回答
  • 2020-12-02 06:34

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

    Use AJAX

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

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

    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);
    
    0 讨论(0)
  • 2020-12-02 06:36

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

    see item 13 -> link.

    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:

     [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.

    0 讨论(0)
  • 2020-12-02 06:39

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

    Mentioned here and here.

    0 讨论(0)
  • 2020-12-02 06:46

    Maybe try

    return View("Index");
    

    instead of

    return Index();
    
    0 讨论(0)
  • 2020-12-02 06:52

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

    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.

    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.

    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.

    Thanks, Eilon

    0 讨论(0)
  • 2020-12-02 06:54

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

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

    0 讨论(0)
提交回复
热议问题