Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to pass a model containing an IEnumerable model (complex) into a controller from a view C# MVC3?

I have had a look through other questions and answers on this site but cannot find the answer I need.

I have a StudentRecord entity:

public class StudentRecord : Persistent {
        public virtual string LastName { get; set; }
        public virtual string FirstName { get; set; }
        public virtual DateTime Dob { get; set; }
        public virtual int StudentRef { get; set; }
        public virtual IEnumerable<StudentAddress> Addresses { get; set; }
        public virtual StudentAddress Address { get; set; }
        public virtual string Notes { get; set; }
    }

As you can see it contains a single StudentAddress entity and also an IEnumerable of StudentAddress:

public class StudentAddress: Persistent {
        public virtual int StudentRef { get; set; }
        public virtual string Addressee { get; set; }
        public virtual string Property { get; set; }
        public virtual string District { get; set; }
        public virtual string PostalTown { get; set; }
        public virtual string County { get; set; }
        public virtual string Postcode { get; set; }
    }

I am passing a student record to a view, contained within a viewmodel:

public class UserViewModel {
        public StudentRecord Student;      
        public ICurrentUserService CurrentUserService;
        public ParentUser ParentUser;        
    }

Then displaying it in a form so it can be edited, and submitting the form passes the StudentRecord back to the controller. All works fine except the Addresses within the StudentRecord are null. The single StudentAddress in the StudentRecord is for if a new address is added, and that works fine too.

Is it possible to edit and send the addresses back to the controller, or do I need to have them in a separate form on a separate page? I can do that but would prefer to have it all in one.

My problem may be that it is not possible, or it may be the way I am putting the addresses into the form. A student may have more than one address.

Here is the form: (I have stripped out some html layout for clarity. The 'Add another address' tickbox shows the New Student Address section with jquery.)

@using (Html.BeginForm()) {
    Personal Details
    Full Name: @Html.TextBoxFor(x => x.Student.FirstName) @Html.TextBoxFor(x => x.Student.LastName)
    DOB: @Html.TextBoxFor(x => x.Student.Dob)

    @if (Model.Student.Addresses.Any()) {
        // Only print addresses if they exist
            int count = 1;
            int element = 0;
                @if (Model.Student.Addresses.Count() > 1) {
                    foreach (var address in Model.Student.Addresses) {
                        Student Address @count
                        Addressee @Html.TextBoxFor(x => x.Student.Addresses.ElementAt(element).Addressee)
                        Property @Html.TextBoxFor(x => x.Student.Addresses.ElementAt(element).Property)
                        District @Html.TextBoxFor(x => x.Student.Addresses.ElementAt(element).District)
                        Postal Town @Html.TextBoxFor(x => x.Student.Addresses.ElementAt(element).PostalTown)
                        County @Html.TextBoxFor(x => x.Student.Addresses.ElementAt(element).County)
                        Postcode @Html.TextBoxFor(x => x.Student.Addresses.ElementAt(element).Postcode)
                        count++;
                        element++;
                    } //end foreach
                } else {
                    Student Address
                    Addressee @Html.TextBoxFor(x => x.Student.Addresses.ElementAt(0).Addressee)
                    Property @Html.TextBoxFor(x => x.Student.Addresses.ElementAt(0).Property)
                    District @Html.TextBoxFor(x => x.Student.Addresses.ElementAt(0).District)
                    Postal Town @Html.TextBoxFor(x => x.Student.Addresses.ElementAt(0).PostalTown)
                    County @Html.TextBoxFor(x => x.Student.Addresses.ElementAt(0).County)
                    Postcode @Html.TextBoxFor(x => x.Student.Addresses.ElementAt(0).Postcode)
                } @*end if (Model.Student.Addresses.Count() > 1)*@

                Add another address @Html.CheckBox("Add another address", false, new {@id = "newBox"})

                New Student Address
                Addressee @Html.TextBoxFor(x => x.Student.Address.Addressee)
                Property @Html.TextBoxFor(x => x.Student.Address.Property)
                District @Html.TextBoxFor(x => x.Student.Address.District)
                Postal Town @Html.TextBoxFor(x => x.Student.Address.PostalTown)
                County @Html.TextBoxFor(x => x.Student.Address.County)
                Postcode @Html.TextBoxFor(x => x.Student.Address.Postcode)
    } else {
        No address for this student.
    } @*end if (Model.Student.Addresses.Any())*@

    Notes: @Html.TextAreaFor(x => x.Student.Notes, new { @style = "width: 100%;"})

    <input type="submit" value="Send" class="btn btn-primary" style="clear: both;"/>
} @*end of form*@
like image 214
tekiegirl Avatar asked May 29 '12 12:05

tekiegirl


People also ask

How do I pass model value from controller view?

The other way of passing the data from Controller to View can be by passing an object of the model class to the View. Erase the code of ViewData and pass the object of model class in return view. Import the binding object of model class at the top of Index View and access the properties by @Model.

How model view and controller interact with each other?

First, the browser sends a request to the Controller. Then, the Controller interacts with the Model to send and receive data. The Controller then interacts with the View to render the data. The View is only concerned about how to present the information and not the final presentation.


1 Answers

The problem is that the name attributes of the text input controls do not contain correct values. I invite you to read the following blog post to better understand the convention used by the default model binder to bind to collections and dictionaries.

Then I would recommend you using editor templates instead of writing foreach loops in your views:

@using (Html.BeginForm()) {
    Personal Details

    @Html.LabelFor(x => x.Student.FirstName, "Full Name:")
    @Html.EditorFor(x => x.Student.FirstName) 
    @Html.EditorFor(x => x.Student.LastName)

    @Html.LabelFor(x => x.Student.Dob, "DOB:")
    @Html.TextBoxFor(x => x.Student.Dob)

    @if (Model.Student.Addresses.Any()) {
        @Html.EditorFor(x => x.Student.Addresses)
    } else {
        <text>No address for this student.</text>
    }

    @Html.LabelFor(x => x.Student.Notes, "Notes:")
    @Html.TextAreaFor(x => x.Student.Notes, new { @style = "width: 100%;"})

    <input type="submit" value="Send" class="btn btn-primary" style="clear: both;"/>
}

and then define a custom editor template that will be automatically rendered for each element of the Addresses collection (~/Views/Shared/EditorTemplates/StudentAddress.cshtml):

@model StudentAddress
@Html.LabelFor(x => x.Addressee, "Addressee")
@Html.EditorFor(x => x.Addressee)

@Html.LabelFor(x => x.Property, "Property")
@Html.EditorFor(x => x.Property)

@Html.LabelFor(x => x.District, "District")
@Html.EditorFor(x => x.District)

@Html.LabelFor(x => x.PostalTown, "Postal Town")
@Html.EditorFor(x => x.PostalTown)

@Html.LabelFor(x => x.County, "County")
@Html.EditorFor(x => x.County)

@Html.LabelFor(x => x.Postcode, "Postcode")
@Html.EditorFor(x => x.Postcode)

But all this is static. If you want to be able to dynamically add and remove addresses I invite you to read the following blog post from Steven Sanderson in which he illustrates how a custom HTML helper could be used to generate proper names for the input fields (Html.BeginCollectionItem) and use AJAX to add new rows.

like image 117
Darin Dimitrov Avatar answered Sep 28 '22 00:09

Darin Dimitrov