I have a ViewModel that contains a Product type and an IEnumerable< Product > type. I have one main view that displays the ViewModel.Product at the top of the page but then I have a partial view that renders the ViewModel.IEnumerable< Product > data. On the post the first level product object comes back binded from the ViweModel whereas the ViewModel.IEnumerable< Product > is coming back null.
Of course if I remove the partial view and move the IEnumerable< Product > view to the main View the contents comes back binded fine. However, I need to put these Enumerable items in a partial view because I plan on updating the contents dynamically with Ajax.
Why is the IEnumerable< Prouduct> property not getting binded when it’s placed in a partial view? Thx!
Models:
public class Product
{
public int ID { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
public class ProductIndexViewModel
{
public Product NewProduct { get; set; }
public List<Product> Products { get; set; }
}
public class BoringStoreContext
{
public BoringStoreContext()
{
Products = new List<Product>();
Products.Add(new Product() { ID = 1, Name = "Sure", Price = (decimal)(1.10) });
Products.Add(new Product() { ID = 2, Name = "Sure2", Price = (decimal)(2.10) });
}
public List<Product> Products { get; set; }
}
Views:
Main index.cshtml:
@model ViewModelBinding.Models.ProductIndexViewModel
@using (@Html.BeginForm())
{
<div>
@Html.LabelFor(model => model.NewProduct.Name)
@Html.EditorFor(model => model.NewProduct.Name)
</div>
<div>
@Html.LabelFor(model => model.NewProduct.Price)
@Html.EditorFor(model => model.NewProduct.Price)
</div>
@Html.Partial("_Product", Model.Products)
<div>
<input type="submit" value="Add Product" />
</div>
}
Parial View _Product.cshtml:
@model List<ViewModelBinding.Models.Product>
@for (int count = 0; count < Model.Count; count++)
{
<div>
@Html.LabelFor(model => model[count].ID)
@Html.EditorFor(model => model[count].ID)
</div>
<div>
@Html.LabelFor(model => model[count].Name)
@Html.EditorFor(model => model[count].Name)
</div>
<div>
@Html.LabelFor(model => model[count].Price)
@Html.EditorFor(model => model[count].Price)
</div>
}
Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
BoringStoreContext db = new BoringStoreContext();
ProductIndexViewModel viewModel = new ProductIndexViewModel
{
NewProduct = new Product(),
Products = db.Products
};
return View(viewModel);
}
[HttpPost]
public ActionResult Index(ProductIndexViewModel viewModel)
{
// work with view model
return View();
}
}
When you use
@Html.Partial("_Product", Model.Products)your input fields do not have correct names. For example instead of:you get:
Just look at your generated markup and you will see the problem. This comes from the fact that when you use
Html.Partialthe navigational context is not preserved. The input fields names are not prefixed with the name of the collection –Productsand as a consequence the model binder is not able to bind it correctly. Take a look at the following blog post to better understand the expected wire format.I would recommend you using editor templates which preserve the context. So instead of:
use:
and now move your
_Product.cshtmltemplate to~/Views/Shared/EditorTemplates/Product.cshtml. Also since the editor template automatically recognizes that the Products property is anIEnumerable<T>it will render the template for each item of this collection. So your template should be strongly typed to a single Product and you can get rid of the loop:Now everything works by convention and it will properly bind.