When the form in the below view is submitted the cart object
in session fails to update with the client side inputed values.
Am I missing something ? Is the custom model binder to blame ?
All the element names are 1:1 with the cart object property names and structure.
View Excerpt:
<% using (Html.BeginForm("UpdateCart", "Cart"))
{ %>
<% int index = 0; %>
<% foreach (var line in Model.Cart.Lines)
{ %>
<tr>
<%: Html.Hidden("Lines.Index", index) %>
<td align="center"><%: Html.TextBox("Lines[" + index + "].Quantity", line.Quantity)%></td>
<td align="left"><%: line.Product.Name%></td>
<td align="right"><%: line.Product.ListPrice.ToString("c")%></td>
<td align="right">
<%: (line.Quantity * line.Product.ListPrice).ToString("c")%>
</td>
</tr>
<%--<%: Html.Hidden("Lines[" + index + "].Product.ProductID", line.Product.ProductID)%>--%>
<% index++; %>
<% } %>
<%: Html.Hidden("returnUrl", Model.ReturnUrl) %>
<input type="submit" value="Update Cart" />
<% } %>
Cart Object:
public class Cart
{
private IList<CartLine> lines = new List<CartLine>();
public IList<CartLine> Lines { get { return lines; } set { lines = value; } }
}
public class CartLine
{
public Product Product
{
get;
set;
}
public int Quantity
{ get; set; }
}
Action Method that receives posted form data and should update the cart object by binding the form values:
[HttpPost]
public RedirectToRouteResult UpdateCart(Cart cart, string returnUrl)
{
return RedirectToAction("Index", new { returnUrl });
}
Custom model binder that instantiates cart in session and returns that instance for all controllers and methods to use.
public class CartModelBinder : DefaultModelBinder
{
private const string cartSessionKey = "_cart";
protected override void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
controllerContext.HttpContext.Session[cartSessionKey] = (Cart)bindingContext.Model;
}
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
//if (bindingContext.Model != null)
// throw new InvalidOperationException("Cannot update instances");
Cart cart = (Cart)controllerContext.HttpContext.Session[cartSessionKey];
if (cart == null)
{
cart = new Cart();
controllerContext.HttpContext.Session[cartSessionKey] = cart;
}
return cart;
}
}
The cutom modelbinder you created is called when you post your data to ther server, so your parameter cart of the action UpdateCart will be loaded with the data from client using the modelbinder. To use a custom modelbinder to update your session after the object is loaded you can do this:
In this modelbinder we are inheriting the defaultmodelbinder and overiding the OnModelUpdate event that happens after the model is binded, so in the OnModelUpdated we have the model with the values from the view and we can set the session.
But for the DefaultModelBinder do the bind you’ll have to change you view, the names of the inputs must match the names of the properties of your object, so instead of cart.Lines[” + index + “].Quantity you should use Lines[” + index + “].Quantity (do the same for the other properties). In you Cart object you’ll have to create a setter for the Lines property because without that the defaultbinder won’t be able to set the value inputed. You don’t need to call UpdateModel inside your view.