2

I am using a partial view to create a parent child view. What I would ideally like is the submit button on the parent view to save the child values.

I have the following model.

public class Course
{
    public int CourseId { get; set; }
    public string Name { get; set; }
    public int Par { get; set; }
    public string Tee { get; set; }
    public decimal Rating { get; set; }
    public virtual IEnumerable<CourseHole> Holes { get; set; }

    public static Course Create()
    {
        var course = new Course();
        course.Par = 72;
        var holes = new List<CourseHole>();
        for (int i = 0; i < 18; i++)
        {
            holes.Add(new CourseHole() { Course = course, Number = i + 1, Par = 4 });
        }
        course.Holes = holes;
        return course;
    }
}

public class CourseHole
{
    public int CourseHoleId { get; set; }
    public int Number { get; set; }
    public int Par { get; set; }
    public int Length { get; set; }
    public int Ranking { get; set; }
    public Course Course { get; set; }
}

And the following parent view.

@model Golf_Statz.Models.Course

@{
    ViewBag.Title = "Create";
}

<h2>Create</h2>


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

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

            @Html.LabelFor(model => model.Par, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-4">
                @Html.EditorFor(model => model.Par, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Par, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.Tee, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-4">
                @Html.EditorFor(model => model.Tee, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Tee, "", new { @class = "text-danger" })
            </div>

            @Html.LabelFor(model => model.Rating, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-4">
                @Html.EditorFor(model => model.Rating, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Rating, "", new { @class = "text-danger" })
            </div>
        </div>

        @foreach (Golf_Statz.Models.CourseHole hole in Model.Holes)
        {
            @Html.Partial("CreateHole", hole)
        }

        <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 following partial view.

@model Golf_Statz.Models.CourseHole

@{
    ViewBag.Title = "CreateHole";
}

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

    <div class="form-horizontal" id="CreateHole-" + model.CourseHoleId>
        @Html.ValidationSummary(true, "", new { @class = "text-danger" })
        <div class="form-group">
            <div class="col-md-2 col-md-offset-2">
                <p>@Model.Number</p>
            </div>
            <div class="col-md-2">
                @Html.EditorFor(model => model.Par, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Par, "", new { @class = "text-danger" })
            </div>
            <div class="col-md-2">
                @Html.EditorFor(model => model.Length, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Length, "", new { @class = "text-danger" })
            </div>
            <div class="col-md-2">
                @Html.EditorFor(model => model.Ranking, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Ranking, "", new { @class = "text-danger" })
            </div>
        </div>
    </div>
}

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

My controller methods are.

// GET: Course/Create
        public ActionResult Create()
        {           
            return View(Course.Create());
        }

        // POST: Course/Create
        // To protect from overposting attacks, please enable the specific properties you want to bind to, for 
        // more details see http://go.microsoft.com/fwlink/?LinkId=317598.
        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Create([Bind(Include = "CourseId,Name,Par,Tee,Rating")] Course course)
        {
            if (ModelState.IsValid)
            {
                db.Courses.Add(course);
                foreach (var hole in course.Holes)
                {
                    db.CourseHoles.Add(hole);
                }
                db.SaveChanges();
                return RedirectToAction("Index");
            }

            return View(course);
        }

No matter what I seem to do course.Holes in the HttpPost Create method is always null. I think I want something similar to this but it's for editing and I want it for creating. Any help would be greatly appreciated as this is my first mvc project.

1
  • 1
    A couple of observations: - You cannot nest forms: Your partial has a form and our parent nests this partial for each CourseHole. This is not allowed. - You partial has an antiforgery token. This is not needed as the parent view already has this token. Commented Apr 7, 2015 at 8:28

2 Answers 2

7

Your foreach loop is generating duplicate id attributes (invalid html) and name attributes which have no relationship to your model. Change the partial to an EditorTemplate

/Views/Shared/EditorTemplates/CourseHole.cshtml

and remove the BeginForm, AntiForgeryToken and scripts

@model Golf_Statz.Models.CourseHole
<div class="form-horizontal" id="CreateHole-" + model.CourseHoleId>
    <div class="form-group">
        <div class="col-md-2 col-md-offset-2">
            <p>@Model.Number</p>
        </div>
        <div class="col-md-2">
            @Html.EditorFor(model => model.Par, new { htmlAttributes = new { @class = "form-control" } })
            @Html.ValidationMessageFor(model => model.Par, "", new { @class = "text-danger" })
        </div>
        <div class="col-md-2">
            @Html.EditorFor(model => model.Length, new { htmlAttributes = new { @class = "form-control" } })
            @Html.ValidationMessageFor(model => model.Length, "", new { @class = "text-danger" })
        </div>
        <div class="col-md-2">
            @Html.EditorFor(model => model.Ranking, new { htmlAttributes = new { @class = "form-control" } })
            @Html.ValidationMessageFor(model => model.Ranking, "", new { @class = "text-danger" })
        </div>
    </div>
</div>

and then in the main view

@Html.EditorFor(m => m.Holes)

The EditorFor() method will correctly generate the html for binding to a collection, for example

<input name="Holes[0].Par" ...>
<input name="Holes[1].Par" ...>

You also need to remove the [Bind] attribute since you are excluding property Holes, and you seem to want to bind to all properties anyway.

Side note: You do not generate an input for the hole CourseHoleId or Number properties so these wont post back.

Sign up to request clarification or add additional context in comments.

3 Comments

How would you add an additional hole? (I know this is a little outside the functional objective of the OP, but I really like this pattern and would like to use it to add additional child records.)
@J-Rome. Refer the answers here and here if you looking to create a form to dynamically add/remove collection items in the view
I was just searching and found your same answers on those same questions! Thank you for your many contributions! (And I will be more diligent in my searches next time. ;)
1

In your case you have two forms! Razor would generate this

<form>
    <form>
    </form>
 </form>

remove @using (Html.BeginForm()) {} from the partial view

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.