rst
rst

Reputation: 2714

Dynamically add ICollection Items to Model while "creating" in MVC

Found a solution, but a very ugly one, see at the bottom, can someone improve that solution?

So I have a dummy Model like this one

public class TestModel
{
    public int TestModelID { get; set; }
    public string Name { get; set; }
}

And another one like this one

public class Collector
{
    public int CollectorID { get; set; }
    public string CollectorString { get; set; }
    public ICollection<TestModel> MyList { get; set; }
}

I would like to (in simple CRUD style) create a new object Collector and populate (later with dynamic addition of new fields, for now only one) the ICollection. This is my view

@model TestApplication.Models.Collector

@{
    ViewBag.Title = "Create";
}

<h2>Create</h2>


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

    <div class="form-horizontal">
        <h4>Collector</h4>
        <hr />
        @Html.ValidationSummary(true, "", new { @class = "text-danger" })
        <div class="form-group">
            @Html.LabelFor(model => model.CollectorString, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.CollectorString, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.CollectorString, "", new { @class = "text-danger" })
            </div>
        </div>


        <div class="form-group">
            @Html.LabelFor(model => Model.MyList.ToList()[0].Name)
            <div class="col-md-10">
                @Html.EditorFor(model => model.MyList.ToList()[0].Name, new { htmlAttributes = new { @class = "form-control" } })


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

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

And the controller

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Collector collector)
{
    if (ModelState.IsValid)
    {
        db.Collectors.Add(collector);
        db.SaveChanges();
        return RedirectToAction("Index");
    }

    return View(collector);
}

This is the resulting HTML code (only relevant part I hope)

<div class="form-group">
    <label for="">Name</label>
    <div class="col-md-10">
        <input class="form-control text-box single-line" name="[0].Name" type="text" value="" />
    </div>
</div>

However, the MyList in the controller when creating is always null, why? (Yes I know Haackeds Blog entry, still can't figure out why it doesn't work)

So the problem is, that this line

@Html.EditorFor(model => model.MyList.ToList()[0].Name, new { htmlAttributes = new { @class = "form-control" } })

although very recommended to use in MVC, generates this here

<input class="form-control text-box single-line" name="[0].Name" type="text" value="" />

which is obviously not working. How do I get razor to change [0].Name to MyList[0].Name?

**Update: ** So I found a solution, if a hard-code this here

<input class="form-control text-box single-line" name="MyList[0].Name" type="text" value="" />

The controller understands it and I don't get null. How to solve it using razor?

Upvotes: 2

Views: 3768

Answers (3)

Nkosi
Nkosi

Reputation: 247048

ICollection<T> does not have an indexer. IList<T> does

public class Collector {
    public Collector() {
        MyList = new List<TestModel>(); 
    }
    public int CollectorID { get; set; }
    public string CollectorString { get; set; }
    public IList<TestModel> MyList { get; set; }
}

This would allow

@Html.EditorFor(model => model.MyList[0].Name, new { htmlAttributes = new { @class = "form-control" } })

in the view to generate the desired markup

<input class="form-control text-box single-line" name="MyList[0].Name" type="text" value="" />

Which can also be used in a loop for multiple items

<div class="form-group">
@for(int i = 0; i < Model.MyList.Count; i++) {
    @Html.LabelFor(model => Model.MyList[i].Name, htmlAttributes: new { @class = "control-label col-md-2" })
    <div class="col-md-10">
        @Html.EditorFor(model => model.MyList.ToList()[i].Name, new { htmlAttributes = new { @class = "form-control" } })
    </div>
}
</div>

When returning the model from the controllers initial call, just make sure that calls to the model's collection index has an item.

[HttpGet]
public ActionResult Create() {
    var model = new Collector();
    model.MyList.Add(new TestModel());
    return View(model);
}

Upvotes: 3

jignesh patel
jignesh patel

Reputation: 982

You can do by this using razor syntax

<div class="form-group">
  @for(int i = 0; i < Model.MyList.Count; i++) {
      @Html.LabelFor(model => Model.MyList[i].Name, htmlAttributes: new {      @class = "control-label col-md-2" })
      <div class="col-md-10">
         @Html.EditorFor(model => model.MyList[i].Name, new { htmlAttributes = new { @class = "form-control" } })
      </div>
  }
</div>

Upvotes: 0

Kirsten
Kirsten

Reputation: 18056

Try changing your collector class to include initialization.

   public class Collector
   {
     public Collector() 
     {
        set MyList = new Collection<TestModel>();
     }
    public int CollectorID { get; set; }
    public string CollectorString { get; set; }
    public ICollection<TestModel> MyList { get; set; }
   }

Upvotes: 0

Related Questions