4

I have an object called Job and one of the properties is a 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 a 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 have to manually build the input tag opposed to using Html.EditorFor. The reason is that I need to control name of the id so that it passes the Index into the id and name. I would assume there is a better approach that would allow MVC to render the correct values using labelFor, EditorFor and ValidationMessageFor.

The questions I have are:

  1. Is there a set of controls I can use with MVC5 that allows me to render complex child objects without going through these extra steps?
  2. If no on 1, then is there a better approach than manually create input tag?
Community
  • 1
  • 1
Josh
  • 8,219
  • 13
  • 76
  • 123

2 Answers2

7

Option 1: Replace the foreach loop 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 an editor template called Step.chtml for the Step class 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 these ways the framework will give the inputs correct names and ids.

Daniel Williams
  • 8,912
  • 15
  • 68
  • 107
Zabavsky
  • 13,340
  • 8
  • 54
  • 79
3

Looks like complicated the things, try the below.
1. Create a new editor template (which is a view) named 'Step.cshtml' under the EditorTemplates folder with the model Step.
2. In that do the below 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. Remove the foreach statement from your view, and instead call the editor template as,

@Html.EditorFor(model => model.Steps)
Venkatesh
  • 1,204
  • 1
  • 10
  • 18
  • Good answer but Zabavsky was a one minute faster with almost the same answer. – Josh Oct 19 '14 at 16:38
  • @Josh Thanks for your appreciation, I was trying to post the full source code of your modified view which I wasn't doing good in presenting in code format that made me pull back in answering. Any way answers helped regardless of person. Happy. – Venkatesh Oct 19 '14 at 16:41