On Preserving State after Validation Failures in ASP.NET MVC
For those ASP.NET developers migrating from WebForms to MVC (or MonoRail), one of the first hurdles to overcome is the absence of ViewState. This hurdle is perhaps highest when working with a form that will represent itself after failed validation. For example, a page in a checkout sequence might require a user to enter a shipping address. If it’s an existing customer, the shipping address might be pre-populated. Let’s say the user inadvertently changes his or her address to something that fails our validation rules. On the server, a request was made, validation routines failed and there was a redirect back to the order page. At this point, the page should have maintained the state of the failed post data rather than the saved customer data as it exists in the database.
This scenario used to come for free with ViewState. We’d write code to check whether a request was a post back. If so, we didn’t reload the customer, but rather we allowed ViewState to repopulate the fields as submitted. A pattern that has served me well while working with MVC (or MonoRail) is to store the failed state in TempData (Flash for MonoRail) and reload it from there. In case you don’t know, TempData (or Flash) is simply a Dictionary with entries that survive a redirect. After the redirected action completes execution, the TempData (which is available to the view) is cleared.
This pattern is hardly novel and is one I’m sure i garnered by reading the Castle docs, but I’m posting it here because I think it should be in as many places as possible.
The pattern requires an View and Action for item edit and a view-less Action for Save. When Edit is called, TempData is checked for the existence of a transient entity. If it’s there, it’s used as the model for the View. Otherwise, the Model is loaded from the database (or set to a new instance when no ID is provided). Save sets the model in the TempData when the validation rules fail (intentionally contrived in this example).
public ActionResult Edit(int? id) {
try {
if (TempData["Customer"] != null) {
ViewData.Model = TempData["Customer"];
return View();
}
if (!id.HasValue || id.Value <= 0) {
ViewData.Model = new Customer();
} else {
ViewData.Model = CustomerDao.GetCustomer(id.Value);
}
} catch (Exception ex) {
TempData["Error"] = "Your error message here.";
}
return View();
}
public ActionResult Save([Bind(Prefix = "")]Customer customer) {
try {
if (customer.FirstName == "" || customer.LastName == "") {
TempData["Error"] = "First name and last name are required";
TempData["Customer"] = customer;
} else {
if (customer.CustomerID == 0) {
CustomerDao.CreateCustomer(customer);
} else {
Customer.UpdateCustomer(customer);
}
TempData["Message"] = "Customer successfully saved.";
}
} catch (Exception ex) {
TempData["Error"] = "Error saving Customer";
}
return RedirectToAction("Edit", new { id = customer.CustomerID});
}
RSS feed for comments on this post. | TrackBack URI
January 21st, 2009 at 10:35 pm
[…] to VoteOn Preserving State after Validation Failures in ASP.NET MVC (1/21/2009)Wednesday, January 21, 2009 from www.dllhell.netFor those ASP. NET developers migrating from […]