Perform List, Insert, Update and Delete in a Single View in ASP.NET MVC
A common way to perform list, insert, update and delete operations in
ASP.NET MVC is to
create four separate views. The List view forms the launching view where records
are displayed and you can choose to Edit, Delete or Insert a record. However, in
some cases you may want to perform all these operations in a single view itself.
This task can be accomplished using full page postback or using Ajax. This
article discusses the former technique.
Consider the following figure that shows one such arrangement:
The above figure shows a list of records from Customers table of Northwind
database. You can Insert a new customer by clicking on Insert button. You can
select a row for editing by clicking on the Select button. The selected customer
is shown below the main table for editing. Similarly you can also delete a
customer by clicking on the Delete button.
Model and View Model
Let's see how the above application can be built. Begin by creating a new
empty ASP.NET MVC
project in Visual
Studio. Then add an ADO.NET Entity Data Model for the Customers table. The
Customer entity class is shown below:
Then add a new POCO to the Models folder and name it CustomersViewModel. As
you will see later, this view model class will be passed from the HomeController
to the Index view. The CustomersViewModel class is shown below:
public class CustomersViewModel
{
public List<Customer> Customers { get; set; }
public Customer SelectedCustomer { get; set; }
public string DisplayMode { get; set; }
}
The CustomersViewModel class consists of three properties. The Customers
property holds a List of Customer that are to be displayed on the view. The
SelectedCustomer property points to a Customer that is selected by the user. If
no Customer is selected this property is null. The DisplayMode property
indicates the mode of the Customer details area. Possible values are ReadOnly
(after selection), ReadWrite (during edit) and WriteOnly (during insert). For
the sake of simplicity DisplayMode is created as a string property, you can
easily make it to accept an enumeration.
Home controller and its action methods
Then add HomeController in the Controllers folder. The HomeController will
contain the following action methods:
- Index()
- Select()
- New()
- Insert()
- Edit()
- Update()
- Delete()
- Cancel()
The method names are self-explanatory. All the actions except Index() are
called as a result of POST operation. Let's discuss them briefly one by one.
public ActionResult Index()
{
using (NorthwindEntities db = new NorthwindEntities())
{
CustomersViewModel model = new CustomersViewModel();
model.Customers = db.Customers.OrderBy(
m => m.CustomerID).Take(5).ToList();
model.SelectedCustomer = null;
return View(model);
}
}
The Index() action fetches a list of customers and fills it in the Customers
view model property. The SelectedCustomer is set to null because there is no
selected customer in the beginning. Note that for the sake of simplicity the
above code fetches only 5 customers. You can, of course, fetch all if you so
wish.
[HttpPost]
public ActionResult New()
{
using (NorthwindEntities db = new NorthwindEntities())
{
CustomersViewModel model = new CustomersViewModel();
model.Customers = db.Customers.OrderBy(
m => m.CustomerID).Take(5).ToList();
model.SelectedCustomer = null;
model.DisplayMode = "WriteOnly";
return View("Index", model);
}
}
The New() action is called when a user hits the Insert button at the top of
the page. It fills the Customers list as before. SelectedCustomer is set to null
because a new record is to be added. The DisplayMode is set to WriteOnly because
we will be accepting new customer details. The following figure shows how the
insert area looks like:
[HttpPost]
public ActionResult Insert(Customer obj)
{
using (NorthwindEntities db = new NorthwindEntities())
{
db.Customers.Add(obj);
db.SaveChanges();
CustomersViewModel model = new CustomersViewModel();
model.Customers = db.Customers.OrderBy(
m => m.CustomerID).Take(5).ToList();
model.SelectedCustomer = db.Customers.Find(obj.CustomerID);
model.DisplayMode = "ReadOnly";
return View("Index", model);
}
}
The Insert() action is called when a user fills new customer details and
clicks on the Save button (see above figure). It receives a Customer object as
its parameter. Inside, the Insert() action adds that new Customer to the
database. It also sets the currently selected customer to the newly added
customer by setting the SelectedCustomer property. The DisplayMode is set to
ReadOnly so that the record is displayed in read-only manner.
[HttpPost]
public ActionResult Select(string id)
{
using (NorthwindEntities db = new NorthwindEntities())
{
CustomersViewModel model = new CustomersViewModel();
model.Customers = db.Customers.OrderBy(
m => m.CustomerID).Take(5).ToList();
model.SelectedCustomer = db.Customers.Find(id);
model.DisplayMode = "ReadOnly";
return View("Index",model);
}
}
The Select() action method is called when the Select button from a customer
table row is clicked. It receives CustomerID as its parameter. Inside, it fills
Customers list as before. This time SelectedCustomer property is set to the
Customer whose CustomerID is passed. The DisplayMode property is set to ReadOnly
to indicate that the details of the selected customer should be displayed in a
read-only table below the main customer listing (see below).
[HttpPost]
public ActionResult Edit(string id)
{
using (NorthwindEntities db = new NorthwindEntities())
{
CustomersViewModel model = new CustomersViewModel();
model.Customers = db.Customers.OrderBy(
m => m.CustomerID).Take(5).ToList();
model.SelectedCustomer = db.Customers.Find(id);
model.DisplayMode = "ReadWrite";
return View("Index", model);
}
}
The Edit() action is called when a user clicks on the Edit button once a
Customer is selected. Inside, it sets the SelectedCustomer property to the
Customer whose CustomerID is passed to the method. DisplayMode property is set
to ReadOnly to display that record in editable table as shown below:
[HttpPost]
public ActionResult Update(Customer obj)
{
using (NorthwindEntities db = new NorthwindEntities())
{
Customer existing = db.Customers.Find(obj.CustomerID);
existing.CompanyName = obj.CompanyName;
existing.ContactName = obj.ContactName;
existing.Country = obj.Country;
db.SaveChanges();
CustomersViewModel model = new CustomersViewModel();
model.Customers = db.Customers.OrderBy(
m => m.CustomerID).Take(5).ToList();
model.SelectedCustomer = existing;
model.DisplayMode = "ReadOnly";
return View("Index", model);
}
}
The Update() action is called when a user modifies an existing Customer data
and clicks on the Save button (see above figure). Inside, the code updates an
existing Customer and saves the changes back to the database. Then Customers,
SelectedCustomer and DisplayMode properties of the view model are set.
[HttpPost]
public ActionResult Delete(string id)
{
using (NorthwindEntities db = new NorthwindEntities())
{
Customer existing = db.Customers.Find(id);
db.Customers.Remove(existing);
db.SaveChanges();
CustomersViewModel model = new CustomersViewModel();
model.Customers = db.Customers.OrderBy(
m => m.CustomerID).Take(5).ToList();
model.SelectedCustomer = null;
model.DisplayMode = "";
return View("Index", model);
}
}
The Delete() action is called when Delete button in any of the customer row
is clicked. It receives CustomerID as its parameter. Inside, it removes the
specified Customer and saves the changes back to the database. The
SelectedCustomer is set to null because post deletion that customer no longer
exists in the database. For the same reason, DisplayMode is set to an empty
string.
[HttpPost]
public ActionResult Cancel(string id)
{
using (NorthwindEntities db = new NorthwindEntities())
{
CustomersViewModel model = new CustomersViewModel();
model.Customers = db.Customers.OrderBy(
m => m.CustomerID).Take(5).ToList();
model.SelectedCustomer = db.Customers.Find(id);
model.DisplayMode = "ReadOnly";
return View("Index", model);
}
}
The Cancel() action is called when Cancel button from the Edit area is
clicked. It receives CustomerID as its parameter. It changes the DisplayMode
from ReadWrite to ReadOnly so that the SelectedCustomer is displayed in
read-only fashion.
Notice that all the above action methods return Index view and
CustomerViewModel object.
Index view
Now, let's see what goes inside the Index view.
@model MasterDetailsDemo.Models.CustomersViewModel
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Index</title>
<style>
.SelectedCustomer
{
background-color:gray;
font-weight:bold;
}
</style>
</head>
<body>
<h1>List of Customers</h1>
<form method="post">
<input type="submit"
value="Insert" formaction="/home/new" />
<br /><br />
<table border="1" cellpadding="10">
<tr>
<th>CustomerID</th>
<th>CompanyName</th>
<th colspan="2">Actions</th>
</tr>
@foreach (var item in Model.Customers)
{
if (Model.SelectedCustomer != null)
{
if (item.CustomerID ==
Model.SelectedCustomer.CustomerID)
{
@:<tr class="SelectedCustomer">
}
else
{
@:<tr>
}
}
else
{
@:<tr>
}
<td>@item.CustomerID</td>
<td>@item.CompanyName</td>
<td><input type="submit"
formaction="/home/select/@item.CustomerID"
value="Select" /></td>
<td><input type="submit"
formaction="/home/delete/@item.CustomerID"
value="Delete" /></td>
@:</tr>
}
</table>
</form>
<br /><br />
@{
if(Model.SelectedCustomer!=null)
{
if (Model.DisplayMode == "ReadOnly")
{
Html.RenderPartial
("ShowCustomer",Model.SelectedCustomer);
}
if (Model.DisplayMode == "ReadWrite")
{
Html.RenderPartial
("EditCustomer",Model.SelectedCustomer);
}
}
if (Model.DisplayMode == "WriteOnly")
{
Html.RenderPartial("InsertCustomer",
new MasterDetailsDemo.Models.Customer());
}
}
</body>
</html>
The Index view is divided into two logical parts. The top part displays a
list of customers in a table. Notice that a CSS class SelectedCustomer is
applied to the row that contains the selected CustomerID. The bottom part
displays a Partial Page based on the value of DisplayMode. This way either show,
insert or edit areas are displayed. Notice that there are three partial pages
involved:
- ShowCustomer.cshtml
- EditCustomer.cshtml
- InsertCustomer.cshtml
These three partial pages render the read-only, read-write and write-only
displays respectively. All of them take Customer object as their model. Let's
see each of these partial pages one by one.
ShowCustomer partial page
The following code shows ShowCustomer.cshtml partial page.
@model MasterDetailsDemo.Models.Customer
@using(Html.BeginForm("Edit","Home",FormMethod.Post))
{
<table border="1" cellpadding="10">
<tr>
<td>Customer ID :</td>
<td>@Model.CustomerID</td>
</tr>
<tr>
<td>Company Name :</td>
<td>@Model.CompanyName</td>
</tr>
<tr>
<td>Contact Name :</td>
<td>@Model.ContactName</td>
</tr>
<tr>
<td>Country :</td>
<td>@Model.Country</td>
</tr>
<tr>
<td colspan="2">
<input type="submit" value="Edit"
formaction="/home/edit/@Model.CustomerID" />
<input type="submit" value="Cancel"
formaction="/home/index" />
</td>
</tr>
</table>
}
Notice that the Edit and Cancel buttons submit to /home/edit and /home/index
respectively. The other markup from the partial page is quite straightforward
and displays CustomerID, CompanyName, ContactName and Country columns for a
selected customer.
EditCustomer partial page
The following code shows what goes inside EditCustomer.cshtml:
@model MasterDetailsDemo.Models.Customer
@using (Html.BeginForm("Update", "Home", FormMethod.Post))
{
<table border="1" cellpadding="10">
<tr>
<td>Customer ID :</td>
<td>@Html.TextBoxFor(m => m.CustomerID,
new { @readonly = "readonly" })</td>
</tr>
<tr>
<td>Company Name :</td>
<td>@Html.TextBoxFor(m => m.CompanyName)</td>
</tr>
<tr>
<td>Contact Name :</td>
<td>@Html.TextBoxFor(m => m.ContactName)</td>
</tr>
<tr>
<td>Country :</td>
<td>@Html.TextBoxFor(m => m.Country)</td>
</tr>
<tr>
<td colspan="2">
<input type="submit" value="Save"
formaction="/home/update" />
<input type="submit" value="Cancel"
formaction="/home/cancel/@Model.CustomerID" />
</td>
</tr>
</table>
}
Note that Save button and Cancel button submit to /home/update and
/home/cancel respectively.
InsertCustomer partial page
Finally, here is the markup of InsertCustomer.cshtml:
@model MasterDetailsDemo.Models.Customer
@using (Html.BeginForm("Insert", "Home", FormMethod.Post))
{
<table border="1" cellpadding="10">
<tr>
<td>Customer ID :</td>
<td>@Html.TextBoxFor(m => m.CustomerID)</td>
</tr>
<tr>
<td>Company Name :</td>
<td>@Html.TextBoxFor(m => m.CompanyName)</td>
</tr>
<tr>
<td>Contact Name :</td>
<td>@Html.TextBoxFor(m => m.ContactName)</td>
</tr>
<tr>
<td>Country :</td>
<td>@Html.TextBoxFor(m => m.Country)</td>
</tr>
<tr>
<td colspan="2">
<input type="submit" value="Save"
formaction="/home/insert" />
<input type="submit" value="Cancel"
formaction="/home/index" />
</td>
</tr>
</table>
}
The Save and Cancel button submit to /home/insert and /home/index
respectively.
That's it! All the parts of the application are in place. Run the application
and test whether all the operations work as expected.