Reputation: 2714
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
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
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
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