2

So I've been working on a simple asp.net application this week. And today I had a problem with a razor view not returning anything to my controller method. The view had to return several objects in a List<>.

I finally got it working by passing a list of objects to the view and looping through them with a 'for' loop. I also tried it today with a foreach loop but that didn't work.

I've got the following GET method that passes a list of empty objects to the view:

// GET: MpSwitches/CreateSeveral
    public ActionResult CreateSeveral()
    {
        var mpSwitches = new List<MpSwitch>
        {
            new MpSwitch() {IpAdress = "1"},
            new MpSwitch() {IpAdress = "2"},
            new MpSwitch() {IpAdress = "3"},
            new MpSwitch() {IpAdress = "4"},
            new MpSwitch() {IpAdress = "5"}
        };
        // Check if the user gets redirected to this method because of a First Time Startup.
        if (TempData["RedirectFirstTimeStartup"] != null)
        {
            ViewBag.FirstTime =
                "Je bent doorgestuurd naar deze pagina omdat er nog geen MP-switches in de database staan.";
            return View(mpSwitches);
        }
        return View(mpSwitches);
    }

Then there is the View:

@model List<WebInterface.Models.MpSwitch>
@{
    ViewBag.Title = "Create several";
}
<h2>Create several</h2>

@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()

    <div class="form-horizontal">
        <h4>MpSwitch</h4>
        @if (ViewBag.FirstTime != null)
        {
            <div class="alert-warning">
                <b>Opgelet! </b>@ViewBag.FirstTime
            </div>
        }
        @for (int i = 0; i < Model.Count; i++)
        {
            <hr />
            @Html.ValidationSummary(true, "", new { @class = "text-danger" })
            <div class="form-group">
                @Html.LabelFor(model => model[i].IpAdress, htmlAttributes: new { @class = "control-label col-md-2" })
                <div class="col-md-10">
                    @Html.EditorFor(model => model[i].IpAdress, new { htmlAttributes = new { @class = "form-control" } })
                    @Html.ValidationMessageFor(model => model[i].IpAdress, "", new { @class = "text-danger" })
                </div>
            </div>
        }
        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Create" class="btn btn-default" />
            </div>
        </div>
    </div>
}

<div>
    @Html.ActionLink("Back to List", "Index")
</div>

My question is: Why doesn't this same code work with a 'foreach' instead of the 'for' loop?

The code that isn't working currently:

    @foreach (var item in Model)
    {
        <hr />
            @Html.ValidationSummary(true, "", new { @class = "text-danger"})
            <div class="form-group">
            @Html.LabelFor(modelItem => item.IpAdress, htmlAttributes: new { 
    @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(modelItem => item.IpAdress, new { 
    htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(modelItem => item.IpAdress, "", new 
    { @class = "text-danger" })
            </div>
        </div>
    }

Now when using the foreach loop the following controller method doesn't get a parameter (the parameter is null).

    // POST: MpSwitches/CreateSeveral
    // To protect from overposting attacks, please enable the specific 
    properties you want to bind to, for 
    // more details see https://go.microsoft.com/fwlink/?LinkId=317598.
    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult CreateSeveral([Bind(Include = 
    "Id,IpAdress,LastSyncDateTime")] List<MpSwitch> mpSwitches)
    {
        if (ModelState.IsValid)
        {
            foreach (var mpSwitch in mpSwitches)
            {
                db.MpSwitches.Add(mpSwitch);
            }
            db.SaveChanges();
            return RedirectToAction("Index");
        }
        return View(mpSwitches);
    }

And i get the following error: Link to image of my error

Jeroen
  • 1,625
  • 3
  • 16
  • 28

3 Answers3

0

The reason you're not getting is that when you use a foreach instead of a for, you don't have an index of the item and the names/ids of the HTML elements overlap each other. Take a look at the HTML source that you're sending to the browser (View Page Source or Inspect Element), and you'll notice that the name is MpSwitch[0].IpAddress, etc. When you use a foreach it will only be MpSwitch.IpAddress. That can't be serialized into a collection.

krillgar
  • 12,596
  • 6
  • 50
  • 86
0

The MVC ModelBinder needs the index of the list items to correctly bind them to the ViewModel.

If you use a for loop, the correct index is incorporated into the id and name attributes of the generated HTML <input> elements.

You can also incorporate the index explicitly, see Binding arrays with missing elements in asp.net mvc.

Community
  • 1
  • 1
Georg Patscheider
  • 9,357
  • 1
  • 26
  • 36
0

if you look at the rendered html when using foreach loop you will get this

<input class="form-control text-box single-line" id="item_IpAdress" name="item.IpAdress" value="" type="text">

and when you use for loop it will be like this

<input class="form-control text-box single-line" id="MpSwitch_0__IpAdress" name="MpSwitch[0].IpAdress" value="" type="text">

since you are using List<MpSwitch> so when you submit the form using for loop in MpSwitch[0].IpAdress new value of IpAddress will be assigned to MpSwitch on index 0 while in foreach loop value will be assigned to item.IpAdress and when you submit List<MpSwitch> will be null because to bind complex objects, you have to provide an index for each item. you can also check this Model Binding To A List

Usman
  • 4,615
  • 2
  • 17
  • 33