Search code examples
asp.net-mvcrazoractionmethod

Why does the action method get wrong parameter?


I have the Action method and the View for editing properties of some items.

    [HttpPost]
    [ValidateAntiForgeryToken]
    [Authorize(Roles = "Admin")]
    public async Task<ActionResult> Edit(Item item)
    {
        if (ModelState.IsValid)
        {
            db.Entry(item).State = EntityState.Modified;

            await db.SaveChangesAsync();
            return RedirectToAction("Index");
        }
        ViewBag.CatagorieId = new SelectList(db.Catagories, "ID", "Name", item.CatagorieId);
        return View(item);
    }    

and

@model OpenOrderFramework.Models.Item
@using OpenOrderFramework.Extensions
@{
    ViewBag.Title = "edit";
}

<h2>Editing</h2>


@using (Html.BeginForm())
{

    <div class="form-horizontal">
        <h4>The car</h4>
        <hr />
        @Html.ValidationSummary(true, "", new { @class = "text-danger" })
        @Html.HiddenFor(model => model.ID)
         <-- etc -->

But when I submit the form I get an error

Store update, insert, or delete statement affected an unexpected number of rows (0).

I figured out that in action method ID of the item that was posted is always 0 even if real ID of the item is different. enter image description here Why does it happen?

GET Action method:

    // GET: Items/Edit/5
     [Authorize(Roles = "Admin")]
    public async Task<ActionResult> Edit(int? id)
    {
        if (id == null)
        {
            return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
        }
        Item item = await db.Items.FindAsync(id);
        if (item == null)
        {
            return HttpNotFound();
        }
        ViewBag.CatagorieId = new SelectList(db.Catagories, "ID", "Name", item.CatagorieId);
        return View(item);
    }

Solution

  • When you post the form, the http call to your HttpPost action method is a totally separate Http request and Entity framework cannot track that entity.

    As Darin mentioned in the comment, It is not a good idea to mix the entity classes in your UI layer. That makes it very tightly coupled.

    What you should be using is creating and using a view model for your view. View model's are simply POCO classes, which is specific to the view.

    public class ItemViewModel
    {
      public int Id {set;get;}
      public string Name {set;get;}
      public List<SelectListItem> Categories { set;get;}
      public int SelectedCategory {set;get;}
    }
    

    And in your GET action, read the entity from your database,create an object of the view model and set the property values to that

    public ActionResult Edit(int id)
    {
       var vm=new ItemViewModel { Id=id };
       var item = db.Items.FirstOrDefault(s=>s.Id==id);
       if(item!=null)
       {
         vm.Name = item.Name;
       }
       vm.Categories =db.Categories.Select(s=> new SelectListItem { Value=s.Id.ToString(),
                                   Text=s.Name 
                                }).ToList();
       return View(vm);
    }
    

    And your view will be strongly typed to your view model

    @model ItemViewModel
    @using(Html.BeginForm())
    {
      @Html.DropdDownListFor(s=>s.SelectedCategory,Model.Categories,"Select")
      @Html.HiddenFor(s=>s.Id)
      @Html.TextBoxFor(s=>s.Name)
    
      <input type="submit" />
    }
    

    And in your HttpPost action, read the existing entity from your db and update the property values you want to update.

    [HttpPost]
    public ActionResult Edit(ItemViewModel model)
    {      
      if(ModelState.IsValid)
      {
         var item = d.Items.FirstOrDefault(s=>s.Id==model.Id);
         item.Name = model.Name;
         db.Entry(item).State = EntityState.Modified;
         db.SaveChanges();
         return RedirectToAction("Index");
      }
      model.Categories =db.Categories.Select(s=> 
              new SelectListItem { 
                                   Value=s.Id.ToString(),
                                   Text=s.Name }).ToList();
      return View(model);
    }
    

    Make sure to add enough NULL checkings before accessing the entities/ objects in the code.