When clicking save to submit my form, my HTTPPost Edit Action receives a null value for IEnumerable<MedicalProduct> productList
. I am not sure what parameter to use for my edit Action to prevent nulls. I would try and determine the cause with some breakpoints but cannot find the spot before the parameter is assigned null.
Side Note:
I am adapting my code to use this new Model/ViewModel hierarchy. My Controller hasn't been completely tested with these new models, but I started testing the Edit Action, and received the null reference exception when trying to use the parameter IEnumerable<MedicalProduct> productList
in my Edit post action.
Another Side Note:
I am using a sub-ViewModel class MedicalProductViewModelLineItem
(haven't figured out a better name yet) inside my ViewModel MedicalProductViewModel
because I need a way to retrieve all the Brand names from the database with one database call, and then assign them one by one to the MedicalProductViewModelLineItem
.
EDIT: CODE UPDATE 10/22/13 5:14pm CST . The values produced in FormCollection.Keys
parameter in the HttpPost Edit
action method are now fixed. Now, values like "Products[0].Name" or "Products[0].ID" are produced in FormCollection.Keys
parameter instead of "p.Name" or "Name". However , the productList
parameter is still null
.
public class MedicalProductViewModel
{
public List<MedicalProductViewModelLineItem> Products { get; private set; }
//public SelectListItem BrandSelectListItem { get; private set; }
public void BuildViewModel(IEnumerable<MedicalProductViewModelLineItem> productsList, IEnumerable<Brand> brandList)
{
BuildProducts(productsList, brandList);
}
public void BuildViewModel(IEnumerable<MedicalProduct> productsList, IEnumerable<Brand> brandList)
{
BuildProducts(productsList, brandList);
}
private IEnumerable<SelectListItem> BuildSelectListItems(IEnumerable<Brand> brandList)
{
return brandList.Select(b => new SelectListItem()
{
Text = b.Name,
Value = b.ID.ToString()
});
}
private void BuildProducts(IEnumerable<MedicalProductViewModelLineItem> productList, IEnumerable<Brand> brandList)
{
var medicalProducts = productList.Select(p => new MedicalProduct()
{
BrandID = p.BrandID,
ID = p.ID,
Name = p.Name,
Price = p.Price
});
BuildProducts(medicalProducts, brandList);
}
private void BuildProducts(IEnumerable<MedicalProduct> productsList, IEnumerable<Brand> brandList)
{
Products = productsList.Select(p => new MedicalProductViewModelLineItem()
{
BrandID = p.BrandID,
BrandName = brandList.Single(b => b.ID == p.BrandID).Name,
BrandSelectListItem = BuildSelectListItems(brandList),
ID = p.ID,
Name = p.Name,
Price = p.Price
}).ToList();
}
}
// Sub-ViewModel of MedicalProductViewModel
// It gets displayed as one row on a view.
public class MedicalProductViewModelLineItem
{
[Key]
public int ID { get; set; }
[Required]
[StringLength(50)]
public string Name { get; set; }
[Required]
[DataType(DataType.Currency)]
public double Price { get; set; }
// is a foreign key
public int BrandID { get; set; }
public string BrandName { get; set; }
}
// DOMAIN MODEL
public class MedicalProduct
{
[Key]
public int ID { get; set; }
[Required]
[StringLength(50)]
public string Name { get; set; }
[Required]
[DataType(DataType.Currency)]
public double Price { get; set; }
// is a foreign key
public int BrandID { get; set; }
}
public class MedicalProductController : Controller
{
private MvcMedicalStoreDb _db = new MvcMedicalStoreDb()
//
// GET: /MedicalSupply/Edit/5
public ActionResult Edit(int id = 0)
{
MedicalProduct product = _db.Products.Find(id);
if (product == null)
{
return HttpNotFound();
}
var productList = new List<MedicalProduct> { product };
var viewModel = GetMedicalProductViewModel(productList);
return View(viewModel);
}
// ==========================================
// NULL REFERENCE EXCEPTION OCCURS IN THIS ACTION
// ==========================================
// POST: /MedicalSupply/Edit/5
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(IEnumerable<MedicalProductViewModelLineItem> productList, FormCollection values)
{
if (ModelState.IsValid)
{
foreach (var product in productList)
_db.Entry(product).State = EntityState.Modified;
_db.SaveChanges();
return RedirectToAction("Index");
}
var productViewModelList = GetMedicalProductViewModel(productList);
return View(productViewModelList);
}
protected override void Dispose(bool disposing)
{
_db.Dispose();
base.Dispose(disposing);
}
}
@model MvcMedicalStore.Models.MedicalProductViewModel
@{
ViewBag.Title = "Edit";
}
<h2>Edit</h2>
@using (Html.BeginForm()) {
@Html.AntiForgeryToken()
@Html.ValidationSummary(true)
<fieldset>
<legend>MedicalProduct</legend>
@for (int i = 0; i < Model.Products.Count(); i++)
{
@Html.EditorFor(m => m.Products[i])
}
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
<div>
@Html.ActionLink("Back to List", "Index")
</div>
@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
}
@model MvcMedicalStore.Models.MedicalProductViewModelLineItem
@Html.HiddenFor(item => Model.ID)
<div class="editor-label">
@Html.LabelFor(item => Model.Name)
</div>
<div class="editor-field">
@Html.EditorFor(item => Model.Name)
@Html.ValidationMessageFor(item => Model.Name)
</div>
<div class="editor-label">
@Html.LabelFor(item => Model.Price)
</div>
<div class="editor-field">
@Html.EditorFor(item => Model.Price)
@Html.ValidationMessageFor(item => Model.Price)
</div>
<div class="editor-label">
@Html.LabelFor(item => Model.BrandID)
</div>
<div class="editor-field">
@Html.DropDownListFor(item => Model.BrandID, Model.BrandSelectListItem)
@Html.ValidationMessageFor(item => Model.BrandID)
</div>
two images of the foreach
approach, and for
approach used in MedicalProductViewModel.cshtml, and the resulting key values of FormsCollection
parameter values
Use a BindAttribute
with Prefix
in your Controller Action like below:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Prefix = "Products")] IEnumerable<MedicalProductViewModelLineItem> productList)
{
if (ModelState.IsValid)
{
foreach (var product in productList)
_db.Entry(product).State = EntityState.Modified;
_db.SaveChanges();
return RedirectToAction("Index");
}
var productViewModelList = GetMedicalProductViewModel(productList);
return View(productViewModelList);
}
Change your view from foreach loop to for loop
@foreach (MvcMedicalStore.Models.MedicalProductViewModelLineItem p in Model.Products)
{
@Html.HiddenFor(item => p.ID)
//rest of the code.
}
to
@for ( i = 0; i < Model.count(); i++)
{
@Html.LabelFor(m => m[i].Name)
@Html.HiddenFor(m => m[i].Name)
@Html.LabelFor(m => m[i].Price)
@Html.EditorFor(m => m[i].Price)
//rest of the code.
}
[HttpPost]
public MedicalProductViewModel GetMedicalProductViewModel(ICollection<MedicalProduct> productList)
{
var brandList = _db.Brands.ToArray();
var mapper = new MedicalProductMapper();
return mapper.MapMedicalProductViewModel(productList, brandList);
}
Make it more explicit, specify
FormMethod.Post
@using (Html.BeginForm()) { }
to
@using (Html.BeginForm("Action", "Controller", FormMethod.Post, new { id = "forId" }))
{}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.