My ViewModel is not being properly rehydrated after failed vailidation on HttpPost resulting in a ‘Object reference not set to an instance of an object‘ error on ‘CartItems’ when the view is being re-displayed.
How do I rehydrate my ViewModel so it can be re-displayed with error messages?
I have tried using @Html.HiddenFor(...) to persist the non-displayed values (as per this answer) but this did not work.
ViewModel
public class CheckoutViewModel
{
public List<Cart> CartItems { get; set; }
[DisplayFormat(DataFormatString = "${0:F2}")]
public double CartTotal { get; set; }
public virtual Order Order { get; set; }
}
Controller GET: ActionResult
//GET: /Checkout/AddressAndPayment
public ActionResult AddressAndPayment()
{
var order = new Order();
order.Username = User.Identity.Name;
MembershipUser currentUser = Membership.GetUser(User.Identity.Name, true /* userIsOnline */);
storeDB.SaveChanges();
var cart = ShoppingCart.GetCart(this.HttpContext);
// Set up the ViewModel
var viewModel = new CheckoutViewModel
{
CartItems = cart.GetCartItems(),
CartTotal = cart.GetTotal(),
Order = order
};
// Return the view
return View(viewModel);
}
Controller POST: ActionResult
[HttpPost]
public ActionResult AddressAndPayment(CheckoutViewModel checkoutViewModel)
{
TryValidateModel(checkoutViewModel);
try
{
checkoutViewModel.Order.Username = User.Identity.Name;
checkoutViewModel.Order.OrderDate = DateTime.Now;
storeDB.Orders.Add(checkoutViewModel.Order);
// Error occurs on the following line if a custom data annotation validation attribute fails
storeDB.SaveChanges();
var cart = ShoppingCart.GetCart(this.HttpContext);
cart.CreateOrder(checkoutViewModel.Order);
storeDB.SaveChanges();
return RedirectToAction("Complete", new { id = checkoutViewModel.Order.OrderID });
}
catch
{
//Invalid - redisplay with errors
return View(checkoutViewModel);
}
}
Edit #1
@SoWeLie talked me through it and I have fixed the problem.
To rehydrate the cartItems in my ViewModel before passing it back to the View I added the following code to the catch block of my HttpPost ActionResult. This code basically calls methods which hit the DB to get the necessary info for the cart:
catch
{
//Invalid - redisplay with errors
//The model is not valid, we need to redisplay the same view so that the user can fix the errors => fetch the cartItems
var cart = ShoppingCart.GetCart(this.HttpContext);
checkoutViewModel.CartItems = cart.GetCartItems();
checkoutViewModel.CartTotal = cart.GetTotal();
return View(checkoutViewModel);
}
You wouldn’t want to persist entire objects such as a list of CartItem in hidden fields. Attempting to carry all that data around as post values is inefficient and difficult to maintain. Your cart object needs to be persisted somewhere within the application such as the database. You could also store the cart in the user’s session.
The best method for accomplishing this would be to save the user’s cart in the database. There are a number of ways you can do this. Without knowing your database model, the easiest solution would be to add the cart and its items to the database then take the cart id and store it either in the session or a cookie (depending on how long you want the cart to be persisted).