Failing to pass data from view to the Action by Ht

2019-02-27 21:41发布

问题:

I am very new at asp.net mvc, so the reason behind my failure might be something basic as well, but I can't seem to find it after nearly a days work now.

What I am trying to do is to get the edited Model from the Index view and pass it to a second action which does not have view and returns return RedirectToAction("Index") in the related controller. In OrdersItemsController my Action is as the following:

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult MarkedShipped(IEnumerable<orders_items> orderItems)
{
    if (ModelState.IsValid)
    {
        foreach (var item in orderItems)
        {
            unitOfWork.GenericRepository<orders_items>().Update(item);
        }
    }
    return RedirectToAction("Index");
}

And in the Index.cshtml which is in OrdersItems folder in the Views, what I did is as following:

@model IEnumerable<Project.DataAccess.orders_items>
@{
    ViewBag.Title = "Index";
}

<h2>Index</h2>

@using (Html.BeginForm("MarkedShipped", "OrdersItems", new { orderItems = Model }))
{

    @Html.AntiForgeryToken()
    @Html.ValidationSummary(true)

    <table class="table">
        <tr>
            <th>
                @Html.DisplayNameFor(model => model.quantity)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.itemprice)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.trackingnumber)
            </th>
        </tr>

        @foreach (var item in Model)
        {
            <tr>
                <td>
                    @Html.DisplayFor(modelItem => item.quantity)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.itemprice)
                </td>
                <td>
                    @Html.EditorFor(modelItem => item.trackingnumber)
                </td>
                <td>
                    @Html.ActionLink("Edit", "Edit", new { id = item.itemid })
                </td>
            </tr>

        }
    </table>
    <div class="form-group">
        <div class="col-md-offset-2 col-md-10">
            <input type="submit" value="MarkShipped" class="btn btn-default" />
        </div>
    </div>
}

My problem is, I am not able to get the Model from the view with orderItems parameter, I am not sure if this is the right "syntax" for what I am trying to accomplish; but what I get for orderItems when the action is called is a List of orders_items with Count = 0 not a null value.

I have also checked if there is an application level exception, but got no exception from Application_Error in Global.asax

I am stuck for a day now so If anyone can point me a direction on how to pass the Model (or "the edited data") to the action for me to update the db, I would be very grateful. Thanks.

回答1:

Firstly you cannot add a model which is a collection (or a model which contains a property which is a complex object or collection) to a forms route parameters. Internally the helper calls the .ToString() method and if you inspect the html generated for your form tag you will see something like

<form action=OrdersItems/MarkedShipped?orderItems=System.Collection.Generic.......

and binding will fail since you collection cannot be bound to a string. Even if it did work, it would be rather pointless because it would just post back the original values of the model.

Next, you cannot use a foreach loop to generate form controls. Again if you inspect the html your generating you will see that the EditorFor() method is generating inputs with duplicate id attributes (invalid html) and duplicate name attributes which do not have the necessary indexers to bind to a collection when you post. You need to use either a for loop of a custom EditorTemplate for typeof orders_items

Using a for loop means that your model must implement IList<T> and the view needs to be

@model IList<Hochanda.CraftsHobbiesAndArts.DataAccess.orders_items>
@using (Html.BeginForm()) // only necessary to add the controller and action if they differ from the GET method
{
  ....
  @for(int i = 0; i < Model.Count; i++)
  {
    @Html.DisplayFor(m => m[i].quantity)
    ....
    @Html.EditorFor(m => m[i].trackingnumber)
  }
  ....
}

Using an EditorTemplate, create a partial in /Views/Shared/EditorTemplates/orders_items.cshtml (note the name of the file must match the name of the class)

@model Hochanda.CraftsHobbiesAndArts.DataAccess.orders_items
<tr>
  <td>@Html.DisplayFor(m => m.quantity)</td>
  ....
  <td>@Html.EditorFor(m => m.trackingnumber)</td>
  ....
</tr>

and then in the main view (you can use IEnumerable<T>)

@model IEnumerable<Hochanda.CraftsHobbiesAndArts.DataAccess.orders_items>
@using (Html.BeginForm())
{
  <table class="table">
    <thead>
      ....
    <thead>
    <tbody>
      @Html.EditorFor(m => m)
    </tbody>
  </table>
  ....
}

The EditorFor() method accepts IEnumerable<T> and will generate one row for each item in you collection based on the html in the EditorTemplate

In both cases, it you inspect the html you will now see the correct name attributes necessary for you model to bind when you post

<input type="text" name="[0].trackingnumber" ... />
<input type="text" name="[1].trackingnumber" ... />
<input type="text" name="[3].trackingnumber" ... />


回答2:

Try For instead of foreach here.

@for (int i = 0; i < Model.Count; i++)
        {
            <tr>
                <td>
                    @Html.DisplayFor(modelItem => item.quantity)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.itemprice)
                </td>
                <td>
                    @Html.EditorFor(modelItem => item.trackingnumber)
                </td>
                <td>
                    @Html.ActionLink("Edit", "Edit", new { id = item.itemid })
                </td>
            </tr>
        }

Check this :- http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx/



回答3:

You can do the following

 for (int i = 0; i < @ViewBag.Persons; i++)
  {

    <li>
        <table>

            <tr>
                <td>
                    @Html.LabelFor(m => m.Fullname,new { @id = "FullnameLabel" + @i })
                </td>
                <td>
                    @Html.TextBoxFor(m => m.Fullname, new { @id = "Fullname" + @i })
                </td>

            </tr>

In this way each html element gets its own unique id and its possible to retrieve the information back from them individually. You can use for or Foreach. Just make sure that your razor iterator is functioning. You can view the element ids in the source to see if it is working.