I have the following two action methods (simplified for question):
[HttpGet] public ActionResult Create(string uniqueUri) { // get some stuff based on uniqueuri, set in ViewData. return View(); } [HttpPost] public ActionResult Create(Review review) { // validate review if (validatedOk) { return RedirectToAction("Details", new { postId = review.PostId}); } else { ModelState.AddModelError("ReviewErrors", "some error occured"); return RedirectToAction("Create", new { uniqueUri = Request.RequestContext.RouteData.Values["uniqueUri"]}); } }
So, if the validation passes, i redirect to another page (confirmation).
If an error occurs, i need to display the same page with the error.
If i do return View()
, the error is displayed, but if i do return RedirectToAction
(as above), it loses the Model errors.
I'm not surprised by the issue, just wondering how you guys handle this?
I could of course just return the same View instead of the redirect, but i have logic in the "Create" method which populates the view data, which i'd have to duplicate.
Any suggestions?
Basically, use TempData to save and restore the ModelState object. However, it's a lot cleaner if you abstract this away into attributes. E.g. If you also want to pass the model along in TempData (as bigb suggested) then you can still do that too.
The error typically means that your Model doesn't meet the requirements to be validated. In your example, your FirstName , LastName , Email , Subject and Message properties are decorated with the [Required] attribute. This means that those values shouldn't be null and empty otherwise your condition if(ModelState.
IsValid is false now. That's because an error exists; ModelState. IsValid is false if any of the properties submitted have any error messages attached to them. What all of this means is that by setting up the validation in this manner, we allow MVC to just work the way it was designed.
Clear() is required to display back your model object. If you are getting your Model from a form and you want to manipulate the data that came from the client form and write it back to a view, you need to call ModelState. Clear() to clean the ModelState values.
I had to solve this problem today myself, and came across this question.
Some of the answers are useful (using TempData), but don't really answer the question at hand.
The best advice I found was on this blog post:
http://www.jefclaes.be/2012/06/persisting-model-state-when-using-prg.html
Basically, use TempData to save and restore the ModelState object. However, it's a lot cleaner if you abstract this away into attributes.
E.g.
public class SetTempDataModelStateAttribute : ActionFilterAttribute { public override void OnActionExecuted(ActionExecutedContext filterContext) { base.OnActionExecuted(filterContext); filterContext.Controller.TempData["ModelState"] = filterContext.Controller.ViewData.ModelState; } } public class RestoreModelStateFromTempDataAttribute : ActionFilterAttribute { public override void OnActionExecuting(ActionExecutingContext filterContext) { base.OnActionExecuting(filterContext); if (filterContext.Controller.TempData.ContainsKey("ModelState")) { filterContext.Controller.ViewData.ModelState.Merge( (ModelStateDictionary)filterContext.Controller.TempData["ModelState"]); } } }
Then as per your example, you could save / restore the ModelState like so:
[HttpGet] [RestoreModelStateFromTempData] public ActionResult Create(string uniqueUri) { // get some stuff based on uniqueuri, set in ViewData. return View(); } [HttpPost] [SetTempDataModelState] public ActionResult Create(Review review) { // validate review if (validatedOk) { return RedirectToAction("Details", new { postId = review.PostId}); } else { ModelState.AddModelError("ReviewErrors", "some error occured"); return RedirectToAction("Create", new { uniqueUri = Request.RequestContext.RouteData.Values["uniqueUri"]}); } }
If you also want to pass the model along in TempData (as bigb suggested) then you can still do that too.
You need to have the same instance of Review
on your HttpGet
action. To do that you should save an object Review review
in temp variable on your HttpPost
action and then restore it on HttpGet
action.
[HttpGet] public ActionResult Create(string uniqueUri) { //Restore Review review = TempData["Review"] as Review; // get some stuff based on uniqueuri, set in ViewData. return View(review); } [HttpPost] public ActionResult Create(Review review) { //Save your object TempData["Review"] = review; // validate review if (validatedOk) { return RedirectToAction("Details", new { postId = review.PostId}); } else { ModelState.AddModelError("ReviewErrors", "some error occured"); return RedirectToAction("Create", new { uniqueUri = Request.RequestContext.RouteData.Values["uniqueUri"]}); } }
If you want this to work even if the browser is refreshed after the first execution of the HttpGet
action, you could do this:
Review review = TempData["Review"] as Review; TempData["Review"] = review;
Otherwise on refresh button object review
will be empty because there wouldn't be any data in TempData["Review"]
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With