Nested Collection Models in MVC to Add Multiple Phone Numbers - Part 3
This
is Part 3 of the article series. Actually, in
this article series we were developing an MVC application that will allow
adding multiple phone numbers using Nested Model concept. User will be able to
add or remove (zero or more phone numbers, I need minimum two phone numbers for
each employee) phone numbers for any single employee.
Previous logs:
Now, in this post you will learn how to list, how to edit,
how to delete the employees. As we are working with nested models we need to follow
quite different appraoch coz for each employee in Employee table we have zero
or more phone numbers in Phone table associated with one to many relationships.
In the image given below you can see for each employee we
have different set of phone numbers.
Okay, let’s create this one, just follow the steps.
Step
8: Listing Records
At very first, create an action method inside Employee
controller class, here is the code.
:::::::::::
CompanyEntities db = new CompanyEntities();
public ActionResult List()
{
return View(db.Employees.ToList());
}
:::::::::::
This code will return the list of employees to the view page.
Add the view page for this action method by using following codes which loops
through the collection of employees.
List.cshtml
@model
IEnumerable<NestedModelsMvc.Models.Employee>
@{
ViewBag.Title = "List";
}
<h2>Employee List</h2>
<p>
@Html.ActionLink("Create
New", "New")
</p>
<table>
<tr>
<th>
@Html.DisplayNameFor(model => model.Name)
</th>
<th>
@Html.DisplayNameFor(model => model.Salary)
</th>
<th>
@Html.DisplayNameFor(model => model.Phones)
</th>
<th></th>
</tr>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Name)
</td>
<td>
@Html.DisplayFor(modelItem => item.Salary)
</td>
<td>
@Html.DisplayFor(modelItem => item.Phones)
</td>
<td>
@Html.ActionLink("Edit", "Edit", new { id=item.EmployeeId }) |
@Html.ActionLink("Delete", "Delete", new { id=item.EmployeeId })
</td>
</tr>
}
</table>
Now, again in above code you can see, I am using modelItem
=> item.Phones which is nothing but an ICollection.
This will again look at the type of model, then go looking
for available display templates in DisplayTemplates folder for a file named
Phone.cshtml that we don’t have. Let’s add this too.
In Phone.cshtml file use following quick code.
Phone.cshtml
@model
NestedModelsMvc.Models.Phone
@Html.DisplayFor(x => x.PhoneNumber)
And that’s it. Run the application it whould work now.
In List.cshtml view page, we have Edit and Delete links that
we don’t have impletement yet, let’s implement.
Step
9: Editing Record
To allow editing, create two action methods one for GET and
another for POST reqests inside Employee controller class, here is the code.
//
:::::::::::
public ActionResult Edit(int id = 0)
{
Employee employee = db.Employees.Find(id);
if (employee == null)
{
return HttpNotFound();
}
return View(employee);
}
[HttpPost]
public ActionResult Edit(Employee employee)
{
if (ModelState.IsValid)
{
db.Entry(employee).State = EntityState.Modified;
db.SaveChanges();
foreach (var item in employee.Phones)
{
db.Entry(item).State = EntityState.Modified;
db.SaveChanges();
}
return RedirectToAction("List");
}
return View(employee);
}
// :::::::::::
GET version of the action method will look for passed url
parameter (id) and then depending upon passed id an employee and its associated
phone numbers will be returned to the view page. Now, add a new view page for
this action method and use following code.
Edit.cshtml
@model
NestedModelsMvc.Models.Employee
@{
ViewBag.Title = "Edit";
}
<h2>Edit</h2>
@using (Html.BeginForm()) {
@Html.ValidationSummary(true)
<fieldset>
<legend>Department</legend>
@Html.HiddenFor(model => model.EmployeeId)
<div class="editor-label">
@Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Name)
@Html.ValidationMessageFor(model => model.Name)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.Salary)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Salary)
@Html.ValidationMessageFor(model => model.Salary)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.Phones)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Phones)
@Html.ValidationMessageFor(model => model.Phones)
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
<div>
@Html.ActionLink("Back
to List", "List")
</div>
@section
Scripts {
@Scripts.Render("~/bundles/jqueryval")
}
Now, again in above code you can see, I am using model =>
item.Phones which is nothing but an ICollection. This will again look at the
type of model, then go looking for available editor templates in EditorTemplates
folder for a file named Phone.cshtml that we already created in previous post.
I added two new line (highligted in the code below) of codes to match the
requirement, if you don’t do, you will experience following error.
A referential integrity constraint
violation occurred: The property values that define the referential constraints
are not consistent between principal and dependent objects in the relationship.
Phone.cshtml
@model
NestedModelsMvc.Models.Phone
@using NestedModelsMvc.Models
<div class="phoneNumber">
<p>
<label>Phone Number</label>
@Html.HiddenFor(model =>
model.PhoneId)
@Html.HiddenFor(model =>
model.EmployeeId)
@Html.TextBoxFor(x => x.PhoneNumber)
@Html.HiddenFor(x => x.DeletePhone, new { @class = "mark-for-delete"
})
@Html.RemoveLink("Remove", "div.phoneNumber", "input.mark-for-delete")
</p>
</div>
Look at the codes used in the POST version of the action
method. This code is also quite different from normal update code.
Here you can run the application and test editing, it should
work now.
Step
10: Deleting Record
To allow deleting, create two action methods one for GET and
another for POST reqests inside Employee controller class, here is the code.
//
::::::::::::
public ActionResult Delete(int id = 0)
{
Employee employee = db.Employees.Find(id);
if (employee == null)
{
return HttpNotFound();
}
return View(employee);
}
[HttpPost, ActionName("Delete")]
public ActionResult DeleteConfirmed(int id)
{
Employee employee = db.Employees.Find(id);
var phones = db.Phones.Where(i => i.EmployeeId ==
employee.EmployeeId);
DeleteChilds(phones);
db.Employees.Remove(employee);
db.SaveChanges();
return RedirectToAction("List");
}
private void DeleteChilds(IEnumerable<Phone> phones)
{
foreach (var item in phones)
{
db.Phones.Remove(item);
}
}
// ::::::::::::
GET version of the action method will look for passed url
parameter (id) and then depending upon passed id an employee and its associated
phone numbers will be returned to the view page asking ‘Are you sure you want
to delete this?’.
Let’s add this view page and use following code.
Delete.cshtml
@model
NestedModelsMvc.Models.Employee
@{
ViewBag.Title = "Delete";
}
<h2>Delete</h2>
<h3>Are you sure you want to delete this?</h3>
<fieldset>
<legend>Employee</legend>
<div class="display-label">
@Html.DisplayNameFor(model => model.Name)
</div>
<div class="display-field">
@Html.DisplayFor(model => model.Name)
</div>
<div class="display-label">
@Html.DisplayNameFor(model => model.Salary)
</div>
<div class="display-field">
@Html.DisplayFor(model => model.Salary)
</div>
<div class="display-label">
@Html.DisplayNameFor(model => model.Phones)
</div>
<div class="display-field">
@Html.DisplayFor(model => model.Phones)
</div>
</fieldset>
@using (Html.BeginForm()) {
<p>
<input type="submit" value="Delete" /> |
@Html.ActionLink("Back
to List", "List")
</p>
}
When user will hit Delete button POST version of the action
method will be called. If you look at the POST version of the code, you will
find an method DeleteChilds is being used. As you know each employee records
has related recores in Phone table with one to many relationship, so deleteing
such records will have quite different approach, you can check the code above.
If you use normal deleting code, you will get following error.
The operation failed: The
relationship could not be changed because one or more of the foreign-key
properties is non-nullable. When a change is made to a relationship, the
related foreign-key property is set to a null value. If the foreign-key does
not support null values, a new relationship must be defined, the foreign-key
property must be assigned another non-null value, or the unrelated object must
be deleted.
Here you can run the application and test deleting record, it
should work now.
That’s
all. Ending the series here.
Source code: https://github.com/itorian/NestedCollectionMVC
Hope
this helps.
Wondering how you would go about handling nested nested collections. For example using your Employee Profile you could add contact people where they could have 0 to many contacts and each contact could have say 1 to many phone numbers.
ReplyDeleteIt's nice but how do you add a new phone number while editing an employee? Editing current phone numbers works fine but there is a problem if you try to add a new phone number for an existing employee.
ReplyDeleteI have the same problem with the edit mode when i add a new phone it doesn't post ...?
DeleteDo you find how to do this, please. Thanks very much :)
You can do this in a couple ways. The way I handled it was by modifying the HTML helper for addlink. I added an extra optional property for employee id. If an employee ID is present, it runs a replace on the value of the employeeID field, and inserts the new employee ID.
DeleteFrom there, you can modify your edit method inside your controller to be similar to the following:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(employees1 employees1)
{
if (ModelState.IsValid)
{
foreach (var item in employee.phone)
{
if (item.deletedPhone)
{
db.Entry(item).State = EntityState.Deleted;
}
else if (item.phoneId == 0)
{
db.Entry(item).State = EntityState.Added;
}
else
{
db.Entry(item).State = EntityState.Modified;
}
db.SaveChanges();
}
db.Entry(employee).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
To get it to work on the edit page, modify the addLink HTML Helper to have an optional ID field. If the ID field is present, then you will replace the employeeID value field within the hidden field. From there, you need to modify your edit method inside the employee controller to something similar to:
Delete[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(employees1 employees1)
{
if (ModelState.IsValid)
{
foreach (var item in employee.Phones)
{
if (item.DeletePhone)
{
db.Entry(item).State = EntityState.Deleted;
}
else if (item.PhoneId == 0)
{
db.Entry(item).State = EntityState.Added;
}
else
{
db.Entry(item).State = EntityState.Modified;
}
db.SaveChanges();
}
db.Entry(employee).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
Dear Jdsfighter,
DeleteCould you pls help me with the modification of the addlink HTML helper. I am really blank :(
Dear Jdsfighter,
DeleteCould you pls give me the modified addlink HTML Helper as well? Because I have no idea at all about it :(
Dear jds fighter, pls post your modified code for the addlink helper
Deleteany luck with adding a new phone no while editing, after passing the employeeID how to create a new instance for the employee.phones
DeleteHas anyone figured out the modified code for addlink helper?
DeleteGitHub not working
ReplyDeleteFor adding a new Phone number to an Employee which has none, simply pass in the EmployeeId into the HtmlHelper AddLink method (pass in 0 when doing a Create) then use this
ReplyDeleteobject nestedObject;
if (identifier > 0)
{
nestedObject = Activator.CreateInstance(nestedType, identifier);
}
else
{
nestedObject = Activator.CreateInstance(nestedType);
}
Finally, add a new constructor to the Phone class which takes in an EmployeeId. This then sets the EmployeeId property of the class and now it will be ready to save
how to add new phone number when we editing record
ReplyDeletei have a dropdownlist instead of the phone textbox how could i update the DeletePhone where the select tag doesnt have a value property only the option have and its already used for displaying options
ReplyDeleteany help?
Hello , If we begin with one phone number instead of two add phone number helper doesn't work! Any ideas?
ReplyDeleteHello , If we begin with one phone number instead of two add phone number helper doesn't work! Any ideas?
ReplyDeleteIs there any update on the revised html helper that is needed to add phone numbers when editing employees? I followed the tutorial and got stuck here as well.
ReplyDelete