简体   繁体   中英

Parameter in HttpPost edit action is null

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 .


Model Classes


MedicalProductViewModel

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();
    }
}

MedicalProductViewModelLineItem

// 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; }
}

MedicalProduct

// 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; }
}

Controller


MedicalProductController

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);
    }
}

Views


Edit.cshtml

@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")
}

EditorTemplates\\MedicalProductViewModelLineItem.cshtml

@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>

EDIT: (Pictures obsolete)

two images of the foreach approach, and for approach used in MedicalProductViewModel.cshtml, and the resulting key values of FormsCollection parameter values

使用for方法使用foreach方法

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM