MVC
MVC
MVC
Net MVC
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
FormCollection in MVC
14.
15.
16.
17.
18.
19.
20.
21.
Including and excluding properties from model binding using bind attribute
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
Partial views
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
Check uncheck all checkboxes with another single checkbox using jquery
67.
68.
69.
70.
71.
72.
HandleError attribute
73.
OutputCache attribute
74.
CacheProfiles
75.
RequireHttps attribute
76.
ValidateInput attribute
77.
78.
79.
Areas
80.
StringLength attribute
81.
Range attribute
82.
83.
RegularExpression attribute
84.
Compare attribute
85.
86.
ValidationSummary
3
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
2. C# Tutorial
3. SQL Tutorial
4. ADO.NET Tutorial
5. ASP.NET Tutorial
6. GridView Tutorial
In this video we will discuss about installing asp.net mvc. Before installing asp.net mvc,
first let's determine what version is already installed. Go to "Control Panel" and click
on"Programs and Features". On my computer at the moment I only have asp.net mvc 2
as shown below.
When you create a new project, make sure you have .NET Framework 4 selected in the drop
down list. If you have any other earlier .net framework versions selected, then ASP.NET
MVC 3 Web Application and ASP.NET MVC 4 Web Application templates will not be
available.
To fix this error, we need to add a view with name, "Index". We will discuss about views in
detail in a later video session. Let's fix it another way. The following is the function that is
automatically added to HomeController class
public ActionResult Index()
{
return View();
}
Change the return type of Index() function from "ActionResult" to "string", and return
string "Hello from MVC Application" instead of View().
6
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
The following URL does not have id. This is not a problem because id is optional in the
default route.
http://localhost/MVCDemo/Home/Index
Now pass id in the URL as shown below and press enter. Nothing happens.
http://localhost/MVCDemo/Home/Index/10
Change the Index() function in HomeController as shown below.
public string Index(string id)
{
return "The value of Id = " + id;
}
Enter the following URL and press enter. We get the output as expected.
http://localhost/MVCDemo/Home/Index/10
In the following URL, 10 is the value for id parameter and we also have a query
string"name".
http://localhost/MVCDemo/home/index/10?name=Pragim
Change the Index() function in HomeController as shown below, to read both the
parameter values.
public string Index(string id, string name)
{
return "The value of Id = " + id + " and Name = " + name;
}
Just like web forms, you can also use "Request.QueryString"
public string Index(string id)
{
return "The value of Id = " + id + " and Name = " + Request.QueryString["name"];
}
Make the following modifications to the Index() function of the HomeController class, so
that, the HomeController returns a view instead of List<string>.
// Change the return type from List<string> to ActionResult
public ActionResult Index()
{
// Store the list of Countries in ViewBag.
ViewBag.Countries = new List<string>()
{
"India",
"US",
"UK",
"Canada"
};
// Finally return a view
return View();
}
We will discuss ViewBag & ViewData, and the differences between them in our next video
session. For now, understand that, ViewBag & ViewData is a mechanism to pass data from
the controller to the view.
Please Note: To pass data from controller to a view, It's always a good practice to use
strongly typed view models instead of using ViewBag & ViewData. We will discuss view
models in a later video session.
Now, copy and paste the following code in "Index.cshtml" view
@{
ViewBag.Title = "Countries List";
}
<h2>Countries List</h2>
<ul>
@foreach (string strCountry in ViewBag.Countries)
{
<li>@strCountry</li>
}
</ul>
Please Note: We use "@" symbol to switch between html and c# code. We will discuss
razor views and their syntax in a later video session.
Both ViewData and ViewBag are used to pass data from a controller to a view. ViewData
is a dictionary of objects that are stored and retrieved using strings as keys. The syntax of
ViewData is very similar to that of ViewState, SessionState and ApplicationState.
// Storing data in ViewData
ViewData["YourData"] = "SomeData";
// Retrieving data from ViewData
string strData = ViewData["YourData"].ToString();
ViewData does not provide compile time error checking. For example, if you mis-spell the
key names you wouldn't get any compile time error. You get to know about the error only at
runtime.
ViewBag uses the dynamic feature that was introduced in to C# 4.0. It allows an object to
have properties dynamically added to it. Using ViewBag the above code can be rewritten as
below.
9
To encapsulate Employee information, add Employee model class to the Models folder. To
do this
1. Right click on "Models" folder > Add > Class
2. Name the class as Employee.cs
3. Click "Add"
Copy and paste the following code in Employee.cs class file.
public class Employee
{
public int EmployeeId { get; set; }
public string Name { get; set; }
public string Gender { get; set; }
public string City { get; set; }
}
Now let's Add EmployeeController class to "Controllers" folder. To do this
1. Right click on "Controllers" folder > Add > Controller
2. Use EmployeeController as the name
3. Click "Add"
We want to use "Employee" model class in EmployeeController. So copy and paste the
following "using" statement in "EmployeeController.cs"
using MVCDemo.Models;
By default an Index() Action method is created in EmployeeController. Change the name of
the function to Details(). Create an instance of Employee class. For now we will hard code
Employee data in this class. In a later video session, we will discuss about retrieving
employee information from the database table tblEmployee. At this point
EmployeeController should look as shown below.
public ActionResult Details()
{
Employee employee = new Employee()
{
EmployeeId = 101,
Name = "John",
Gender = "Male",
10
City = "London"
};
return View();
}
Now, we need to pass the employee model object that we constructed in
EmployeeController to a view, so the view can generate the HTML and send it to the
requested client. To do this we first need to add a view. To add a view
1. Right click on Details() action method and select "Add View" from the context menu
2. Set
a)View Name = Details
b)View Engine = Razor
c)Select "Create strongly typed view" check box
d)From the "Model class" dropdownlist, select "Employee (MVCDemo.Models)"
Note: If Employee class is not visible in the dropdownlist, please build your project and
then try adding the view again.
3. Finally click "Add"
At this point, Details.cshtml should be added to "Employee" folder. Please note that
"Employee" folder is automatically created and added to "Views" folder.
Copy and paste the following code in Details.cshtml file.
@model MVCDemo.Models.Employee
@{
ViewBag.Title = "Employee Details";
}
<h2>Employee Details</h2>
<table style="font-family:Arial">
<tr>
<td>
Employee ID:
</td>
<td>
@Model.EmployeeId
</td>
</tr>
<tr>
<td>
Name:
</td>
<td>
@Model.Name
</td>
</tr>
<tr>
<td>
Gender:
</td>
<td>
@Model.Gender
</td>
</tr>
<tr>
<td>
City:
</td>
<td>
@Model.City
</td>
</tr>
</table>
At this point if you run the project, and if you navigate to the following URL, you get a
runtime error stating - Object reference not set to an instance of an object.
11
http://localhost/MVCDemo/Employee/Details
To fix this error, pass "Employee" object to the view. The "return" statement in Details()
action method need to be modified as shown below.
return View(employee);
That's it. Run the application and navigate to http://localhost/MVCDemo/Employee/Details.
We should get the output as expected.
When the hyperlink is clicked, the user will be redirected to employee details page,
displaying the full details of the employee as shown below.
Copy and paste the following Index() action method in EmployeeController class. This
method retrieves the list of employees, which is then passed on to the view for rendering.
13
into
into
into
into
into
into
into
into
into
tblEmployee
tblEmployee
tblEmployee
tblEmployee
tblEmployee
tblEmployee
tblEmployee
tblEmployee
tblEmployee
values('Mark','Male','London',1)
values('John','Male','Chennai',3)
values('Mary','Female','New York',3)
values('Mike','Male','Sydeny',2)
values('Scott','Male','London',1)
values('Pam','Female','Falls Church',2)
values('Todd','Male','Sydney',1)
values('Ben','Male','New Delhi',2)
values('Sara','Female','London',1)
{
[Table("tblDepartment")]
public class Department
{
public int ID { get; set; }
public string Name { get; set; }
public List<Employee> Employees { get; set; }
}
}
Step 2: Add "Departments" property to "EmployeeContext" class that is present
in"EmployeeContext.cs" file in "Models" folder.
public class EmployeeContext : DbContext
{
public DbSet<Department> Departments { get; set; }
public DbSet<Employee> Employees { get; set; }
}
Step 3: Right click on the "Controllers" folder and add a Controller, with
name=DepartmentController. Copy and paste the following code.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using MVCDemo.Models;
namespace MVCDemo.Controllers
{
public class DepartmentController : Controller
{
public ActionResult Index()
{
EmployeeContext employeeContext = new EmployeeContext();
List<Department> departments = employeeContext.Departments.ToList();
return View(departments);
}
}
}
Step 4: Right click on the Index() action method in DepartmentController class and
select "Add View" from the context menu. Set
View name = Index
View engine = Razor
Select "Create Strongly-typed view" checkbox
Select Department class, from "Model class" dropdownlist
Click "Add" button
Copy and paste the following code in Index.cshtml view file in Department folder
@using MVCDemo.Models;
@model IEnumerable<Department>
<div style="font-family:Arial">
@{
ViewBag.Title = "Departments List";
}
<h2>Departments List</h2>
<ul>
@foreach (Department department in @Model)
16
{
<li>@Html.ActionLink(department.Name, "Index", "Employee",
new { departmentId = department.ID }, null)</li>
}
</ul>
</div>
Changes to Employee List and Detail pages
Add "DepartmentId" property to "Employee" model class that is present
inEmployee.cs file in "Models" folder.
[Table("tblEmployee")]
public class Employee
{
public int EmployeeId { get; set; }
public string Name { get; set; }
public string Gender { get; set; }
public string City { get; set; }
public int DepartmentId { get; set; }
}
Add "departmentId" parameter to Index() action method in "EmployeeController"class
that is present in "EmployeeController.cs" file in "Controllers" folder. Use
the"departmentId" parameter to filter the list of employees as shown below.
public ActionResult Index(int departmentId)
{
EmployeeContext employeeContext = new EmployeeContext();
List<Employee> employees = employeeContext.Employees.Where(emp =>
emp.DepartmentId == departmentId).ToList();
return View(employees);
}
Copy and paste the following line in "Index.cshtml" that is present in "Employee" folder
in "Views" folder. With this change we are able to generate an action link to redirect the
user to a different controller action method.
@Html.ActionLink("Back to Department List", "Index", "Department")
Change the following line in "Details.cshtml" that is present in "Employee" folder
in"Views" folder.
CHANGE THIS LINE @Html.ActionLink("Back to List", "Index")
TO
@Html.ActionLink("Back to Employee List", "Index", new { departmentId
=@Model.DepartmentId })
{
ViewData["Countries"] = new List<string>()
{
"India",
"US",
"UK",
"Canada"
};
return View();
}
}
The following URL will invoke Index() action method with in the
HomeController.Notice that our HomeController inherits from base Controller class which
inturn inherits from ControllerBase class. ControllerBase inturn inherits from IController
class.
http://localhost/MVCDemo/Home/Index
return View() statement with in the HomeController by default looks for a view with
name = "Index" in "/Views/Home/" and "/Views/Shared/" folders. If a view with name =
"Index" is not found, then, we get an error stating
The view 'Index' or its master was not found or no view engine supports the searched
locations. The following locations were searched:
~/Views/Home/Index.aspx
~/Views/Home/Index.ascx
~/Views/Shared/Index.aspx
~/Views/Shared/Index.ascx
~/Views/Home/Index.cshtml
~/Views/Home/Index.vbhtml
~/Views/Shared/Index.cshtml
~/Views/Shared/Index.vbhtml
But with models, there are no strict rules. Infact "Models" folder is optional and they
can live anywhere. They can even be present in a separate project.
Let's now turn our attention to using business objects as model. We will be using
table"tblEmployee" for this demo. Use the sql script to create and populate this table.
Create table tblEmployee
(
Id int Primary Key Identity(1,1),
Name nvarchar(50),
Gender nvarchar(10),
City nvarchar(50),
DateOfBirth DateTime
)
Insert
Insert
Insert
Insert
Insert
into
into
into
into
into
tblEmployee
tblEmployee
tblEmployee
tblEmployee
tblEmployee
values('Mark','Male','London','01/05/1979')
values('John','Male','Chennai','03/07/1981')
values('Mary','Female','New York','02/04/1978')
values('Mike','Male','Sydeny','02/03/1974')
values('Scott','Male','London','04/06/1972')
employee.Gender = rdr["Gender"].ToString();
employee.City = rdr["City"].ToString();
employee.DateOfBirth = Convert.ToDateTime(rdr["DateOfBirth"]);
employees.Add(employee);
}
}
return employees;
}
}
}
}
Step 6: Right click on the "References" folder of the "MVCDemo" project, and add a
reference to "BusinessLayer" project.
Step 7: Include a connection string with name = "DBCS" in Web.Config file
<add name="DBCS"
connectionString="server=.; database=Sample; integrated security=SSPI"
providerName="System.Data.SqlClient"/>
Step 8: Right click on the "Controllers" folder and add Controller with name
="EmployeeController.cs".
public class EmployeeController : Controller
{
public ActionResult Index()
{
EmployeeBusinessLayer employeeBusinessLayer =
new EmployeeBusinessLayer();
List<Employee> employees = employeeBusinessLayer.Employees.ToList();
return View(employees);
}
}
Step 9: Right click on the Index() action method in the "EmployeeController" class and
select "Add View" from the context menu. Set
View name = Index
View engine = Razor
Select "Create a strongly-typed view" checkbox
Scaffold Template = List
Click "Add" button
Run the application and navigate to http://localhost/MVCDemo/Employee/Index. The output
should be as shown below.
20
Copy and paste the following "Create" action method, in EmployeeController class.
[HttpGet]
public ActionResult Create()
{
return View();
}
Please note that, the method is decorated with "HttpGet" attribute. This makes this action
method to respond only to the "GET" request.
Now let's add a "Create" view. To do this, right click on the "Create" action method and
select "Add View" from the context menu. Set
1. View name = "Create"
2. View engine = "Razor"
3. Select "Create Strongly-typed view" checkbox
4. Select "Employee" class, from "Model class" dropdownlist
5. Scaffold Template = Create
6. Click "Add" button
At this point "Create.cshtml" view will be added in "Employee" folder. If you have the
following "Scripts" section at the bottom of the view, please delete it. We will discuss
about sections and scripts in a later video session.
@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
}
Run the application and navigate to the following URL
"http://localhost/MVCDemo/Employee/Index"
Click on "Create New" link. You will be naviaged to the following URL
"http://localhost/MVCDemo/Employee/Create"
A form with textboxes to add a new employee is rendered. For employee "Gender" it is
ideal to have a dropdownlist instead of a text box. To achieve this, REPLACE THE
21
FOLLOWING LINE
@Html.EditorFor(model => model.Gender)
WITH
@Html.DropDownList("Gender", new List<SelectListItem>
{
new SelectListItem { Text = "Male", Value="Male" },
new SelectListItem { Text = "Female", Value="Female" }
}, "Select Gender")
Run the application and notice that, a dropdownlist is now displayed for "Gender".
If you click on "Create" button, you will get an error message stating - The resource cannot
be found. This is because we don't have the "Create" controller action method that can
handle HTTPPost request. We will discuss, fixing this in our next video.
We can use the FormCollection to loop thru each key and it's value that is posted to the
server.
[HttpPost]
public ActionResult Create(FormCollection formCollection)
{
if (ModelState.IsValid)
{
foreach (string key in formCollection.AllKeys)
{
Response.Write("Key = " + key + " ");
Response.Write("Value = " + formCollection[key]);
Response.Write("<br/>");
}
22
}
return View();
}
The output is as shown below
Create the following stored procedure to insert employee data into tblEmployee table
Create procedure spAddEmployee
@Name nvarchar(50),
@Gender nvarchar (10),
@City nvarchar (50),
@DateOfBirth DateTime
as
Begin
Insert into tblEmployee (Name, Gender, City, DateOfBirth)
Values (@Name, @Gender, @City, @DateOfBirth)
End
Add the following method to EmployeeBusinessLayer.cs file.
public void AddEmmployee(Employee employee)
{
string connectionString =
ConfigurationManager.ConnectionStrings["DBCS"].ConnectionString;
using (SqlConnection con = new SqlConnection(connectionString))
{
SqlCommand cmd = new SqlCommand("spAddEmployee", con);
cmd.CommandType = CommandType.StoredProcedure;
SqlParameter paramName = new SqlParameter();
paramName.ParameterName = "@Name";
paramName.Value = employee.Name;
cmd.Parameters.Add(paramName);
SqlParameter paramGender = new SqlParameter();
paramGender.ParameterName = "@Gender";
paramGender.Value = employee.Gender;
cmd.Parameters.Add(paramGender);
SqlParameter paramCity = new SqlParameter();
paramCity.ParameterName = "@City";
paramCity.Value = employee.City;
cmd.Parameters.Add(paramCity);
SqlParameter paramDateOfBirth = new SqlParameter();
paramDateOfBirth.ParameterName = "@DateOfBirth";
paramDateOfBirth.Value = employee.DateOfBirth;
cmd.Parameters.Add(paramDateOfBirth);
con.Open();
cmd.ExecuteNonQuery();
}
}
To save form data, to a database table, copy and paste the following code
23
inEmployeeController.cs file.
[HttpPost]
public ActionResult Create(FormCollection formCollection)
{
Employee employee = new Employee();
// Retrieve form data using form collection
employee.Name = formCollection["Name"];
employee.Gender = formCollection["Gender"];
employee.City = formCollection["City"];
employee.DateOfBirth =
Convert.ToDateTime(formCollection["DateOfBirth"]);
EmployeeBusinessLayer employeeBusinessLayer =
new EmployeeBusinessLayer();
employeeBusinessLayer.AddEmmployee(employee);
return RedirectToAction("Index");
}
Do we really have to write all the dirty code of retrieving data from FormCollection and
assign it to the properties of "employee" object. The answer is no. This is the job of
themodelbinder in MVC. We will discuss modelbinders in our next video.
employee.DateOfBirth = dateOfBirth;
EmployeeBusinessLayer employeeBusinessLayer =
new EmployeeBusinessLayer();
employeeBusinessLayer.AddEmmployee(employee);
return RedirectToAction("Index");
}
But do we really to do these mappings manually. The answer is no. In a later video session
we will see how to automatically map the request data, without having to do it manually.
Please note that, the order of the parameters does not matter. What matters is the
name of the parameter. If the parameter name is different from the form control name,
then the form data will not be mapped as expected.
employeeBusinessLayer.AddEmmployee(employee);
return RedirectToAction("Index");
}
return View();
}
When you make this change, you get a compilation error stating - Type
'MVCDemo.Controllers.EmployeeController' already defines a member called 'Create' with
the same parameter types.Our intention here is to overload the "Create" controller action
method based on "HttpGet" and "HttpPost". To fix this error, use "ActionName"attribute
as shown below.
[HttpGet]
[ActionName("Create")]
public ActionResult Create_Get()
{
return View();
}
[HttpPost]
[ActionName("Create")]
public ActionResult Create_Post()
{
if (ModelState.IsValid)
{
EmployeeBusinessLayer employeeBusinessLayer =
new EmployeeBusinessLayer();
Employee employee = new Employee();
UpdateModel<Employee>(employee);
employeeBusinessLayer.AddEmmployee(employee);
return RedirectToAction("Index");
}
return View();
}
Please Note:
1. We have changed the names of "Create" action methods
to "Create_Get" and"Create_Post" depending on the actions they respond to.
2. "ActionName" is specified as "Create" for both of these methods. So, if
a "GET"request is made to the "URL http://localhost/MVCDemo/Employee/Create" then"Create_Get()" controller action
method is invoked. On the other hand if a "POST"request is made to the same URL,
then "Create_Post()" controller action method is invoked.
3. Instead of passing "Employee" object as a parameter to "Create_Post()" action
method, we are creating an instance of an "Employee" object with in the function, and
updating it using "UpdateModel()" function. "UpdateModel()" function inspects all
theHttpRequest inputs such as posted Form data, QueryString, Cookies and Server
variables and populate the employee object.
When you run the application, you may get an intermittent error stating - Adding the
specified count to the semaphore would cause it to exceed its maximum count. To fix this
error, either
1. Restart IIS
OR
2. Disable connection pooling in the connection string of your web.config file
26
Name
City
DateOfBirth
To achieve this we can use "Required" attribute that is present
inSystem.ComponentModel.DataAnnotations namespace. To use this namespace,
BusinessLayer project need a reference to "EntityFramework" assembly. The changes to
the "Employee" class are shown below.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.DataAnnotations;
namespace BusinessLayer
{
public class Employee
{
public int ID { get; set; }
[Required]
public string Name { get; set; }
public string Gender { get; set; }
[Required]
public string City { get; set; }
[Required]
public DateTime? DateOfBirth { get; set; }
}
}
Run the application and navigate to the following URL.
http://localhost/MVCDemo/Employee/Create
Submit the page without entering any data. We now get an error stating - The model of
type 'BusinessLayer.Employee' could not be updated. Notice that this error is thrown when
UpdateModel() function is invoked.
Now let's use TryUpdateModel() instead of UpdateModel(). Make changes
to"Create_Post()" controller action method in "EmployeeController" as shown below.
[HttpPost]
[ActionName("Create")]
public ActionResult Create_Post()
{
EmployeeBusinessLayer employeeBusinessLayer =
new EmployeeBusinessLayer();
Employee employee = new Employee();
TryUpdateModel(employee);
if (ModelState.IsValid)
{
employeeBusinessLayer.AddEmmployee(employee);
return RedirectToAction("Index");
}
else
{
return View();
}
}
Run the application and navigate to the following URL
http://localhost/MVCDemo/Employee/Create
29
Submit the page without entering any data. Notice that, we don't get an exception now and
the user remains on "Create" view and the validation errors are displayed to the user.
So, the difference is UpdateModel() throws an exception if validation fails, where
asTryUpdateModel() will never throw an exception. The similarity is, both the functions
are used to update the Model with the Form values and perform the validations.
Is it mandatory to use "UpdateModel()" or "Try"UpdateModel()" function to update the
Model?
The answer is NO.
The above method can be re-written as shown below and we get the same behaviour.
[HttpPost]
[ActionName("Create")]
public ActionResult Create_Post(Employee employee)
{
EmployeeBusinessLayer employeeBusinessLayer =
new EmployeeBusinessLayer();
if (ModelState.IsValid)
{
employeeBusinessLayer.AddEmmployee(employee);
return RedirectToAction("Index");
}
else
{
return View();
}
}
So the next question is, Why do we need to explicitly invoke model binding?
If you want to limit on what can be bound, explicitly invoking model binding can be very
useful. We will discuss more about this in a later video session.
context menu
b) Set
View name = Edit
View engine = Razor
Select "Create a strongly-typed view" check box
Model class = "Employee"
Scaffold template = "Edit"
Finally click "Add" button
c) This should add "Edit.cshtml" to "Employee" folder in "Views" foolder
d) Delete the following scripts section that is present at the bottom of "Edit.cshtml" view
@section Scripts
{
@Scripts.Render("~/bundles/jqueryval")
}
Run the application and navigate to http://localhost/MVCDemo/Employee/Index. This page
should list all the employees. Click on "Edit" link. The "Edit" page should display the details
of the "Employee" being edited. Notice that, by default "textboxes" are used for editing.
It is ideal to have a dropdownlist for gender rather than a textbox. To achieve this. make
the following changes to "Edit.cshtml"
REPLACE THE FOLLOWING CODE
@Html.EditorFor(model => model.Gender)
@Html.ValidationMessageFor(model => model.Gender)
WITH
@Html.DropDownList("Gender", new List<SelectListItem>
{
new SelectListItem { Text = "Male", Value="Male" },
new SelectListItem { Text = "Female", Value="Female" }
}, "Select Gender")
@Html.ValidationMessageFor(model => model.Gender)
Run the application. Edit an employee, and notice that a DropDownList is used for gender
as expected. Post the form by clicking on "Save" button. We will get an error stating - The
resource cannot be found. We will discuss fixing this in our next video.
passed to SaveEmployee() method, which saves the employee details. After the employee
details are saved, the user is redirected to "Index" action.
3. If there are model validation errors, none of the code in the IF block gets executed. In
this case, the user stays on the "Edit" view. Since we are passing "Employee" object to
the "Edit" view, the user gets to see the validation errors. This allows him to fix those
errors and re-submit the view.
the moment, "Employee Edit" view can be used to change all of the following fields.
Name
Gender
City
DateOfBirth
In this video we will discuss, preventing unintended updates in mvc. Please watch Part 19,
before proceeding.
Modify "Edit" controller action method that is decorated with [HttpPost] attribute as shown
below. This method is present in "EmployeeController.cs" file.
[HttpPost]
[ActionName("Edit")]
public ActionResult Edit_Post(int id)
{
EmployeeBusinessLayer employeeBusinessLayer = new EmployeeBusinessLayer();
Employee employee = employeeBusinessLayer.Employees.Single(x => x.ID == id);
UpdateModel(employee, new string[] { "ID", "Gender", "City", "DateOfBirth" });
if (ModelState.IsValid)
{
employeeBusinessLayer.SaveEmployee(employee);
return RedirectToAction("Index");
}
return View(employee);
}
Please note:
1. The name of the method is changed from "Edit" to "Edit_Post"
2. The method is decorated with [ActionName("Edit")] and [HttpPost] attributes. This
indicates that, this method is going to respond to "Edit" action, when the form is posted to
the server.
3. The "id" of the employee that is being edited, is passed as a parameter to this method.
4. Using the "id" parameter we load the employee details(Id, Name, Gender, City &
DateOfBirth) from the database.
Employee employee = employeeBusinessLayer.Employees.Single(x => x.ID == id);
5. We then call UpdateModel() function. This should automatically
update "Employee"object with data from the posted form. We are also passing a string
array as the second parameter. This parameter specifies the list of model properties to
update. This is also called as include list or white list. Notice that, we did not
include "Name" property in the list. This means, even if the posted form data contains
value for "Name" property, it will not be used to update the "Name" property of
the "Employee" object.
UpdateModel(employee, new string[] { "ID", "Gender", "City", "DateOfBirth" });
So, if we were to generate a post request using fiddler as we did in the previous
session,"Name" property of the "Employee" object will not be updated.
Alternatively, to exclude properties from binding, we can specify the exclude list as shown
below.
[HttpPost]
[ActionName("Edit")]
public ActionResult Edit_Post(int id)
{
EmployeeBusinessLayer employeeBusinessLayer = new EmployeeBusinessLayer();
Employee employee = employeeBusinessLayer.Employees.Single(x => x.ID == id);
UpdateModel(employee, null, null, new string[] { "Name" });
if (ModelState.IsValid)
{
employeeBusinessLayer.SaveEmployee(employee);
34
return RedirectToAction("Index");
}
return View(employee);
}
Notice that we are using a different overloaded version of UpdateModel() function. We are
passing "NULL" for "prefix" and "includeProperties" parameters.
UpdateModel<TModel>(TModel model, string prefix, string[] includeProperties, string[]
excludeProperties)
[Required]
public string City { get; set; }
[Required]
public DateTime? DateOfBirth { get; set; }
}
So, if we were to generate a post request using fiddler as we did in the previous
session,"Name" property of the "Employee" object will not be updated.
Alternatively, to exclude properties from binding, we can specify the exclude
list using"Bind" attribute as shown below.
[HttpPost]
[ActionName("Edit")]
public ActionResult Edit_Post([Bind(Exclude = "Name")] Employee employee)
{
// Rest of the method implementation remains the same
}
con.Open();
cmd.ExecuteNonQuery();
}
}
Step 3: Add the following "DELETE" controller action method to"EmployeeController".
public ActionResult Delete(int id)
{
EmployeeBusinessLayer employeeBusinessLayer =
new EmployeeBusinessLayer();
employeeBusinessLayer.DeleteEmployee(id);
return RedirectToAction("Index");
}
Run the application and navigate to "Index" action. Click the "Delete" link. This
issues"GET" request to the following URL, and deletes the record.
http://localhost/MVCDemo/Employee/Delete/1
Deleting database records using GET request opens a security hole and is not
recommended by Microsoft. Just imagine what can happen if there is an image tag in a
malicious email as shown below. The moment we open the email, the image tries to load
and issues a GET request, which would delete the data.
<img src="http://localhost/MVCDemo/Employee/Delete/2" />
Also, when search engines index your page, they issue a GET request which would delete
the data. In general GET request should be free of any side-effects, meaning it should not
change the state.
Deletes should always be performed using a POST request. We will discuss, implementing
this in our next video.
<tr>
<td>
@Html.DisplayFor(modelItem => item.Name)
</td>
<td>
@Html.DisplayFor(modelItem => item.Gender)
</td>
<td>
@Html.DisplayFor(modelItem => item.City)
</td>
<td>
@Html.DisplayFor(modelItem => item.DateOfBirth)
</td>
<td>
@Html.ActionLink("Edit", "Edit", new { id=item.ID }) |
@Html.ActionLink("Details", "Details", new { id=item.ID }) |
@Html.ActionLink("Delete", "Delete", new { id=item.ID })
</td>
</tr>
}
WITH
@foreach (var item in Model)
{
using (Html.BeginForm("Delete", "Employee", new { id = item.ID }))
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Name)
</td>
<td>
@Html.DisplayFor(modelItem => item.Gender)
</td>
<td>
@Html.DisplayFor(modelItem => item.City)
</td>
<td>
@Html.DisplayFor(modelItem => item.DateOfBirth)
</td>
<td>
@Html.ActionLink("Edit", "Edit", new { id = item.ID }) |
<input type="submit" value="Delete" />
</td>
</tr>
}
}
Notice that, we are using "Html.BeginForm()" html helper to generate a form tag.
Step 3: To include client-side confirmation, before the data can be deleted, add
the"onclick" attribute to "Delete" button as shown below.
<input type="submit" value="Delete" onclick="return confirm('Are you sure you want to
delete record with ID = @item.ID');" />
39
We will be using tables tblDepartment and tblEmployee for this demo. You can get the
sql script to create and populate these tables from Part 10 of this video series.
Step 1: Create a new asp.net mvc 4 web application.
Step 2: Right click on the "Models" folder and add "ADO.NET Entity Data Model". Set
Name = EmployeeDataModel.edmx.
On the subsequent screen, select "Generate from database" option and click "Next".
On "Choose your data connection screen", click on "New Connection" button.
Specify the sql server name. In my case, I have sql server installed on my local machine. So
I have set "Server Name=(local)". From "Select or enter a database
name"dropdownlist, select the Database name and click "OK".
Click "Next".
40
On Create and Edit views, please delete the following scripts section. We will discuss these
in a later video session.
@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
}
At this point, if you run the application by pressing CTRL + F5, you will get an error stating
- The resource cannot be found. This is because, by default, the application goes to "HOME"
controller and "Index" action.
To fix this,
1. Open "RouteConfig.cs" file from "App_Start" folder
2. Set Controller = "Employee"
Run the application again. Notice that, all the employees are listed on the index view. We
can also create a new employee, edit an employee, view their full details and delete an
employee as well. However, there are few issues, with each of the views, which we will
address in our upcoming videos.
In this video we will discuss, customizing the auto-generated create view. Please watch Part
26, before proceeding.
At the moment, none of the fields on "Create" view are required. This means,when you
click on the "Create" button without filling any data, NULL values are stored in all the
columns of tblEmployee table.
So, how to make these fields on the "Create" view required?
Add [Required] attribute to the "Employee" class. The "Employee" class that is present
in "EmployeeDataModel.Designer.cs" is auto-generated by the entity framework.
There is no point in adding the [Required] attribute to this class, as we will loose the
To achieve this, add a class file with "name=Employee.cs" to "Models" folder.
Copy and paste the following code in "Employee.cs" file
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
namespace MVCDemo.Models
{
[MetadataType(typeof(EmployeeMetaData))]
public partial class Employee
{
}
public class EmployeeMetaData
{
[Required]
public string Name { get; set; }
[Required]
public string Gender { get; set; }
[Required]
public string City { get; set; }
[Required]
[Display(Name="Department")]
public int DepartmentId { get; set; }
}
}
At this point, run the application and click on the "Create" button without filling any data.
Notice that we get validation error messages as expected. In a later video session, we will
discuss changing the colour of the validation messages.
If you want "Select Department" as the first item in the "Department" dropdownlist
on"Create" view, then,
REPLACE THE FOLLOWING LINE
@Html.DropDownList("DepartmentId", String.Empty)
WITH
@Html.DropDownList("DepartmentId", "Select Department")
Notice that, a textbox is used for gender. It is ideal to have a dropdownlist for gender rather
than a textbox. To achieve this, make the following changes to "Create.cshtml"view.
REPLACE THE FOLLOWING CODE
43
employeeFromDB.EmployeeId = employee.EmployeeId;
employeeFromDB.Gender = employee.Gender;
employeeFromDB.City = employee.City;
employeeFromDB.DepartmentId = employee.DepartmentId;
employee.Name = employeeFromDB.Name;
if (ModelState.IsValid)
{
db.ObjectStateManager.ChangeObjectState(employeeFromDB, EntityState.Modified);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.DepartmentId = new SelectList(db.Departments, "Id", "Name",
employee.DepartmentId);
return View(employee);
}
Please note that, we have excluded "Name" property from model binding
using "Bind"attribute. Even without BIND attribute, users will not be able to change
the "NAME" of the employee, as we are copying only the required properties(Excluding
NAME property) from"employee" object to "employeeFromDB" which in turn is persisted
to the database. Since, I want to demonstrate adding model validation errors dynamically,
let the attribute be there.
At this point if we run the application and click on "Save" button on "Edit" view, we get a
validation error stating - The Name field is required. This is because "Name" property is
decorated with [Required] attribute in "Employee.cs" file. To prevent the validation error,
remove the [Required] attribute.
The problem with this change is that, "Name" field on "Create" view is no longer
mandatory. This means we will now be able to create a new employee, without NAME. To
fix the "Create" view, let's add model validation errors dynamically. Change the
implementation of "Create" controller action method that responds to [HttpPost] request
as shown below.
[HttpPost]
public ActionResult Create(Employee employee)
{
if (string.IsNullOrEmpty(employee.Name))
{
ModelState.AddModelError("Name", "The Name field is required.");
}
if (ModelState.IsValid)
{
db.Employees.AddObject(employee);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.DepartmentId = new SelectList(db.Departments, "Id", "Name",
employee.DepartmentId);
return View(employee);
}
the Employee orDepartment class does not have Total property. This is one example,
where a Data Transfer Object can be used as a model.
Right click on the "Models" folder and add a class with name="DepartmentTotals.cs".
Copy and paste the following code.
public class DepartmentTotals
{
public string Name { get; set; }
public int Total { get; set; }
}
Now add the following "EmployeesByDepartment" controller action method
toEmployeeController class.
public ActionResult EmployeesByDepartment()
{
var departmentTotals = db.Employees.Include("Department")
.GroupBy(x => x.Department.Name)
.Select(y => new DepartmentTotals
{
Name = y.Key, Total = y.Count()
}).ToList();
return View(departmentTotals);
}
At this point, build the solution, so that the newly added DepartmentTotals class is
compiled.
Now right click on "EmployeesByDepartment" action method
in"EmployeeController" and select "Add View" from the context menu.
View name = EmployeesByDepartment
View engine = Razor
Select "Create a strongly-typed view" checkbox
Model class = DepartmentTotals
Model class = DepartmentTotals
To list the employees in ascending order of total employee, use OrderBy() LINQ method
as shown below.
var departmentTotals = db.Employees.Include("Department")
.GroupBy(x => x.Department.Name)
.Select(y => new DepartmentTotals
{
Name = y.Key, Total = y.Count()
}).ToList().OrderBy(y => y.Total);
To sort the list in descending order use, OrderByDescending() LINQ method.
var departmentTotals = db.Employees.Include("Department")
.GroupBy(x => x.Department.Name)
.Select(y => new DepartmentTotals
{
Name = y.Key, Total = y.Count()
46
a).cshtml
b).vbhtml
c).aspx
d).ascx
If I have all of the following files in "Views/Employee" folder, then MVC picks up
"Index.aspx"
a) Index.aspx
b) Index.cshtml
c) Index.vbhtml
d) Index.ascx
If you want to use "Index.cshtml" instead, then specify the full path as shown
below.
public ActionResult Index()
{
var employees = db.Employees.Include("Department");
return View("~/Views/Employee/Index.cshtml", employees.ToList());
}
If you specify only the name of the view along with it's extension as shown below, you will
get an error.
return View("Index.cshtml", employees.ToList());
If you want to use a view name which is not inside the views folder of the current controller,
then specify the full path as shown below.
public ActionResult Index()
{
var employees = db.Employees.Include("Department");
return View("~/Views/Home/Index.aspx", employees.ToList());
}
Please note that asp.net mvc
1. Is all about convention over configuration
2. Views folder contains one folder for each controller and a "Shared" folder
3. Shared folder is used to store views shared between controllers. Master and Layout
pages are stored in "Shared" folder.
The downside of hard coding dropdownlist values with-in code is that, if we have to add or
remove departments from the dropdownlist, the code needs to be modified.
In most cases, we get values from the database table. For this example, let's use entity
framework to retrieve data. Add ADO.NET entity data model. We discussed working with
entity framework in Part 8 & Part 25.
To pass list of Departments from the controller, store them in "ViewBag"
public ActionResult Index()
{
// Connect to the database
SampleDBContext db = new SampleDBContext();
// Retrieve departments, and build SelectList
ViewBag.Departments = new SelectList(db.Departments, "Id", "Name");
return View();
}
Now in the "Index" view, access Departments list from "ViewBag"
@Html.DropDownList("Departments", "Select Department")
}
public List<Department> Departments
{
get
{
SampleDBContext db = new SampleDBContext();
return db.Departments.ToList();
}
}
public string CompanyName
{
get
{
return _name;
}
set
{
_name = value;
}
}
}
Copy and paste the following code in HomeController class. Notice that we are storing
the "Departments" and "CompanyName" in the ViewBag object.
public ActionResult Index()
{
Company company = new Company("Pragim");
ViewBag.Departments = new SelectList(company.Departments, "Id", "Name");
ViewBag.CompanyName = company.CompanyName;
return View();
}
Right click on the "Index" action method in "HomeController" and add a view
with"name=Index". Copy and paste the following code. Notice that, here the view is not
strongly typed, and we are hard-coding the name for TextBox and DropDownListHTML
helpers.
@{
ViewBag.Title = "Index";
}
<h2>Index</h2>
@Html.TextBox("CompanyName", (string)ViewBag.CompanyName)
<br />
@Html.DropDownList("Departments", "Select Department")
Add the following "Index1" action method to "HomeController". Notice that we are
passing "Company" object to the View, and hence the view is strongly typed. Since the
view is strongly typed, we can use TextBoxFor and DropDownListFor HTML helpers.
public ActionResult Index1()
{
Company company = new Company("Pragim");
return View(company);
}
Right click on the "Index1" action method in "HomeController" and add a view
with"name=Index1". Copy and paste the following code.
@model MVCDemo.Models.Company
@{
52
ViewBag.Title = "Index1";
}
<h2>Index1</h2>
@Html.TextBoxFor(m => m.CompanyName)
<br />
@Html.DropDownListFor(m =>
m.Departments, new SelectList(Model.Departments, "Id","Name"), "Select Department")
At this point, run the application and navigate
to"http://localhost/MVCDemo/home/index". A textbox and a dropdownlist will be
rendered. Right click on the page and view it's source. The generated HTML is as shown
below.
<h2>Index</h2>
<input id="CompanyName" name="CompanyName" type="text" value="Test" />
<br />
<select id="Departments" name="Departments"><option value="">Select
Department</option>
<option value="1">IT</option>
<option value="2">HR</option>
<option value="3">Payroll</option>
</select>
Now navigate to "http://localhost/MVCDemo/home/index1" and view page source. The
HTML will be exactly the same as above.
So, in short, here are the differences
Html.TextBox amd Html.DropDownList are not strongly typed and hence they doesn't
require a strongly typed view. This means that we can hardcode whatever name we want.
On the other hand, Html.TextBoxFor and Html.DropDownListFor are strongly typed and
requires a strongly typed view, and the name is inferred from the lambda expression.
Strongly typed HTML helpers also provide compile time checking.
Since, in real time, we mostly use strongly typed views, prefer to use Html.TextBoxFor and
Html.DropDownListFor over their counterparts.
Whether, we use Html.TextBox & Html.DropDownList OR Html.TextBoxFor &
Html.DropDownListFor, the end result is the same, that is they produce the same HTML.
Strongly typed HTML helpers are added in MVC2.
Right click on the "Models" folder and add a class file with "name=Company.cs".
Copy and paste the following code.
public class Company
{
public string SelectedDepartment { get; set; }
public List<Department> Departments
{
get
{
SampleDBContext db = new SampleDBContext();
return db.Departments.ToList();
}
}
}
Copy and paste the following 2 "Index" action methods in HomeController class.
[HttpGet]
public ActionResult Index()
{
Company company = new Company();
return View(company);
}
[HttpPost]
public string Index(Company company)
{
if (string.IsNullOrEmpty(company.SelectedDepartment))
53
{
return "You did not select any department";
}
else
{
return "You selected department with ID = " + company.SelectedDepartment;
}
}
Right click on the "Index" action method in "HomeController" and add a view
with"name=Index". Copy and paste the following code.
@model MVCDemo.Models.Company
@{
ViewBag.Title = "Index";
}
<h2>Index</h2>
@using (Html.BeginForm())
{
foreach (var department in Model.Departments)
{
@Html.RadioButtonFor(m => m.SelectedDepartment,
department.Id)@department.Name
}
<br />
<br />
<input type="submit" value="Submit" />
}
Run the application and click on "Submit" without selecting any department. Notice that,
you get a message stating you have not selected any department. On the other hand,
select a department and click "Submit". The selected department ID must be displayed.
{
Company company = new Company();
return View(company);
}
[HttpPost]
public string Index(Company company)
{
if (string.IsNullOrEmpty(company.SelectedDepartment))
{
return "You did not select any department";
}
else
{
return "You selected department with ID = " + company.SelectedDepartment;
}
}
Right click on the "Index" action method in "HomeController" and add a view
with"name=Index". Copy and paste the following code.
@model MVCDemo.Models.Company
@{
ViewBag.Title = "Index";
}
<h2>Index</h2>
@using (Html.BeginForm())
{
foreach (var department in Model.Departments)
{
@Html.RadioButtonFor(m => m.SelectedDepartment,
department.Id)@department.Name
}
<br />
<br />
<input type="submit" value="Submit" />
}
Run the application and click on "Submit" without selecting any department. Notice that,
you get a message stating you have not selected any department. On the other hand,
select a department and click "Submit". The selected department ID must be displayed.
55
We should be generating a checkbox for each city from the table tblCity.
into
into
into
into
into
tblCity
tblCity
tblCity
tblCity
tblCity
values
values
values
values
values
('London', 0)
('New York', 0)
('Sydney', 1)
('Mumbai', 0)
('Cambridge', 0)
Right click on the "Controllers" folder, and add a "HomeController". Include the
following 2 namespaces in "HomeController"
using MVCDemo.Models;
using System.Text;
Copy and paste the following code.
[HttpGet]
public ActionResult Index()
{
SampleDBContext db = new SampleDBContext();
return View(db.Cities);
}
[HttpPost]
public string Index(IEnumerable<City> cities)
{
if (cities.Count(x => x.IsSelected) == 0)
56
{
return "You have not selected any City";
}
else
{
StringBuilder sb = new StringBuilder();
sb.Append("You selected - ");
foreach (City city in cities)
{
if (city.IsSelected)
{
sb.Append(city.Name + ", ");
}
}
sb.Remove(sb.ToString().LastIndexOf(","), 1);
return sb.ToString();
}
}
Right click on the "Views" folder, and a "Home" folder. Right click on the "Home" folder
and "EditorTemplates" folder.
Right click on "EditorTemplates" folder > Add > View. In the "Add View" dialog box, set
View Name = City
View Engine = Razor
and click "Add".
Copy and paste the following code in "City.cshtml"
@model MVCDemo.Models.City
@{
ViewBag.Title = "City";
}
@Html.HiddenFor(x => x.ID)
@Html.HiddenFor(x => x.Name)
@Html.CheckBoxFor(x => x.IsSelected)
@Html.DisplayFor(x => x.Name)
Please Note: Put the templates in "Shared" folder, if you want the "Templates", to be
available for all the views.
Right click on the "Index" action method in "HomeController", and select "Add
View"from the contex menu. Set
View Name = Index
View Engine = Razor and click "Add"
Copy and paste the following code in "Index.cshtml"
@model IEnumerable<MVCDemo.Models.City>
@{
ViewBag.Title = "Index";
}
<div style="font-family:Arial">
<h2>Index</h2>
@using (Html.BeginForm())
{
@Html.EditorForModel()
<br />
57
Please refer to Part 38, if you need the sql script to create and populate this table. Please
watch Part 38, before proceeding.
The generated listbox should be as shown below. Notice that for each city in table "tblCity",
there is an entry in the listbox.
For this demo, we will be using the "City" entity that we generated using ADO.NET entity
framework, in Part 38.
The first step is to create a ViewModel class. In asp.net mvc, view model's are used as
techniques to shuttle data between the controller and the view. Right click on the "Models"
folder, and add a class file with name=CitiesViewModel.cs. Copy and paste the following
code. This class is going to be the Model for the view.
public class CitiesViewModel
{
public IEnumerable<string> SelectedCities { get; set; }
public IEnumerable<SelectListItem> Cities { get; set; }
}
Right click on the "Controllers" folder, and add a "HomeController". Include the
following 2 namespaces in "HomeController"
using MVCDemo.Models;
using System.Text;
Copy and paste the following code.
[HttpGet]
public ActionResult Index()
58
{
SampleDBContext db = new SampleDBContext();
List<SelectListItem> listSelectListItems = new List<SelectListItem>();
foreach (City city in db.Cities)
{
SelectListItem selectList = new SelectListItem()
{
Text = city.Name,
Value = city.ID.ToString(),
Selected = city.IsSelected
};
listSelectListItems.Add(selectList);
}
CitiesViewModel citiesViewModel = new CitiesViewModel()
{
Cities = listSelectListItems
};
return View(citiesViewModel);
}
[HttpPost]
public string Index(IEnumerable<string> selectedCities)
{
if (selectedCities == null)
{
return "No cities selected";
}
else
{
StringBuilder sb = new StringBuilder();
sb.Append("You selected - " + string.Join(",", selectedCities));
return sb.ToString();
}
}
Right click on the "Index" action method in "HomeController" and select "Add
View"from the context menu. Set
View Name = Index
View Engine = Razor
and click "Add".
Copy and paste the following code in "Index.cshtml"
@model MVCDemo.Models.CitiesViewModel
@{
ViewBag.Title = "Index";
}
<div style="font-family:Arial">
<h2>Index</h2>
@using (Html.BeginForm())
{
@Html.ListBoxFor(m => m.SelectedCities, Model.Cities, new { size = 4 })
<br />
<input type="submit" value="Submit" />
}
</div>
59
Note: To select multiple items from the listbox, hold down the CTRL Key.
display attributes. Right click on the "Models" folder and add Employee.cs class file. Copy
and paste the following code.
namespace MVCDemo.Models
{
[MetadataType(typeof(EmployeeMetaData))]
public partial class Employee
{
}
public class EmployeeMetaData
{
//If you want "FullName" to be displayed as "Full Name",
//use DisplayAttribute or DisplayName attribute.
//DisplayName attribute is in System.ComponentModel namespace.
//[DisplayAttribute(Name="Full Name")]
//[Display(Name = "Full Name")]
[DisplayName("Full Name")]
public string FullName { get; set; }
//To get only the date part in a datetime data type
//[DisplayFormat(DataFormatString = "{0:d}")]
//[DisplayFormatAttribute(DataFormatString="{0:d}")]
//To get time in 24 hour notation
//[DisplayFormat(DataFormatString = "{0:dd/MM/yyyy HH:mm:ss}")]
//To get time in 12 hour notation with AM PM
[DisplayFormat(DataFormatString = "{0:dd/MM/yyyy hh:mm:ss tt}")]
public DateTime? HireDate { get; set; }
// If gender is NULL, "Gender not specified" text will be displayed.
[DisplayFormat(NullDisplayText = "Gender not specified")]
public string Gender { get; set; }
//If you don't want to display a column use ScaffoldColumn attribute.
//This only works when you use @Html.DisplayForModel() helper
[ScaffoldColumn(false)]
public int? Salary { get; set; }
}
}
Make sure to include the following using statements:
using System.ComponentModel.DataAnnotations;
using System.ComponentModel;
We will discuss the following attributes in our next video session.
DataTypeAttribute,
DisplayColumnAttribute
}
public class EmployeeMetaData
{
// Display mailto hyperlink
[DataType(DataType.EmailAddress)]
public string EmailAddress { get; set; }
// Display currency symbol. For country specific currency, set
// culture using globalization element in web.config.
// For Great Britain Pound symbol
// <globalization culture="en-gb"/>
[DataType(DataType.Currency)]
public int? Salary { get; set; }
// Generate a hyperlink
[DataType(DataType.Url)]
public string PersonalWebSite { get; set; }
// Display only Time Part
// [DataType(DataType.Time)]
// Display only Date Part
[DataType(DataType.Date)]
public DateTime? HireDate { get; set; }
}
DisplayColumn attribute is useful, when a class has a property of complex type, and you
want to pick one property of this complex object for display purpose. Let's understand this
with an example.
Right click on the "Models" folder and add Company.cs class file. Copy and paste the
following code.
public class Company
{
public Employee CompanyDirector
{
get
{
SampleDBContext db = new SampleDBContext();
return db.Employees.Single(x => x.Id == 1);
}
}
}
Notice that, this class has CompanyDirector property which returns an Employeeobject.
Employee is a complex type. Employee object has got several properties. If you want
FullName to be used for display purpose, then make the following changes.
Decorate "Employee" partial class in "Models" folder, with DisplayColumn attribute.
[MetadataType(typeof(EmployeeMetaData))]
[DisplayColumn("FullName")]
public partial class Employee
{
}
Change "Details" action method in "Home" controller as shown below.
public ActionResult Details(int id)
{
Company company = new Company();
return View(company);
}
Copy and paste the following code in Details.cshtml view
@model MVCDemo.Models.Company
62
@{
ViewBag.Title = "Details";
}
@Html.DisplayTextFor(x => x.CompanyDirector)
Navigate to localhost/MVCDemo/Home/Details/1 and you should see the FullName of the
employee.
return View(employee);
}
[HttpPost]
public ActionResult Edit(Employee employee)
{
if (ModelState.IsValid)
{
SampleDBContext db = new SampleDBContext();
Employee employeeFromDB = db.Employees.Single(x => x.Id == employee.Id);
// Populate all the properties except EmailAddrees
employeeFromDB.FullName = employee.FullName;
employeeFromDB.Gender = employee.Gender;
employeeFromDB.Age = employee.Age;
employeeFromDB.HireDate = employee.HireDate;
employeeFromDB.Salary = employee.Salary;
employeeFromDB.PersonalWebSite = employee.PersonalWebSite;
db.ObjectStateManager.ChangeObjectState(employeeFromDB,
System.Data.EntityState.Modified);
db.SaveChanges();
return RedirectToAction("Details", new { id = employee.Id });
}
return View(employee);
}
Edit.cshtml view
@model MVCDemo.Models.Employee
@{
ViewBag.Title = "Edit";
}
<div style="font-family:Arial">
@using (Html.BeginForm())
{
@Html.EditorForModel()
<br />
<br />
<input type="submit" value="Save" />
}
</div>
@{
ViewBag.Title = "Details";
}
<h2>Details</h2>
<fieldset>
<legend>Employee</legend>
@Html.Display("EmployeeData")
</fieldset>
At this point, if you run the application, you should be able to view Employee details, as
expected.
Now, change the implementation of "Details" action method with in home controller as
shown below. Notice that, instead of storing the "Employee" object in ViewData, we are
passing it to the View.
public ActionResult Details(int id)
{
SampleDBContext db = new SampleDBContext();
Employee employee = db.Employees.Single(x => x.Id == id);
return View(employee);
}
Change Details.cshtml view as below. We have specified "Employee" as the model
object. So, here we are working with a strongly typed view, and hence we are
using@Html.DisplayFor(model => model) templated helper. Since, none of the properties
of Employee class return a complex object, the ideal choice here would be, to use
@Html.DisplayForModel() templated helper. In either cases, in this scenario you will get the
same output.
@model MVCDemo.Models.Employee
@{
ViewBag.Title = "Details";
}
<h2>Details</h2>
<fieldset>
<legend>Employee</legend>
@Html.DisplayFor(model => model)
</fieldset>
You work with Editor templates in the same way. In HomeController,
implement Editaction method as shown below.
public ActionResult Edit(int id)
{
SampleDBContext db = new SampleDBContext();
Employee employee = db.Employees.Single(x => x.Id == id);
return View(employee);
}
and in Edit.cshtml view
@model MVCDemo.Models.Employee
@{
ViewBag.Title = "Edit";
}
<h2>Edit</h2>
@using (@Html.BeginForm())
{
@Html.EditorForModel()
}
67
Notice that, for HireDate, users have to type in the date. Dates have got different formats.
For example, MM/DD/YYYY or DD/MM/YY etc. So, different users may type it differently.
Also, from a user experience, it is better to display a DateTime picker from which the user
can simply select the date.
The built-in DateTime editor template used by MVC, simply displays a textbox for editing
Dates. So, let's customize the DateTime editor template, to use jQuery calendar. We want
the output as shown below.
68
The following is the convention used by MVC to find the customized templates
1. The customized display templates must be in a sub-folder that is named
-DisplayTemplates. Editor templates must be in a sub-folder that is named
-EditorTemplates.
2. These sub-folders can live in "Shared" folder, or a specific views folder. If these folders
are present in the Shared folder, then the templates are available for all the views. If they
are in a specific views folder, then, they are available only for that set of views.
3. The name of the template must match the name of the type. For example, as we
are customizing DateTime template, the name of the template in this case has to be
DateTime.ascx or DateTime.cshtml.
Adding a Custom DateTime Editor template
Step 1: If "Shared" folder does not already exists in your project, right click on the project
in solution explorer and add it.
Step 2: Right click on the "Shared" folder, and "EditorTemplates" folder.
Step 3: Right click on "EditorTemplates" folder and add a view with name = DateTime
Step 4: Copy and paste the following code in DateTime.cshtml partial view
@model DateTime?
@Html.TextBox("", (Model.HasValue ?
Model.Value.ToString("dd/MM/yyyy") :string.Empty), new { @class = "date" })
Note: Please refer to the following MSDN articel for all the DateTime format strings
http://msdn.microsoft.com/en-us/library/8kb3ddd4.aspx
Step 5: Copy and paste the following code in Edit.cshtml view
@model MVCDemo.Models.Employee
@{
ViewBag.Title = "Edit";
}
<h2>Edit</h2>
69
71
72
@Html.Raw() method can also be used to avoid automatic html encoding. Notice that, the
string that is returned by Image() method is passed as the input for Raw() method, which
renders the image as expected.
@Html.Raw(Html.Image(@Model.Photo, @Model.AlternateText))
Avoiding html encoding in ASPX views:
<%: %> syntax will automatically encode html in aspx views. So, the following will encode
and display the html, instead of rendering the image. At the moment, the custom Image()
html helper method is returning string of type system.string. If you make this method
return IHtmlString, then the following code will render the image instead of html encoding
it.
<%: Html.Image(Model.Photo, Model.AlternateText) %>
To avoid automatic html encoding, you can use
1. <%= %>
2. Html.Raw()
3. Strings of type IHtmlString are not encoded
Both the following code blocks will render the image
<%= Html.Image(Model.Photo, Model.AlternateText) %>
OR
<%: Html.Raw(Html.Image(Model.Photo, Model.AlternateText)) %>
Different techniques to avoid automatic html encoding in MVC
In HomeController.cs
public ActionResult Details(int id)
{
SampleDBContext db = new SampleDBContext();
Employee employee = db.Employees.Single(x => x.Id == id);
return View(employee);
}
In Details.cshtml View
@model dynamic
<div class="display-label">
@Html.DisplayName("FullName")
</div>
<div class="display-field">
@Model.FullName
</div>
<div class="display-label">
@Html.DisplayName("Gender")
</div>
<div class="display-field">
@Model.Gender
</div>
With dynamic type also, we don't get intellisense and compile-time error checking.
Using Strongly Typed View: No change is required in the controller action method. Make
the following change to Details.cshtml view. Notice that the view is strongly typed against
Employee model class. We get intellisense and if we mis-spell a property name, we get
to know about it at compile time.
@model MVCDemo.Models.Employee
<div class="display-label">
@Html.DisplayName("FullName")
</div>
<div class="display-field">
@Model.FullName
</div>
<div class="display-label">
@Html.DisplayName("Gender")
</div>
<div class="display-field">
@Model.Gender
</div>
Please Note: We discussed enabling compile time error checking in views in Part 50 of
the MVC tutorial.
Let us understand partial views with an example. We want to display, employee photo
and his details as shown in the image below.
}
To simplify this view, let's encapsulate the HTML and code that produces the employee
table in a partial view.
Right click on the "Shared" folder and add a view. Set
View name = _Employee
View engine = Razor
Create a strongly typed view = Checked
Model class = Employee (MVCDemo.Models)
Scaffold template = Empty
Create as a partial view = Checked
This should add "_Employee.cshtml" partial view to the "Shared" folder.
Please note that, partial views can be added to "Shared" folder or to a specific views
folder. Partial views that are in the "Shared" folder are available for all the views in the
entire project, where as partial views in a specific folder are available only for the views
with-in that folder.
Copy and paste the following code in "_Employee.cshtml" partial view
@model MVCDemo.Models.Employee
<table style="font-family:Arial; border:1px solid black; width: 300px">
<tr>
<td>
<img src="@Url.Content(Model.Photo)" alt="@Model.AlternateText" />
</td>
<td>
<table>
<tr>
<td><b>Age:</b></td>
<td>@Model.Age</td>
</tr>
<tr>
<td><b>Gender:</b></td>
<td>@Model.Gender</td>
</tr>
<tr>
<td><b>Salary:</b></td>
<td>@Model.Salary</td>
</tr>
</table>
</td>
</tr>
</table>
Now, make the following changes to Index.cshtml view. Notice that the view is much
simplified now. To render the partial view, we are using Partial() html helper method. There
are several overloaded versions of this method. We are using a version that expects 2
parameters, i.e the name of the partial view and the model object.
@model IEnumerable<MVCDemo.Models.Employee>
@foreach (var item in Model)
{
@Html.Partial("_Employee", item)
}
79
80
By deleting this from CustomTool property, we are telling visual studio not to run them
during the build. They will be manually called when we add a view or a controller
using"Add View" and "Add Controller" dialog box.
Is it possible to add your own T4 template to the existing list of templates?
Absolutely, simply create a file with ".tt" file extension in "AddController" folder
in"CodeTemplates". If it is for adding a view, then put it in "AspxCSharp"(if view engine
is aspx) or "CSHTML"(if view engine is razor) folder.
Click "Add". This generates the HomeController and the required views.
In Create.cshtml view, change
@Html.EditorFor(model => model.Comments)
TO
@Html.TextAreaFor(model => model.Comments)
The above change is to display a multi-line textbox for entering comments.
Now click "Create". Data gets saved as expected, and the user is redirected to Index
action. On the "Index" view, instead of rendering the word "very good" with an under-line
and in bold, the encoded html is displayed as shown below.
By default asp.net mvc encodes all html. This is another security measure in place, to
prevent XSS attack. To disable html encoding, make the fllowing change on Index.cshtml
view.
CHANGE
@Html.DisplayFor(modelItem => item.Comments)
To
@Html.Raw(item.Comments)
Navigate to Index view, and notice that, the word "Very Good" is rendered with an
underline and in bold.
The following 2 changes that we have done has opened the doors for XSS.
1. By allowing HTML to be submitted on Create.cshtml view
2. By disabling HTML encoding on Index.cshtml
To initiate a cross site scripting attack
1. Navigate to "Create" view
2. Type the following javascript code in "Comments" textbox
<script type="text/javascript">
alert('Your site is hacked');
</script>
3. Click "Create"
Refresh Index view and notice that javascript alert() box is displayed, stating that the site is
hacked. By injecting script, it is also very easy to steal session cookies. These stolen
session cookies can then be used to log into the site and do destructive things.
So, in short, by allowing HTML to be submitted and disabling HTML encoding we are
opening doors for XSS attack.
In our next video, we will discuss preventing XSS while allowing only the HTML that we
want to accept.
83
Output:
1 2 3 4 5 6 7 8 9 10
Use @{ } to define a code block. If we want to define some variables and perform
calculations, then use code block. The following code block defines 2 variables and
computes the sum of first 10 even and odd numbers.
@{
int SumOfEvenNumbers = 0;
int SumOfOddNumbers = 0;
for(int i =1; i<=10; i++)
{
if(i %2 == 0)
{
SumOfEvenNumbers = SumOfEvenNumbers + i;
}
else
{
SumOfOddNumbers = SumOfOddNumbers + i;
}
}
}
<h3>Sum of Even Numbers = @SumOfEvenNumbers</h3>
<h3>Sum of Odd Numbers = @SumOfOddNumbers</h3>
Output:
Sum of Even Numbers = 30
Sum of Odd Numbers = 25
Use <text> element or @: to switch between c# code and literal text
@for (int i = 1; i <= 10; i++)
{
<b>@i</b>
if (i % 2 == 0)
{
<text> - Even </text>
}
else
{
<text> - Odd </text>
}
<br />
}
The above program can be re-written using @: as shown below.
@for (int i = 1; i <= 10; i++)
{
<b>@i</b>
if (i % 2 == 0)
{
@: - Even
}
else
{
@: - Odd
}
<br />
}
Output:
85
1 - Odd
2 - Even
3 - Odd
4 - Even
5 - Odd
6 - Even
7 - Odd
8 - Even
9 - Odd
10 - Even
@ symbol is used as code delimiter in razor views. However, razor is smart enough to
recognize the format of internet email address and not to treat the @ symbol as a code
delimiter.
This is my email address<br />
<b>kudvenkat@gmail.com</b>
Use @ symbol to escape @
I will meet you @@ office
86
Output:
I will meet you @ office
Rather than having all of these sections, in each and every view, we can define them in a
layout view and then inherit that look and feel in all the views. With layout views,
maintaining the consistent look and feel across all the views becomes much easier, as we
have only one layout file to modify, should there be any change. The change will then be
immediately reflected across all the views in entire application.
Let us now create a layout view with
1. Header
2. Footer
3. Navigation menu and
4. a place to plugin view specific content
Step 1: Create an empty asp.net mvc4 application.
Step 2: Right click on "Views" folder and add "Shared" folder.
Step 3: Right click on "Shared" folder and add "_Layout.cshtml" view. This is our layout
view, where we will define the site wide look and feel. The layout file can have any name,
and will have .cshtml file extension. Copy and paste the following html
<html>
<head>
<title>@ViewBag.Title</title>
@*All the javascript and css files that are required by the
application can be referenced here so that there is no
need to reference them in each and every view*@
</head>
<body>
<table border="1" style="width:800px; font-family:Arial">
<tr>
<td colspan="2" style="text-align:center">
87
<h3>Website Header</h3>
</td>
</tr>
<tr>
<td style="width:200px">
<h3>Menu</h3>
</td>
<td style="width:600px">
@RenderBody()
</td>
</tr>
<tr>
<td colspan="2" style="text-align:center; font-size:x-small">
<h3>Website Footer</h3>
</td>
</tr>
</table>
</body>
</html>
Points to note:
1. View specific title is retrieved using @ViewBag.Title.
2. View specific content will be plugged-in at the location, where RenderBody() function is
called.
Step 4: Let us use the following table tblEmployee, and generate a few views that can be
used with our layout view.
Create table tblEmployee
(
Id int identity primary key,
FirstName nvarchar(50),
LastName nvarchar(50),
Salary int
)
Insert into tblEmployee values('Tom', 'S', 5000)
Insert into tblEmployee values('Mary', 'P', 8000)
Insert into tblEmployee values('Ben', 'K', 3000)
Step 5: Add ADO.NET entity data model based on the above table. Build the solution, so
that Employee model class is compiled.
Step 6: Add a HomeController, with the following settings, so that Index, Details, Create,
Edit and Delete views are auto-generated.
1. Controller name - HomeController
2. Template - MVC controller with read/write actions and views, using Entity Framework
3. Model class - Employee
4. Data context class - SampleDBContext
5. Views - Razor
Step 7: Now, we need to make modifications to Index.cshtml view, to use _Layout.cshtml
layout view. Copy and paste the following code just below, @model declaration. Notice that,
we are storing title in ViewBag object. The layout view is going to retrieve it from viewbag
and use it as the title. The next statement, specifies the layout file to use.
@{
ViewBag.Title = "Employee List Page";
Layout = "~/Views/Shared/_Layout.cshtml";
}
88
At this point navigate to Index view and notice that, it uses the layout file that we have
defined.
Step 8: Make the following modifications to layout view. Replace
1. Website Header with Employee Portal
2. <h3>Website Footer</h3> with 2013 Pragim Technologes
3. <h3>Menu</h3> with @Html.ActionLink("Employee List", "Index")
Save changes and navigate to Index view. Now click on "Edit" link. The page crashes. To fix
it, delete "Scripts" section that is at the bottom of the Edit view. Refresh the page. Notice
that, we don't have error now, but this view is not using the layout view. To fix it, we need
to include the following code, as we did on index view.
@{
ViewBag.Title = "Employee Edit Page";
Layout = "~/Views/Shared/_Layout.cshtml";
}
In our next video, we will discuss - How to specify layout view setting for all the
views at one place
If you want a set of views in a specific folder, to use a different layout file, then you can
include another _ViewStart.cshtml file in that specific folder.
When I use _ViewStart.cshtml file, can I still set Layout property on individual
views?
Yes, if you want to use a layout file that is different from what is specified in
_ViewStart.cshtml
Where else can I specify a layout file?
89
Layout file can also be specified in a controller action method or in an action filter.
In Controller Action Method:
Specify which layout to use when returning a view inside a controller action
public ActionResult Create()
{
return View("Create", "_Layout");
}
We will discuss action filters in a later video session.
Can we write some logic in "_ViewStart.cshtml" to dynamically specify which
layout file to use?
Yes, the following code will change the layout file to use based on the browser type.
If the browser is google chrome,
then "_Layout.cshtml" layout file is used
Else
"_DifferentLayout.cshtml" layout file is used
Code in "_ViewStart.cshtml" file
@{
Layout =
Request.Browser.IsBrowser("Chrome") ? "~/Views/Shared/_Layout.cshtml" :"~/Views/Shared
/_DifferentLayout.cshtml" ;
}
All partial views in my application are now using the layout file specified in
"_ViewStart.cshtml". How do I prevent these partial views from using a layout
file?
Details action method below, returns "_Employee" partial view, and is using the layout file
specified in "_ViewStart.cshtml"
public ActionResult Details(int id)
{
Employee employee = db.Employees.Single(e => e.Id == id);
return View("_Employee", employee);
}
To prevent this partial view from using the layout file, specified in "_ViewStart.cshtml",
return "PartialViewResult" from the controller action method as shown below.
public PartialViewResult Details(int id)
{
Employee employee = db.Employees.Single(e => e.Id == id);
return PartialView("_Employee", employee);
}
What will be the layout file extension, if VB.NET is my programming language?
.vbhtml
In our next video, we will discuss using named sections in a layout file.
90
Let us say we want to change the navigation menu dynamically. For example, if I am on the
Edit view, then I want the navigation menu to contain links for List, Details and
Delete views as shown below.
Here are the steps to achieve this using sections in layout file
Step 1: Define "Menu" section in Edit view. To define a section, use @section followed by,
the name of the section. The menu section, is going to display List, Details and Delete links.
@section Menu
{
@Html.ActionLink("List", "Index") <br />
@Html.ActionLink("Details", "Details", new { id = Model.Id }) <br />
@Html.ActionLink("Delete", "Delete", new { id = Model.Id })
}
91
Step 2: Specify a location in layout file, where we want the "Menu" section to be rendered.
The above code that is marked in red, is very simple to understand. If you navigate to a
view, and if there is a "Menu" section defined in that view, then that content will be
injected, else, the default content that is specified in the layout file is used.
For example, Navigate to Edit view. Since "Edit" view has got "Menu" section defined, the
content from that section (i.e List, Details and Delete links ) will be displayed.
Now navigate to "Delete" view. "Menu" section is not defined in this view, so default
content from the layout file (i.e Index action link) will be displayed.
We will be using table tblEmployee for this demo. Use the script below to create and
populate the table with sample data.
92
into
into
into
into
tblEmployee
tblEmployee
tblEmployee
tblEmployee
94
@using PagedList;
b) The model for the view should be IPagedList<Employee>.
@model IPagedList<MVCDemo.Models.Employee>
c) Since, we have changed the model of the view,
fromIEnumerable<MVCDemo.Models.Employee>
toIPagedList<MVCDemo.Models.Employee>, change the section that displays table
headings as shown below.
<tr>
<th>
@Html.DisplayNameFor(model => model.First().Name)
</th>
<th>
@Html.DisplayNameFor(model => model.First().Gender)
</th>
<th>
@Html.DisplayNameFor(model => model.First().Email)
</th>
<th>Action</th>
</tr>
d) Finally to display page numbers for paging
@Html.PagedListPager(Model, page => Url.Action("Index", new { page, searchBy =
Request.QueryString["searchBy"], search = Request.QueryString["search"] }))
e) If you want to display the pager, only if there are more than 1 page
@Html.PagedListPager(Model, page => Url.Action("Index", new { page, searchBy =
Request.QueryString["searchBy"], search =
Request.QueryString["search"] }), newPagedListRenderOptions() { Display
= PagedListDisplayMode.IfNeeded })
f) If you want to display, the current active page and the total number of pages
@Html.PagedListPager(Model, page => Url.Action("Index", new { page, searchBy =
Request.QueryString["searchBy"], search =
Request.QueryString["search"] }), newPagedListRenderOptions() { Display
= PagedListDisplayMode.IfNeeded, DisplayPageCountAndCurrentLocation = true })
g) If you want to display the number of rows displayed, of the total number of
rows available.
@Html.PagedListPager(Model, page => Url.Action("Index", new { page, searchBy =
Request.QueryString["searchBy"], search =
Request.QueryString["search"] }), newPagedListRenderOptions() { Display
= PagedListDisplayMode.IfNeeded, DisplayItemSliceAndTotal = true })
96
By the end of this video, the output should be as shown below. Notice
that "Name" and"Gender" columns are rendered as hyperlinks, which the user can click to
sort data.
@{
ViewBag.Title = "Index";
}
<link href="~/Content/PagedList.css" rel="stylesheet" type="text/css" />
<div style="font-family:Arial">
<h2>Employee List</h2>
<p>
@using (@Html.BeginForm("Index", "Home", FormMethod.Get))
{
<b>Search By:</b>
@Html.RadioButton("searchBy", "Name", true) <text>Name</text>
@Html.RadioButton("searchBy", "Gender") <text>Gender</text><br />
@Html.TextBox("search") <input type="submit" value="search" />
}
</p>
<table border="1">
<tr>
<th>
@Html.ActionLink("Name", "Index", new { sortBy = ViewBag.NameSort, searchBy =
Request["searchBy"], search = Request["search"] })
</th>
<th>
@Html.ActionLink("Gender", "Index", new { sortBy = ViewBag.GenderSort,
searchBy = Request["searchBy"], search = Request["search"] })
</th>
<th>
@Html.DisplayNameFor(model => model.First().Email)
</th>
<th>Action</th>
</tr>
@if (Model.Count() == 0)
{
<tr>
<td colspan="4">
No records match search criteria
</td>
</tr>
}
else
{
foreach (var item in Model)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Name)
</td>
<td>
@Html.DisplayFor(modelItem => item.Gender)
</td>
<td>
@Html.DisplayFor(modelItem => item.Email)
</td>
<td>
@Html.ActionLink("Edit", "Edit", new { id = item.ID }) |
@Html.ActionLink("Details", "Details", new { id = item.ID }) |
@Html.ActionLink("Delete", "Delete", new { id = item.ID })
</td>
</tr>
}
}
98
</table>
@Html.PagedListPager(Model, page => Url.Action("Index", new { page, searchBy =
Request.QueryString["searchBy"], search = Request.QueryString["search"], sortBy =
Request["sortBy"] }), new PagedListRenderOptions() { Display
=PagedListDisplayMode.IfNeeded })
</div>
<td>
@Model.Email
</td>
</tr>
Step 6: Copy and paste the following code in HomeController.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using MVCDemo.Models;
namespace MVCTest.Controllers
{
public class HomeController : Controller
{
private SampleDBContext db = new SampleDBContext();
public ActionResult Index()
{
return View(db.Employees.ToList());
}
[HttpPost]
public ActionResult Delete(IEnumerable<int> employeeIdsToDelete)
{
db.Employees.Where(x =>
employeeIdsToDelete.Contains(x.ID)).ToList().ForEach(db.Employees.DeleteObject);
db.SaveChanges();
return RedirectToAction("Index");
}
}
}
Setp 7: Right click on the Index() action and add "Index" view with the following settings.
View name = Index
View engine = Razor
Create a strongly-typed view = checked
Model class = Employee (MVCDemo.Models)
Scaffold Template = Empty
and finally click "Add"
Setp 8: Copy and paste the following code in Index.cshtml view
@model IEnumerable<MVCDemo.Models.Employee>
<div style="font-family:Arial">
<h2>Employee List</h2>
@using (Html.BeginForm("Delete", "Home", FormMethod.Post))
{
<table border="1">
<thead>
<tr>
<th>
Select
</th>
<th>
Name
</th>
<th>
100
Gender
</th>
<th>
Email
</th>
</tr>
</thead>
<tbody>
@Html.EditorForModel()
</tbody>
</table>
<input type="submit" value="Delete selected employees" />
}
</div>
In our next video, we will discuss providing a "SELECT ALL" checkbox to select and deselect all rows.
}
If you want to invoke Index() action method, with the following URL
/Home/List
Then decorate the action method with ActionName attribute as shown below.
public class HomeController : Controller
{
[ActionName("List")]
public string Index()
{
return "Index action method invoked";
}
}
Now, if you navigate to /Home/Index, you will get an error - The resource cannot be
found.
At the moment, the Index() action method is returning a string, but if it returns a view,
should the view be named - Index or List.?
[ActionName("List")]
public ActionResult Index()
{
return View();
}
List should be the view name. If for some reason, you want to use "Index" as the view
name, then modify the controller action method as shown below.
[ActionName("List")]
public ActionResult Index()
{
return View("Index");
}
AcceptVerbs selector: Use this selector, when you want to control, the invocation of an
action method based on the request type. In the example below, the "Edit" method that is
decorated with GET acceptverb responds to the GET request, where as the other "Edit"
method responds to POST request. The default is GET. So, if you don't decorate an action
method with any accept verb, then, by default, the method responds to GET request.
public class HomeController : Controller
{
[AcceptVerbs(HttpVerbs.Get)]
public ActionResult Edit(int id)
{
Employee employee = GetEmployeeFromDB(id);
return View(employee);
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Save(Employee employee)
{
if (ModelState.IsValid)
{
// Save employee to the database
return RedirectToAction("Index");
}
return View(employee);
}
}
HttpGet and HttpPost attributes can be used as shown below. This is an alternative to
103
Now, if you naviage to URL /Home/Method2, you will get an error - The resource cannot
be found.
Another way to restrict access to methods in a controller, is by making them
private.
private string Method2()
{
return "<h1>Method 2 Invoked</h1>";
}
In general, it's a bad design to have a public method in a controller that is not an action
method. If you have any such method for performing business calculations, it should be
somewhere in the model and not in the controller.
However, if for some reason, if you want to have public methods in a controller and you
don't want to treat them as actions, then use NonAction attribute.
{
return View();
}
}
3. Right click on NonSecureMethod() and add a view with name = NonSecureMethod.
Similarly add a view with name = SecureMethod.
4. Associate MVCDemo project with IIS.
a) Right click on the project name in "solution explorer" and select "Properties"
b) Click on "Web" tab
c) Select "Use Local IIS Web Server". In the "Project Url" textbox, type http://localhost/MVCDemo
d) Click "Create Virtual Directory" button
5. Open IIS. Expand "Sites" and then "Default Web Site" and select "MVCDemo".
Double click on "Authentication" icon. Enable "Anonymous
Authentication" and"Windows Authentication", if they are not already enabled.
6. At this point, you will be able to access,
both "SecureMethod" and"NonSecureMethod", by visiting the following URLs.
http://localhost/MVCDemo/Home/SecureMethod
http://localhost/MVCDemo/Home/NonSecureMethod
7. If you want "SecureMethod" to be available only for authenticated users, then
decorate it with "Authorize" attribute.
[Authorize]
public ActionResult SecureMethod()
{
return View();
}
8. Now, if you navigate to "http://localhost/MVCDemo/Home/SecureMethod", then
you will be prompted for your windows credentials. If you don't provide valid windows
credentials or if you click cancel, you will get an error - 401 - Unauthorized: Access is
denied due to invalid credentials. You do not have permission to view this directory or page
using the credentials that you supplied. You should be able to access "NonSecureMethod"
9. Now remove the [Authorize] attribute from SecureMethod(), and apply it on the
HomeController.
[Authorize]
public class HomeController : Controller
{
public ActionResult NonSecureMethod()
{
return View();
}
public ActionResult SecureMethod()
{
return View();
}
}
At this point, "Authorize" attribute is applicable for all action methods in the
HomeController. So, only authenticated users will be able to access SecureMethod() and
NonSecureMethod().
10. To allow anonymous access to NonSecureMethod(), apply [AllowAnonymous] attribute.
AllowAnonymous attribute is used to skip authorization enforced by Authorize attribute.
[AllowAnonymous]
106
1. Any action method that is decorated with [ChildActionOnly] attribute is a child action
method.
2. Child action methods will not respond to URL requests. If an attempt is made, a runtime
error will be thrown stating - Child action is accessible only by a child request.
3. Child action methods can be invoked by making child request from a view
using"Action()" and "RenderAction()" html helpers.
4. An action method doesnt need to have [ChildActionOnly] attribute to be used as a child
action, but use this attribute to prevent if you want to prevent the action method from
being invoked as a result of a user request.
5. Child actions are typically associated with partial views, although this is not compulsory.
6. Child action methods are different from NonAction methods, in that NonAction methods
cannot be invoked using Action() or RenderAction() helpers. We discussed NonAction
methods in Part 70 of ASP.NET MVC tutorial series.
7. Using child action methods, it is possible to cache portions of a view. This is the main
advantage of child action methods. We will cover this when we discuss [OutputCache]
attribute.
Now, let us understand replacing this yellow screen of death, with a friendly error page.
108
Step 3: Enable custom errors in web.config file, that is present in the root directory of your
mvc application. "customErrors" element must be nested under "<system.web>". For
detailed explanation on MODE attribute, please watch Part 71 of ASP.NET Tutorial.
<customErrors mode="On">
</customErrors>
Step 4: Add "Shared" folder under "Views" folder. Add Error.cshtml view inside this
folder. Paste the following HTML in Error.cdhtml view.
<h2>An unknown problem has occured, please contact Admin</h2>
Run the application, and notice that, you are redirected to the friendly "Error" view,
instead of the generic "Yellow screen of death".
We did not apply HandleError attribute anywhere. So how did all this work?
HandleErrorAttribute is added to the GlobalFilters collection in global.asax. When a filter
is added to the GlobalFilters collection, then it is applicable for all controllers and their
action methods in the entire application.
Right click on "RegisterGlobalFilters()" method in Global.asax, and select "Go To
Definition" and you can find the code that adds "HandleErrorAttribute" to
GlobalFilterCollection.
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
}
Is the friendly error page displayed for HTTP status code 404?
No, but there is a way to display the friendly error page.
In the HomeController, we do not have List() action method. So, if a user navigates
to/Home/List, we get an error - The resource cannot be found. HTTP 404.
To display a friendly error page in this case
Step 1: Add "ErrorController" to controllers folder. Copy and paste the following code.
public class ErrorController : Controller
{
public ActionResult NotFound()
{
return View();
}
}
Step 2: Right click on "Shared" folder and add "NotFound.cshtml" view. Copy and paste
the following code.
<h2>Please check the URL. The page you are looking for cannot be found</h2>
Step 3: Change "customErrors" element in web.config as shown below.
<customErrors mode="On">
<error statusCode="404" redirect="~/Error/NotFound"/>
</customErrors>
109
</td>
<td>
@Html.DisplayFor(modelItem => item.Gender)
</td>
<td>
@Html.DisplayFor(modelItem => item.Email)
</td>
<td>
@Html.ActionLink("Edit", "Edit", new { id=item.ID }) |
@Html.ActionLink("Details", "Details", new { id=item.ID }) |
@Html.ActionLink("Delete", "Delete", new { id=item.ID })
</td>
</tr>
}
</table>
</div>
When you navigate to /Home/Index, the view ouput is cached for 10 seconds. If you
refresh the view, within 10 seconds you will get a cached response. After 10 seconds, the
cache expires, code is executed again and the output is cached for another 10 seconds.
Caching specific portion of a view using ChildActionOnly attribute:
Step 1: Remove OutputCache attribute and the line which calls Thread.Sleep(), from
the Index() action method in HomeController. After the changes, the Index() action
method should be as shown below.
public ActionResult Index()
{
return View(db.Employees.ToList());
}
Step 2: Add GetEmployeeCount() action method to HomeController. Notice that, this
method is decorated with OutputCache and ChildActionOnly attributes.
// Child actions can be used to implement partial caching,
// although not necessary. In this case, even if the ChildActionOnly
// attribue is removed, a portion of the view will be cached as expected
[ChildActionOnly]
[OutputCache(Duration = 10)]
public string GetEmployeeCount()
{
return "Employee Count = " + db.Employees.Count().ToString() + "@
" +DateTime.Now.ToString();
}
Step 3: Copy and paste the following code, just below the closing table tag in Index.cshtml
view.
<br /><br />
<b> @Html.Action("GetEmployeeCount") </b>
Navigate to /Home/Index. Notice that, everytime you refresh the page, the time in the
section of the page that displays employee list changes, but not the time, that displays the
employee count. This proves that, only a portion of the view, is cached.
111
There colud be several ways to make cache profiles work with child action methods. The
following is the approach, that I am aware of. Please feel free to leave a comment, if you
know of a better way of doing this.
Create a custom OutputCache attribute, that loads the cache settings from the cache
profile in web.config.
Step 1: Right click on the project name in solution explorer, and add a folder with name =
Common
Setp 2: Right click on "Common" folder and add a class file, with name =
PartialCacheAttribute.cs
Step 3: Copy and paste the following code. Notice that, I have named the
customOutputCache attribute as PartialCacheAttribute.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Configuration;
namespace MVCDemo.Common
{
public class PartialCacheAttribute : OutputCacheAttribute
{
public PartialCacheAttribute(string cacheProfileName)
{
OutputCacheSettingsSection cacheSettings =
(OutputCacheSettingsSection)WebConfigurationManager.GetSection("system.web/caching/
outputCacheSettings");
OutputCacheProfile cacheProfile =
cacheSettings.OutputCacheProfiles[cacheProfileName];
Duration = cacheProfile.Duration;
VaryByParam = cacheProfile.VaryByParam;
VaryByCustom = cacheProfile.VaryByCustom;
}
}
}
Step 4: Use PartialCacheAttribute on the child action method, and pass it the name of the
cache profile in web.config. Please note that, PartialCacheAttribute is in MVCDemo.Common
namespace.
[ChildActionOnly]
[PartialCache("1MinuteCache")]
public string GetEmployeeCount()
{
return "Employee Count = " + db.Employees.Count().ToString()
+ "@ " + DateTime.Now.ToString();
}
HomeController.
[RequireHttps]
public string Login()
{
return "This method should be accessed only using HTTPS protocol";
}
Step 3: Try to navigate to http://localhost/MVCDemo/Home/Login. Notice that you are
automatically redirected to https://localhost/MVCDemo/Home/Login. So, [RequireHttps]
attribute, forces an HTTP request to be re-sent over HTTPS.
For detailed explanation of these attributes, please refer to the following MSDN link
http://msdn.microsoft.com/en-us/library/gg416513(v=vs.98).aspx
Step 1: Create an asp.net mvc 4 application using "Empty" template
Step 2: Right click on the project name in solution explorer and add "Data" folder. Add a
text file to this folder and name it Data.txt
Step 3: Right click on the project name in solution explorer and add "Common" folder. Add
a class file to this folder and name it "TrackExecutionTime.cs". Copy and paste the following
code. Notice that our custom filter "TrackExecutionTime" inherits from ActionFilterAttribute
and IExceptionFilter. ActionFilterAttribute class implements IActionFilter and IResultFilter.
using
using
using
using
using
using
System;
System.Collections.Generic;
System.Linq;
System.Web;
System.Web.Mvc;
System.IO;
namespace MVCDemo.Common
{
public class TrackExecutionTime : ActionFilterAttribute, IExceptionFilter
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
string message = "\n" +
filterContext.ActionDescriptor.ControllerDescriptor.ControllerName +
" -> " + filterContext.ActionDescriptor.ActionName + " -> OnActionExecuting \t"+
DateTime.Now.ToString() + "\n";
LogExecutionTime(message);
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
string message = "\n" +
filterContext.ActionDescriptor.ControllerDescriptor.ControllerName +
" -> " + filterContext.ActionDescriptor.ActionName + " -> OnActionExecuted \t"+
DateTime.Now.ToString() + "\n";
LogExecutionTime(message);
}
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
string message = filterContext.RouteData.Values["controller"].ToString() +
" -> " + filterContext.RouteData.Values["action"].ToString() +
" -> OnResultExecuting \t- " + DateTime.Now.ToString() + "\n";
LogExecutionTime(message);
}
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
string message = filterContext.RouteData.Values["controller"].ToString() +
" -> " + filterContext.RouteData.Values["action"].ToString() +
" -> OnResultExecuted \t- " + DateTime.Now.ToString() + "\n";
LogExecutionTime(message);
LogExecutionTime("---------------------------------------------------------\n");
}
116
117
I have used a tool called ILSpy, to list all the sub-types of ActionResult. To use the tool
yourself, here are the steps
1. Navigate to http://ilspy.net
2. Click on "Download Binaries" button, and extract them to a folder.
3. Run ILSpy.exe which can be found in the folder, where you have extracted the binaries.
4. Click on File - Open From GAC
5. Type "System.Web.Mvc" in the search box. Select the Assembly and click Open
6. At this point System.Web.Mvc assmbly should be loaded into ILSpy. Expand
System.Web.Mvc, then expand ActionResult and then expand "Derived Types". You should
now be able to see all the derived types.
Why do we have so many sub-types?
An action method in a controller, can return a wide range of objects. For example, an action
method can return
1. ViewResult
2. PartialViewResult
3. JsonResult
4. RedirectResult etc..
What should be the return type of an action method - ActionResult or specific
derived type?
It's a good practise to return specific sub-types, but, if different paths of the action method
returns different subtypes, then I would return an ActionResult object. An example is shown
below.
public ActionResult Index()
{
if (Your_Condition)
return View();
// returns ViewResult object
else
return Json("Data"); // returns JsonResult object
}
The following table lists
1. Action Result Sub Types
2. The purpose of each sub-type
118
Employer Area - This functional area allows a job provider to create employer profile,
upload jobs, and search for suitable candidates.
Administrator Area - This functional area allows an administrator to configure the site
and mantain.
To create an area in an MVC application
1. Right click on the project name in Solution Explorer and select Add => Area
2. Provide a meaningful name. For example "Employee" and click Add
At this point, "Areas" folder will be created, and with in this, a folder for Employee area will
be added. You can add as many areas as you want.
In a similar fashion, add areas for Employer and Admin. At this point, your solution explorer
should look as shown below. Notice the Areas folder.
Notice that in each area folder (Employee, Employer and Admin), you have a set of Models,
Views and Controllers folders. Also, there is "AreaRegistration.cs" file, which contains the
code to register a route for the area.
Now navigate to Global.asax.cs file and notice Application_Start(). With in this method
there is code to register all areas in your application.
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
120
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
}
At this point Add "HomeController" to the following areas. Notice that we can have a
HomeController(Controller with the same) in Employee, Employer, Admin and MainArea.
1. Employee
2. Employer
3. Admin
4. Main Area
At this point, we have Index() action method in all of the HomeControllers.
public ActionResult Index()
{
return View();
}
Now Add Index view to all the areas. Copy and paste the following HTML in respective Index
views.
Main Area: <h1>Main Area Index View</h1>
Employee Area: <h1>Employee Area Index View</h1>
Employer Area: <h1>Employer Area Index View</h1>
Admin Area: <h1>Admin Area Index View</h1>
At this point, build the application, and navigate to http://localhost/MVCDemo. You will get
an error
Multiple types were found that match the controller named 'Home'. This can happen if the
route that services this request ('{controller}/{action}/{id}') does not specify namespaces
to search for a controller that matches the request. If this is the case, register this route by
calling an overload of the 'MapRoute' method that takes a 'namespaces' parameter.
The request for 'Home' has found the following matching controllers:
MVCDemo.Controllers.HomeController
MVCDemo.Areas.Admin.Controllers.HomeController
MVCDemo.Areas.Employee.Controllers.HomeController
MVCDemo.Areas.Employer.Controllers.HomeController
To fix this change RegisterRoutes() method in RouteConfig.cs file in App_start folder.
Notice that we are passing the namespace of the HomeController in the Main area using
namespace parameter.
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
namespaces: new [] { "MVCDemo.Controllers" }
);
}
Now, if you navigate to http://localhost/MVCDemo/Employee, you will get an error - The
resource cannot be found.
To fix this, change RegisterArea() area method in EmployeeAreaRegistration.cs file in
Employee folder as shown below. Notice that we are setting HomeController as the
default controller
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
121
"Employee_default",
"Employee/{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
Navigating to http://localhost/MVCDemo/Employee may throw a compilation error related to
System.Web.Optimization. If you get this error follow the steps below.
1. In Visual Studio, click Tools - Library PAckage Manager - Package Manager Console
2. In the Package Manager Console window, type the following command and press enter
Install-Package Microsoft.Web.Optimization -Pre
When we are building links using ActionLink() html helper to navigate between areas, we
need to specify area name as shown below. Copy and paste the following code in all the
Index views in all the areas and you will be able to navigate between areas when you click
on the respective links.
<table border="1">
<tr>
<td>
<b>Links</b>
</td>
</tr>
<tr>
<td>
@Html.ActionLink("Main Area Home Page", "Index", "Home", new { area = "" },null)
</td>
</tr>
<tr>
<td>
@Html.ActionLink("Employee Area Home Page", "Index", "Home", new { area
="Employee" }, null)
</td>
</tr>
<tr>
<td>
@Html.ActionLink("Employer Area Home Page", "Index", "Home", new { area
="Employer" }, null)
</td>
</tr>
<tr>
<td>
@Html.ActionLink("Admin Area Home Page", "Index", "Home", new { area
="Admin" }, null)
</td>
</tr>
</table>
Gender nvarchar(50)
)
Insert into tblEmployee
Insert into tblEmployee
Insert into tblEmployee
Insert into tblEmployee
Generate ADO.NET entity data model for table tblEmployee. Change the entity name
from tblEmployee to Employee. Save and build the project.
Right click on the "Controllers" folder and select Add - Controller. Set
Name = HomeController
Template = MVC controller with read/write actions and views, using Entity Framework
Model class = Employee(MVCDemo.Models)
Data Context Class = EmployeeContext(MVCDemo.Models)
Views = Razor
To validate data, use validation attributes that are found in
System.ComponentModel.DataAnnotations namespace. It is not a good idea, to add these
validation attributes to the properties of auto-generated "Employee" class, as our changes
will be lost, if the class is auto-generated again.
So, let's create another partial "Employee" class, and decorate that class with the
validation attributes. Right click on the "Models" folder and add Employee.cs class file.
Copy and paste the following code.
using System.ComponentModel.DataAnnotations;
namespace MVCDemo.Models
{
[MetadataType(typeof(EmployeeMetaData))]
public partial class Employee
{
}
public class EmployeeMetaData
{
[StringLength(10, MinimumLength = 5)]
[Required]
public string Name { get; set; }
}
}
Notice that, we have decorated "Name" property with "StringLength" attribute and
specified Minimum and Maximum length properties. We also used [Required] attribute.
So, at this point Name property is required and should be between 5 and 10 characters.
Points to remember:
1. [StringLength] attribute is present in System.ComponentModel.DataAnnotations
namespace.
2. [StringLength] attribute verifies that a string is of certain length, but does not enforce
that the property is REQUIRED. If you want to enforce that the property is required
use[Required] attribute.
We will discuss the following attributes in our upcoming video sessions.
RegularExpression
Range
123
view.
public class EmployeeMetaData
{
[StringLength(10, MinimumLength = 5)]
[Required]
public string Name { get; set; }
[Range(1, 100)]
public int Age { get; set; }
[Range(typeof(DateTime), "01/01/2000", "01/01/2010")]
[DisplayFormat(DataFormatString = "{0:d}", ApplyFormatInEditMode = true)]
public DateTime HireDate { get; set; }
}
At this point, we should not be able to enter any values outside the range
of "01/01/2000"and "01/01/2010" for HireDate field.
However, when the Range attribute is used with DateTime fields, the client side validation
does not work as expected. We will discuss this in a later video session.
}
}
}
4. Finally decorate "HireDate" property with our custom DateRangeAttribute as shown
below. Notice that, we are only passing the minimum date value. Maximum date value will
be today's date. Please note, DateRangeAttribute is present in MVCDemo.Common
namespace.
[DateRange("01/01/2000")]
[DisplayFormat(DataFormatString = "{0:d}", ApplyFormatInEditMode = true)]
public DateTime HireDate { get; set; }
Let's now look at another example of creating a custom validation attribute. Let's
say our business rules have changed, and the HireDate property should allow any valid
date that is <= Today's Date. This means, there is no minimum value restriction and the
maximum value should be less than or equal to Today's date. To achieve this, let's add
another custom validation attribute. Here are the steps
1. Right click on the "Common" folder and add a class file with name =
CurrentDateAttribute.cs
2. Copy and paste the following code in CurrentDateAttribute.cs class file.
using System;
using System.ComponentModel.DataAnnotations;
namespace MVCDemo.Common
{
public class CurrentDateAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
DateTime dateTime = Convert.ToDateTime(value);
return dateTime <= DateTime.Now;
}
}
}
3. Decorate "HireDate" property with our custom CurrentDateAttribute as shown below.
[CurrentDate]
[DisplayFormat(DataFormatString = "{0:d}", ApplyFormatInEditMode = true)]
public DateTime HireDate { get; set; }
Please note that the validation error message can be customised using named
parameter "ErrorMessage" as shown below.
[CurrentDate(ErrorMessage = "Hire Date must be less than or equal to Today's Date")]
[DisplayFormat(DataFormatString = "{0:d}", ApplyFormatInEditMode = true)]
public DateTime HireDate { get; set; }
Notice that, we are passing regular expression string to the attribute constructor. Regular
expressions are great for pattern matching and ensures that, the value for name property is
in the format that we want. Also, notice that we are using a verbatim literal(@ symbol)
string, as we don't want escape sequences to be processed. We discussed verbatim literal
in Part 4 of C# tutorial.
Understanding and writing regular expressions is beyond the scope of this video. If you are
interested in learning to write regular expressions, here is a link from MSDN
http://msdn.microsoft.com/en-us/library/az24scfc.aspx
The following website is very helpful, for writing and testing regular expressions. This
website also contains commonly used regular expressions. Infact, I have picked up the
regular expression for validating Name property from here.
http://gskinner.com/RegExr/
In this video, we will discuss displaying all validation errors at one place using
validation summary html helper. A red asterisk (start) should be displayed next to
every field that has failed the validation. The output should be as shown below.
We will be working with the example that we discussed in Part 85. Please watch Part 85,
before proceeding.
First of all, the validation error messages should be displayed in red color. On the "Edit"
view, include a reference to Site.css style sheet.
<link href="~/Content/Site.css" rel="stylesheet" type="text/css" />
In site.css there are 3 CSS classes that control the display of validation error messages.
Adjust the styles of these classes according to your needs.
field-validation-error
input.input-validation-error
validation-summary-errors
To display all errors at one place, use ValidationSummary() HTML helper.
@Html.ValidationSummary(false, "Please fix the following errors and then submit the form")
To display an asterisk, next to the field that has failed validation, modfiy
ValidationMessageFor() html helper as shown below.
@Html.ValidationMessageFor(model => model.Name, "*")
At this point, next to the field that has failed the validation message, a star symbol will be
displayed instead of the detailed error message. All the detailed error messages will be
displayed using validation summary.
also get, better cache support, as all our JavaScript is now present in a separate file, it can
be cached and accessed much faster.
Example:
We want to change the backgroundColor of "Save" button on "Edit" view to "Red" on
MouseOver and to "Grey" on MouseOut.
First let's look at achieving this using obtrusive javascript.
Step 1: Implement MouseOver() and MouseOut() functions
<script type="text/javascript" language="javascript">
function MouseOver(controlId) {
var control = document.getElementById(controlId);
control.style.backgroundColor = 'red'
}
function MouseOut(controlId) {
var control = document.getElementById(controlId);
control.style.backgroundColor = '#d3dce0'
}
</script>
Step 2: Associate the javascript functions with the respective events.
<input id="btnSubmit" type="submit" value="Save"
onmouseover="MouseOver('btnSubmit')" onmouseout="MouseOut('btnSubmit')" />
Now let's look at making this javascript unobtrusive, using jQuery
Step 1: Right click on the "Scripts" folder in "Soultion Explorer", and add a jScript file with
name = "CustomJavascript.js"
Step 2: Copy and paste the following code in CustomJavascript.js file.
$(function () {
$("#btnSubmit").mouseover(function () {
$("#btnSubmit").css("background-color", "red");
});
$("#btnSubmit").mouseout(function () {
$("#btnSubmit").css("background-color", "#d3dce0");
});
});
Step 3: Add a reference to CustomJavascript.js file in Edit view.
<script src="~/Scripts/CustomJavascript.js" type="text/javascript"></script>
Step 4: Remove the following obtrusive Javascript from "Edit" view
<script type="text/javascript" language="javascript">
function MouseOver(controlId) {
var control = document.getElementById(controlId);
control.style.backgroundColor = 'red'
}
function MouseOut(controlId) {
var control = document.getElementById(controlId);
control.style.backgroundColor = '#d3dce0'
}
</script>
Also, remove "onmouseover" and "onmouseout" events from the button control.
<input id="btnSubmit" type="submit" value="Save"
onmouseover="MouseOver('btnSubmit')" onmouseout="MouseOut('btnSubmit')" />
Client side validation in asp.net mvc is unobtrusive. To turn on client side validation
and unobtrusive JavaScript, make sure the following 2 keys under appSettings element
within web.config file are turned on. This will turn on client side validation and unobtrusive
JavaScript for the entire application.
<appSettings>
<add key="ClientValidationEnabled" value="true" />
<add key="UnobtrusiveJavaScriptEnabled" value="true" />
</appSettings>
Is it possible to turn these features on/off using code?
Yes, the features can be enabled or disabled in Application_Start() event handler in
Global.asax as shown below. This will turn on client side validation and unobtrusive
JavaScript for the entire application.
protected void Application_Start()
{
HtmlHelper.UnobtrusiveJavaScriptEnabled = true;
HtmlHelper.ClientValidationEnabled = true;
}
Is it possible to turn these features on/off for a specific view?
Yes, include the following code, on a view where you want to enable/disable these features.
@{
Html.EnableClientValidation(true);
Html.EnableUnobtrusiveJavaScript(true);
}
How is Unobtrusive validation implemented in asp.net mvc?
Using "data" attributes. For example, if we use "Required" attribute, on Name property
and if we enable client side validation and unobtrusive JavaScript, then the generated HTML
for "Name" field is as shown below.
<input class="text-box single-line"
data-val="true"
data-val-required="The Name field is required."
id="Name"
name="Name"
type="text"
value="Sara Nan" />
data-val=true, indicates that the unobtrusive validation is turned on for this element.
data-val-required="The Name field is required.", indicates that the "Name" field is required
and the associated error message will be displayed if the validation fails. These data
attributes are used by jQuery validation plugin for client side validation.
131
{
[MetadataType(typeof(UserMetaData))]
public partial class User
{
}
public class UserMetaData
{
[Remote("IsUserNameAvailable", "Home", ErrorMessage="UserName already in use.")]
public string UserName { get; set; }
}
}
Step 6: Include references to the following css and script files in Create.cshtml
view.jQuery, jquery.validate and jquery.validate.unobtrusive script files are required
for remote validation to work.
<link href="~/Content/Site.css" rel="stylesheet" type="text/css" />
<script src="~/Scripts/jquery-1.7.1.min.js" type="text/javascript"></script>
<script src="~/Scripts/jquery.validate.min.js" type="text/javascript"></script>
<script src="~/Scripts/jquery.validate.unobtrusive.js" type="text/javascript"></script>
Step 7: Make sure ClientValidation and UnobtrusiveJavaScript are enabled in
web.config
<add key="ClientValidationEnabled" value="true" />
<add key="UnobtrusiveJavaScriptEnabled" value="true" />
}
At this point, disable JavaScript in the browser, and test your application. Notice
that, we don't get client side validation, but when you submit the form, server side
validation still prevents the user from submitting the form, if there are validation errors.
However, delegating the responsibility of performing validation, to a controller action
method violates separation of concerns within MVC. Ideally all validation logic should be in
the Model. Using validation attributes in mvc models, should be the preferred method for
validation. In our next video, we will discuss, creating a custom remote attribute and
overriding IsValid() method.
ASP.NET AJAX enables a Web application to retrieve data from the server
asynchronously and to update portions of the existing page. So these, partial page
updates make web application more responsive and hence improves user experience.
In this video, let's discus using Ajax.ActionLink helper. By the end of this video, we should
be able to load either
1. All students or
2. Top 3 students or
3. Bottom 3 students
depending on the link we have clicked.
into
into
into
into
into
into
into
into
into
into
tblStudents
tblStudents
tblStudents
tblStudents
tblStudents
tblStudents
tblStudents
tblStudents
tblStudents
tblStudents
values
values
values
values
values
values
values
values
values
values
('Mark', 900)
('Pam', 760)
('John', 980)
('Ram', 990)
('Ron', 440)
('Able', 320)
('Steve', 983)
('James', 720)
('Mary', 870)
('Nick', 680)
Step 2: Create an ado.net entity data model using table tblStudents. Upon creating the
entity model, change the name of the entity to Student. Save changes and build the
solution, so that the Student entity class gets compiled.
Step 3: Add "Shared" folder (if it doesn't already exists) in "Views" folder. Right click
on"Shared" folder and add a partial view, with name = _Student.cshtml.
@model IEnumerable<MVCDemo.Models.Student>
<table style="border:1px solid black; background-color:Silver">
<tr>
<th>
@Html.DisplayNameFor(model => model.Name)
</th>
<th>
@Html.DisplayNameFor(model => model.TotalMarks)
</th>
</tr>
@foreach (var item in Model)
136
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Name)
</td>
<td>
@Html.DisplayFor(modelItem => item.TotalMarks)
</td>
</tr>
}
</table>
Step 4: Right click on the "Controllers" folder and add a controller with the following
settings
1. Controller Name = HomeController
2. Template = Empty MVC controller
Copy and paste the following code. Please make sure to include MVCDemo.Models
namespace.
public class HomeController : Controller
{
// Create an instance of DatabaseContext class
DatabaseContext db = new DatabaseContext();
public ActionResult Index()
{
return View();
}
// Return all students
public PartialViewResult All()
{
List<Student> model = db.Students.ToList();
return PartialView("_Student", model);
}
// Return Top3 students
public PartialViewResult Top3()
{
List<Student> model = db.Students.OrderByDescending(x =>
x.TotalMarks).Take(3).ToList();
return PartialView("_Student", model);
}
// Return Bottom3 students
public PartialViewResult Bottom3()
{
List<Student> model = db.Students.OrderBy(x => x.TotalMarks).Take(3).ToList();
return PartialView("_Student", model);
}
}
Step 5: Right click on the "Views" folder and add a folder with name = "Home". Right
click on the "Home" folder and add a view with Name = "Index".
Points to remember:
a) For AJAX to work, jquery and jquery.unobtrusive-ajax javascript files need to be
referenced. Reference to jquery.unobtrusive-ajax.js file should be after jquery.js reference.
b) Ajax.ActionLink() html helper has several overloaded versions. We have used the
following version
ActionLink(string linkText, string actionName, AjaxOptions ajaxOptions);
First parameter : is the link text that we see on the user interface
137
Second parameter : is the name of the action method that needs to be invoked, when
the link is cicked.
Third parameter : is the ajaxOptions object.
<div style="font-family:Arial">
<script src="~/Scripts/jquery-1.7.1.min.js" type="text/javascript"></script>
<script src="~/Scripts/jquery.unobtrusive-ajax.min.js" type="text/javascript"></script>
<h2>Students</h2>
@Ajax.ActionLink("All", "All",
new AjaxOptions
{
HttpMethod = "GET", // HttpMethod to use, GET or POST
UpdateTargetId = "divStudents", // ID of the HTML element to update
InsertionMode = InsertionMode.Replace // Replace the existing contents
})
<span style="color:Blue">|</span>
@Ajax.ActionLink("Top 3", "Top3",
new AjaxOptions
{
HttpMethod = "GET", // HttpMethod to use, GET or POST
UpdateTargetId = "divStudents", // ID of the HTML element to update
InsertionMode = InsertionMode.Replace // Replace the existing contents
})
<span style="color:Blue">|</span>
@Ajax.ActionLink("Bottom 3", "Bottom3",
new AjaxOptions
{
HttpMethod = "GET", // HttpMethod to use, GET or POST
UpdateTargetId = "divStudents", // ID of the HTML element to update
InsertionMode = InsertionMode.Replace // Replace the existing contents
})
<br /><br />
<div id="divStudents">
</div>
</div>
What is AJAX
AJAX stands for Asynchronous JavaScript And XML. AJAX enable web applications to
retrieve data from the server asynchronously. Web application using AJAX enables partial
page updates, ie. only the related section of the page is updated, without reloading the
entire page.
Advantages of AJAX
1. AJAX applications are non blocking. As AJAX requests are asynchronous, the user
doesn't have to wait for the request processing to complete. Even while the request is still
138
being processed by the server, the application remains responsive and the user can
interact with the application. When the request processing is complete, the user interface is
automatically updated. This is not the case with, synchronous requests. The user interface
is blocked and the user cannot do anything else until the request has completed
processing.
2. Better performance and reduced network traffic. AJAX enables an application to
send and receive only the data that is required. As a result, there is reduced traffic between
the client and the server and better peofrmance.
3. No screen flicker. An AJAX response consists of only the data that is relevant to the
request. As a result, only a portion of the page is updated avoiding full page refresh and
screen flickers.
Disadvantages of AJAX:
1. AJAX requests cannot be bookmarked easily
2. AJAX relies on JavaScript. If JavaScript is disabled, AJAX application won't work.
3. Harder to debug
4. Search Engine like Google, Bing, Yahoo etc cannot index AJAX pages.
Applications using AJAX
1. Many web sites like Google, bing, youtube, yahoo use AJAX to implement AutoComplete
feature.
2. Gmail use AJAX to implement AutoSave feature
3. Gmail use AJAX to implement RemoteValidation i.e to validate if a user name is already
in use, when creating a gmail account. We discussed remote validation in Parts 89, 90 &91.
4. Facebook use AJAX, to load data as you keep scrolling down.
AJAX is generally used to implement features like
1. AutoComplete
2. AutoSave
3. Remote validation etc.
Please note: You can create animated GIF images using the following website.
http://spiffygif.com
Step 2: Place the following DIV tag on the view, where you want the image to show up
when request is still being processed.
<div id="divloading" style="display:none;">
<img src="~/Images/spinner.gif"/>
</div>
Step 3: Modify the Ajax.ActionLink() html helper method on the view as shown below.
Notice that, we have set LoadingElementId = "divloading"
@Ajax.ActionLink("All", "All",
new AjaxOptions
{
HttpMethod = "GET",
UpdateTargetId = "divStudents",
InsertionMode = InsertionMode.Replace,
LoadingElementId = "divloading"
})
Step 4: The controller action method in our example, completes in no time. So the image
never shows up. To introduce some latency include, System.Threading.Thread.Sleep(1000);
statement in the controller action method. The duration for the sleep method is in
milliseconds. We have specified 1000 mill-seconds, so the thread is going to sleep for 1
second.
public PartialViewResult All()
{
System.Threading.Thread.Sleep(1000);
List<Student> model = db.Students.ToList();
return PartialView("_Student", model);
}
Now run the application and notice that image shows up, while the request is still being
processed. When request processing completes and when the response is received, the
loading image disappears and the UI is automatically updated, with the data received.
2. At the moment, when you click on "Top 3" link, the data from the previous request is
still present in "divStudents". Only when "Top 3" students become
available,"divStudents" is updated with new data.
3. Our application requirement is to clear, "divStudents" element content, as soon as we
click on any AJAX link and before the associated action method is invoked.
To achieve this, let's use "OnBegin" property of AjaxOptions class.
Step 1: The following JavaScript function, finds the "divStudents" and clear it's contents
function ClearResults()
{
$("#divStudents").empty();
}
Step 2: Associate ClearResults() function with "OnBegin" property of AjaxOptions class.
@Ajax.ActionLink("All", "All",
new AjaxOptions
{
HttpMethod = "GET",
UpdateTargetId = "divStudents",
InsertionMode = InsertionMode.Replace,
LoadingElementId = "divloading",
OnBegin = "ClearResults"
})
Please Note:
OnBegin property can also be used to cancel the invocation of the action method. The
JavaScript function that is associated with "OnBegin" property is invoked before the action
method is invoked. So if that associated JavasSript function returns false, then the action
method will not be invoked. Your JavaScript function may look something like this.
function Validate()
{
if (condition)
{
return true;
}
else
{
return false;
}
}
Let's now discuss an example of using "OnSuccess" property. Immediately after the page
is updated with students data, we want to dispay the total number of rows retrieved using
JavaScript alert.
141
Step 1: We will be using tblStudents table in this demo. Please find the sql script below,
to create and populate this table with some data.
Create Table tblStudents
(
ID int identity primary key,
Name nvarchar(50),
Gender nvarchar(20),
TotalMarks int
)
Insert into tblStudents values('Mark Hastings','Male',900)
Insert into tblStudents values('Pam Nicholas','Female',760)
Insert into tblStudents values('John Stenson','Male',980)
143
Insert
Insert
Insert
Insert
Insert
Insert
Insert
into
into
into
into
into
into
into
tblStudents
tblStudents
tblStudents
tblStudents
tblStudents
tblStudents
tblStudents
values('Ram Gerald','Male',990)
values('Ron Simpson','Male',440)
values('Able Wicht','Male',320)
values('Steve Thompson','Male',983)
values('James Bynes','Male',720)
values('Mary Ward','Female',870)
values('Nick Niron','Male',680)
Step 2: Create an ado.net entity data model using table tblStudents. Change the name
of the generated entity from tblStudent to Student.
Step 3: Download autocomplete widget from http://jqueryui.com/download. The following
folders and files will be downloded.
Step 4: Open "js" folder copy "jquery-1.9.1.js" and "jquery-ui1.10.3.custom.min.js"files and paste them in the "Scripts" folder of you mvc project.
Now open "css" folder. This folder will be present in "ui-lightness" folder.
Copy "images" folder and "jquery-ui-1.10.3.custom.min.css" file and paste them
in "Content" folder in your mvc project. If you are following along, at this point your
solution explorer should look as shown below.
Step 5: Right click on the "Controllers" folder and add "Home" controller. Copy and
paste the following code. Please make sure to include "MVCDemo.Models"namespace.
public class HomeController : Controller
{
public ActionResult Index()
{
DatabaseContext db = new DatabaseContext();
return View(db.Students);
}
[HttpPost]
public ActionResult Index(string searchTerm)
{
DatabaseContext db = new DatabaseContext();
List<Student> students;
if (string.IsNullOrEmpty(searchTerm))
144
{
students = db.Students.ToList();
}
else
{
students = db.Students
.Where(s => s.Name.StartsWith(searchTerm)).ToList();
}
return View(students);
}
public JsonResult GetStudents(string term)
{
DatabaseContext db = new DatabaseContext();
List<string> students = db.Students.Where(s => s.Name.StartsWith(term))
.Select(x => x.Name).ToList();
return Json(students, JsonRequestBehavior.AllowGet);
}
}
Step 6: Right click on the "Index" action method in the "HomeController" and
add"Index" view. Copy and paste the following code.
@model IEnumerable<MVCDemo.Models.Student>
<link href="~/Content/jquery-ui-1.10.3.custom.min.css" rel="stylesheet"
type="text/css" />
<script src="~/Scripts/jquery-1.9.1.js" type="text/javascript"></script>
<script src="~/Scripts/jquery-ui-1.10.3.custom.min.js" type="text/javascript"></script>
<script type="text/javascript">
$(function () {
$("#txtSearch").autocomplete({
source: '@Url.Action("GetStudents")',
minLength: 1
});
});
</script>
<div style="font-family:Arial">
@using (@Html.BeginForm())
{
<b>Name: </b>
@Html.TextBox("searchTerm", null, new { id = "txtSearch" })
<input type="submit" value="Search" />
}
<table border="1">
<tr>
<th>
@Html.DisplayNameFor(model => model.Name)
</th>
<th>
@Html.DisplayNameFor(model => model.Gender)
</th>
<th>
@Html.DisplayNameFor(model => model.TotalMarks)
</th>
</tr>
@foreach (var item in Model)
145
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Name)
</td>
<td>
@Html.DisplayFor(modelItem => item.Gender)
</td>
<td>
@Html.DisplayFor(modelItem => item.TotalMarks)
</td>
</tr>
}
</table>
</div>
147
148