ASP.NET MVC 5 Change variant for complex Child list
I have an object named Job and one of the properties is List of Steps:
public class Job
{
[Display(Name = "Id")]
public int? JobId { get; set; }
[Required]
public string Name { get; set; }
public List<Step> Steps { get; set; }
public Job()
{
Steps = new List<Step>();
}
}
public class Step
{
public int? StepId { get; set; }
public int JobId { get; set; }
[Required]
public string Name { get; set; }
}
I have a JobController with the following action to perform the update:
// PUT: /Job/Edit/5
[HttpPut]
public ActionResult Edit(Job model)
{
// Logic to update model here
}
Based on the answer to this question , I updated my UI (using the Bootstrap template that comes with MVC5) to:
@using (Html.BeginForm())
{
@Html.HttpMethodOverride(HttpVerbs.Put)
@Html.AntiForgeryToken()
<div class="form-horizontal">
<hr />
@Html.ValidationSummary(true, "", new { @class = "text-danger" })
@Html.HiddenFor(model => model.JobId)
<div class="form-group">
@Html.LabelFor(model => model.Name, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.Name, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.Name, "", new { @class = "text-danger" })
</div>
</div>
<h3>Steps</h3>
<div>
@foreach (var item in Model.Steps)
{
<div class="form-group">
@Html.Hidden("Steps[" + stepIndex + "].StepId", item.StepId)
@Html.LabelFor(modelItem => item.Name, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
<input class="form-control text-box single-line" data-val="true" data-val-required="The Name field is required."
id="@String.Format("Steps_{0}__Name", stepIndex)" name="@String.Format("Steps[{0}].Name", stepIndex)" type="text" value="@item.Name" />
@Html.ValidationMessageFor(modelItem => item.Name, "", new { @class = "text-danger" })
</div>
</div>
stepIndex += 1;
<hr />
}
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
}
As you can see, I need to manually create an input tag as opposed to using Html.EditorFor. The reason is that I need to manipulate the id name so that it passes the index to id and name. I would guess there is a better approach that would allow MVC to display correct values ββusing labelFor, EditorFor and ValidationMessageFor.
I have the following questions:
- Is there a set of controls that I can use with MVC5 that allows me to render complex child objects without going through these extra steps?
- If not on 1, then is there a better approach than manually creating an input tag?
source to share
Option 1: Replace the loop foreach
with for
:
@for (int i = 0; i < Model.Steps.Count; i++)
{
<div class="form-group">
@Html.HiddenFor(m => m.Steps[i].StepId)
@Html.TextBoxFor(m => m.Steps[i].Name, new { @class = "form-control text-box single-line" })
...
</div>
}
Option 2. Create a named editor template Step.chtml
for the class Step
and use EditorFor
:
Step.chtml
@model Step
<div class="form-group">
@Html.HiddenFor(m => m.StepId)
@Html.TextBoxFor(m => m.Name, new { @class = "form-control text-box single-line" })
...
</div>
Main view
<h3>Steps</h3>
<div>
@Html.EditorFor(m => m.Steps)
<div>
In both of these forms, the framework will give the inputs the correct names and IDs.
source to share
It looks like things are more complicated, try below.
1. Create a new editor template (which is a view) named "Step.cshtml" in the EditorTemplates folder using the Step model.
2. In this case, do the following code,
Step.cshtml
@model Step
<div class="form-group">
@Html.HiddenFor(model => model.StepId)
@Html.LabelFor(modelItem => modelItem.Name, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.TextBoxFor(model => model.Name, htmlAttributes: new { @class = "form-control text-box single-line" })
@Html.ValidationMessageFor(modelItem => modelItem.Name, "", new { @class = "text-danger" })
</div>
3. Extract the foreach statement from your view and instead call the editor template as
@Html.EditorFor(model => model.Steps)
source to share