Nested Collection Models in MVC to Add Multiple Phone Numbers - Part 1

In this article (multi-part article) you will learn how to create MVC application that will allow adding multiple phone numbers using Nested Model Concept.

In this article (multi-part article) you will learn how to create a MVC application that will allow adding multiple phone numbers using the Nested Model Concept. The user will be able to add or remove (zero or more) phone numbers for any single employee.

Here is the first look of the concept.

 

Now, I will go step-by-step so that you can understand what is going on.

Step 1: Database
 
First things first. You need database tables with a proper relationship. The main concept is, there will be zero or more Phone Numbers in the "Phone" table for each employee in the "Employee" table. 

Note, we have navigation properties in both tables and "EmployeeId" in Phone table for each phone number.

You will find a property "DeletePhone" in the Phone table, this property will be used as a flag to delete a contact number by the controller method when the user is marked for deletion in the view. Actually, this field is not required in a database table when you have a ViewModel class, and this is a quick demo so rather than creating a class I will prefer adding a property in the database table.
 
I will share the script file to generate this database at the end of this article. I am using Entity Data Model to generate model classes and the DbContext. Once you done with this, move to the next step.

Step 2: Prepopulating Collection (Phone Number input boxes)

In the very first animated image, you will see the page is loaded with two "Phone Number" input boxes. To do this I need to update the "Employee" model (this model is generated by Entity Data Model) with the following highlighted method.

  1. public partial class Employee  
  2. {  
  3.     public Employee()  
  4.     {  
  5.         this.Phones = new HashSet<Phone>();  
  6.     }  
  7.     public int EmployeeId { getset; }  
  8.     public string Name { getset; }  
  9.     public string Salary { getset; }  
  10.     public virtual ICollection<Phone> Phones { getset; }  
  11.     internal void CreatePhoneNumbers(int count = 1)  
  12.     {  
  13.         for (int i = 0; i < count; i++) {  
  14.             Phones.Add(new Phone());  
  15.         }  
  16.     }  
  17. }  
This highlighted method "CreatePhoneNumbers" will take a parameter and add that collection to the "Phones" model property.

Now, let's add a new controller "Employee" and an action method "Add" and then we will make a call to the highlighted method above, here you go: 

  1. public ActionResult New()  
  2. {  
  3.     var employee = new Employee();  
  4.     employee.CreatePhoneNumbers(2);  
  5.     return View(employee);  
  6. }

I just created an object of the Employee model class and made a call to the "CreatePhoneNumbers" method with 2 as a parameter, this will help in generating two input boxes on the View page. Add the View Page for this Action method.

Step 3: Creating View

Creating a view for the employee model above is also simple. Let's update the view page you added in previous step by the following code.

New.cshtml

  1. @{  
  2.     ViewBag.Title = "New";  
  3. }  
  4. <h2>Add New Employee</h2>  
  5. @using (Html.BeginForm()){  
  6.     @Html.AntiForgeryToken()  
  7.     @Html.EditorForModel()  
  8.     <p>  
  9.     <button type="submit">Create Employee</button>  
  10.     </p>  
  11. }  
If you run this view here, you will see the following output. 

So, I will call this view page template-less. You can see I am using EditorForModel(), this will look at the type of model, then go looking for available templates in the EditorTemplates folder for a file named Employee.cshtml that we don't have. Let's add a new View page at the location Views|Employee|EditorTemplates|Employee.cshtml and add the following code. 

Employee.cshtml

  1. @model NestedModelsMvc.Models.Employee  
  2. @Html.HiddenFor(x => x.EmployeeId)  
  3. <p>  
  4.   <label>Name</label>  
  5.   @Html.TextBoxFor(x => x.Name)  
  6. </p>  
  7. <p>  
  8.   <label>Salary</label>  
  9.   @Html.TextBoxFor(x => x.Salary)  
  10. </p>  
  11. <div id="phoneNumbers">  
  12.   @Html.EditorFor(x => x.Phones)  
  13. </div>  
Now, again inside Employee.cshtml, we have a EditorFor(). This will again look at the type of model, then go looking for available templates in EditorTemplates folder for a file named Phone.cshtml that we don't have. Let's add this too.

Phone.cshtml

  1. @model NestedModelsMvc.Models.Phone  
  2. <div class="phoneNumber">  
  3.   <p>  
  4.     <label>Phone Number</label>  
  5.     @Html.TextBoxFor(x => x.PhoneNumber)  
  6.     @Html.HiddenFor(x => x.DeletePhone, new { @class = "mark-for-delete" })  
  7.   </p>  
  8. </div>  
Now, let me show you the view files so you aren't confused with the view file placements.

Now we are ready to run the New.cshtml file to look at the generated UI.

Great Start! Let's look at the generated HTML markups. 

 


In the HTML markup above, you can see two div tags that have the class="phoneNumber" and inside that we have two input fields.

The first field that is generated by TextBoxFor(x => x.PhoneNumber) has type="text" where the user will enter a phone number. And the second field that is generated by HiddenFor(x => x.DeletePhone, new { @class = "mark-for-delete" }) has type="hidden" that will work as a flag whether to delete or not.

Notice, we called EditorFor(x => x.Phones) in Employee.cshtml and it looped through our phone numbers and created two nested editors and each editor has a format defied in Phone.cshtml and see how smartly id and name attributes are filled up with index based words.

You can also notice the value attribute is blank, this will be filled with value="True" when the user marks for deletion. So, if the value is blank we don't need to execute the delete query, you will see it later.

Step 4: Deleting Phone Number

You will learn this in Part 2.

Hope this helps.