Introduction
The ASP.NET MVC Framework helps developers implement the Model-View-Controller pattern. The framework provides a clean separation of concerns in a web application. This gives the developers better control over the applications which are easy to maintain.
The MVC Framework is a shift of thinking from the traditional ASP.NET web applications. In an ASP.NET application, a URL points to a resource or content such as a web page or image which is served by the server. The content must exist before being served else an exception may be thrown. On the other hand, in a MVC application, the URL request is handled by a piece of code. This means that every request is handled by a method of a particular class. So there is basic difference between the two programming models. The prior serves existing contents for a request whereas the later runs a piece of code for a request.
Before we proceed, let’s look at Fig 1. This figure shows the solution when we create an ASP.NET MVC Application using Visual Studio. It has different sub folders including Models, Views and Controllers. A discussion of these sub folders will follow shortly. If you expand the References folder, you will find a reference added to System.Web.Mvc nampspace which is required to work with a MVC application.
Fig 1
Controllers
In a MVC application, a Controller is responsible for handling a request. Each URL request is served by a particular controller. It acts as a bridge between the requests and the application.
In Fig 1, you can see a Controllers folder. All controllers are added under this folder. The controller name must be suffixed with the word ‘Controller’. For example, to create a Product controller, we name it as ProductController. If a user enters the URL /Product, it gets mapped to /ProductController. Even if a controller with the name Product exists, it will not respond to a user request. This is the default behavior for an ASP.NET MVC Application.
A controller is just a C# or VB class. This class is driven from System.Web.Mvc.Controller base class. Listing 1 is an example of a ProductController class:
Listing 1
using System;
using System.Web.Mvc;
namespace MVCDemo.Controllers
{
public class ProductController : Controller
{
public ActionResult Index ()
{
// Add action logic here
throw new NotImplementedException();
}
}
}
A controller consists of Controller Actions. Controller Actions or simply actions are the public methods in a controller. Each request is processed by a controller action in a controller. For example, if a user enters the URL /Product/Index, the Index method in the ProductController class is invoked. Any method which is added as a public method to a controller becomes a controller action. Controller actions have certain properties which include:
• They cannot be overloaded
• All actions must be public
• They cannot be static
• An action returns an ActionResult
The outcome (return type) of a controller action is an Action Result. There are different types of action result, all of which are driven from the base class ActionResult. These action results include:
ViewResult: Returns HTML markup or content to the browser
EmptyResult – Represents no result
RedirectResult – Redirect to a new URL
RedirectToRouteResult – Redirects to a new controller action
JsonResult – Returns JavaScript Object Notation result for AJAX applications
ContentResult – Returns a text result
We are not required to return a particular type of action result directly i.e. we don’t return a ViewResult or JsonResult. Rather we use one of the methods provided by the base class Controller. The different base class methods are:
1. View – Returns a ViewResult action result.
2. Redirect – Returns a RedirectResult action result.
3. RedirectToAction – Returns a RedirectToRouteResult action result.
4. RedirectToRoute – Returns a RedirectToRouteResult action result.
5. Json – Returns a JsonResult action result.
6. Content – Returns a ContentResult action result.
Listing 2 shows controller actions return different types of action results:
Listing 2
using System;
using System.Web.Mvc;
namespace MVCDemo.Controllers
{
public class ProductController : Controller
{
// 1 – return ViewResult()
public ActionResult Index ()
{
return View ();
}
// 2 – redirect to another controller action – RedirectToRouteResult()
public ActionResult RedirectMe ()
{
return RedirectToAction ("Index");
}
// 3 – return content – ContentResult()
public ContentResult ReturnContent ()
{
return Content ("Just a simple string...");
}
public DateTime ReturnDate()
{
return DateTime.Now;
}
}
}
The first controller action (Index) returns a view by calling the base class method. The view returned should correspond to name of the controller action. So a view named Index.aspx is returned to the browser. The location of Index.aspx will come up in the next section.
The base class method RedirectToAction in the second controller action (RedirectMe) redirects the request to the first controller action. The request is redirected to Index.aspx.
Another special type of action result is the ContentResult which returns plain text. Using the base class method Content in the third controller action (ReturnContent), a string is returned to the browser. The source of the page will show you a simple text message with no HTML embedded in it.
If a controller action does not return an action result, it is also treated as ContentResult. The MVC framework calls the ToString () method on the result and wraps it in a ContentResult action result. The fourth action controller will again return current DateTime as a string.
Views
As mentioned, in a MVC application, a request does not map to a page or resource. Rather all requests are handled by actions in a controller. An action can return different types of action results including a View. A view is closest to an asp.net web page since it renders HTML markup and content. Listing 2 has one of the following actions:
public ActionResult Index ()
{
return View ();
}
This action returns a view. The action is invoked by typing in /Product/Index which returns a page Index.aspx from the following location:
\Views\Product\Index.aspx
The location of a view is inferred from the name of the Controller and name of the Controller Action. If you look at Fig 1, you will find a Views folder. All views are under this folder. For the above URL, the Views folder must have a sub folder named after the Controller (Product). The sub folder in turn must have a view page named after the controller action (Index in this case). So a view page Index.aspx is returned to the browser. Figure 2 makes it clear:
Fig 2
We can also explicitly mention the name of the view when calling the return View () statement. For example, the statement ‘return View (“Index”)’ is equal to the statement ‘return View ()’.
A View is an HTML document where scripts are written. Writing a view is a kind of a journey back to the Active Server Pages age. An ASP page does not have the notion of a code behind file. All validation, data handling and business logic code are written on the same page. We make extensive use of Response.Write in traditional ASP application. A script written in a View is somewhat similar to an ASP code.
In a view, script delimiters <% and %> mark the beginning and end of a script. We can then use the Response.Write method to emit some data. For example, the following line will write a string:
<% Response.Write ("This is a view script");%>
In order to save coding time, we can also use the shortcut delimiter <%= %> in place of Response.Write. Listing 3 makes use of both delimiters:
Listing 3
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Index.aspx.cs"
Inherits="MVCDemo.Views.Home.Index" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Index View" runat="server">
<title>Index</title>
</head>
<body>
<div>
<% Response.Write ("MVC framework demo"); %>
<br />
Date-Time: <%=DateTime.Now%>
</div>
</body>
</html>
To facilitate the developers, HTML Helpers are used to add content to a view. HTML Helpers are methods that return a string (such as markup). This way we don’t have to type the HTML markup. These HTML helpers generate standard controls including TextBoxes, hyperlinks, dropdown lists etc. The helper methods are called using the HTML property of the view. The following snippet generates a simple Product entry form using HTML Helpers:
<form method="post" action="/Product/Add">
<label for="ProductName">Product: </label>
<%=Html.TextBox ("ProductName")%>
<br /><br />
<label for="Quantity">Quantity: </label>
<%=Html.TextBox ("Quantity")%>
<br /><br />
<label for="Available"> </label>
<%=Html.CheckBox ("Available", "Available")%>
<br /><br />
<input type="submit" value="Add Product" />
</form>
Notice the use of the delimiters with HTML helper method. Since HTML Helper methods return a string, we need to use Response.Write or the shortcut delimiter <% %> to render the string (control markup) in the browser. For this reason, the above snippet has the Html.TextBox (…) helper method enclosed in a delimiter.
Another interesting property of a view is the ViewData property. ViewData is used by controllers to pass data to a view. The ViewData works like a dictionary holding collections of key-value pairs. Let us look at an example:
public ActionResult ProductDetails ()
{
ViewData ["Info"] = "Available in stock";
return View ();
}
In the above snippet, the action ProductDetails uses ViewData property to pass down the key ‘Info’ with a value ‘Available in stock’ to the view. This key can be used later in the view to retrieve the data with the following code:
<%= ViewData ["Info "] %>
A good use of the ViewData property would be to send database records or XML data to a view. The controller action can access the database and pass the data to the view. This also results in a clean separation of concerns where data access is separate from the view. Similarly an action can read a file and send its contents to the view.
Models
The controllers hold logic to handle incoming request and views represent the UI of a MVC application. Where does the business logic and data access code reside? This is where Models come into play. Models are responsible for hosting the business logic. They also hold data access components. For example, we can have DAL (Data Access Layer) classes, Entities or LINQ to SQL classes as models. In fig 1, you can see a Models folder. All data access classes must be placed under this folder.
As a best practice, fat models and thin controllers are recommended. The controller should only be concerned with handling requests whereas the models should deal with business logic and data access code. Even if a controller is passing data to a view fetched from the model, it should be limited to making calls to the model. This also results in a clean separation of concerns in a MVC application.
URL Routing
So how does a piece of code get executed when a request is made in a MVC application? This is where the URL Routing comes into play. URL Routing is a key feature of a MVC application. It is a mechanism through which a URL is mapped to a controller action. URL Routing is handled in the Web.Config and Global.asax file.
The Global.asax handles the application lifecycle events. One of these events is the Application_Start event which is raised when an application starts. Listing 4 shows the Global.asax:
Listing 4 – Global.asax
using System.Web.Mvc;
using System.Web.Routing;
namespace HelloMVC
{
public class GlobalApplication : System.Web.HttpApplication
{
public static void RegisterRoutes (RouteCollection routes)
{
routes.IgnoreRoute ("{resource}.axd/{*pathInfo}");
routes.MapRoute (
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = "" }
// Parameter defaults
);
}
protected void Application_Start ()
{
RegisterRoutes (RouteTable.Routes);
}
}
}
The Application_Start event calls the RegisterRoutes method. The RegisterRoutes method creates a route table.
The default route table contains of a single route (Default). If you look at above listing, you can see that the Default route maps the first segment of a URL to a controller name, the second segment of a URL to a controller action, and the third segment to a parameter named id. This means that a request such as /Prodct/Add/20 will look for a controller Product with action Add and a parameter to add equal to 20.
The Default route includes defaults for all three parameters. If you don’t supply a controller, then the controller parameter defaults to the value Home. If you don’t supply an action, the action parameter defaults to the value Index. Finally, if you don’t supply an id, the id parameter defaults to an empty string.
Let us see a few examples of URL mapping. Suppose we enter a URL
/Home/Index/2
The parameters for this URL are:
Controller = Home
Action = Index
Id = 2
The code generated for these parameters is
HomeController.Index (3)
Similarly if the URL is
/Product/Delete/19
It is converted to:
Controller = Product
Action = Delete
Id = 19
Now the code generated is
ProductController.Delete (19)
The default route table is fine for a general MVC application. However, we can also create custom routes depending on our routing needs. For example, we are creating an Online Reporting System. This system helps user create and maintain reports. The reports are maintained on a date-by-date basis. The user is allowed to enter a URL like:
/Files/06-19-2008
To handle such request, let us edit Global.asax in listing 5 with our new route defined:
Listing 5
using System.Web.Mvc;
using System.Web.Routing;
namespace MVCDemo
{
public class GlobalApplication : System.Web.HttpApplication
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"OnlineReporting",
"Files/{DateModified}",
new {controller = "Files", action = "Modified"}
);
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = "" }
// Parameter defaults
);
}
protected void Application_Start()
{
RegisterRoutes (RouteTable.Routes);
}
}
}
The order in which routes are added matters. The OnlineReporting route is added before the default blog. If it is placed after the default route, the default route will always get called before the custom route. Hence our request won’t be handled resulting in an exception.
For the OnlineReport route to work, we must have a Files controller and a Modified action controller. When the Modified method is invoked, DateModified is passed in as parameter. So the code generated is
Files.Modified (DateModified)
Since this method accepts datetime as parameter, the MVC framework converts the parameter to a datetime value.
Summary
In this blog entry, we looked at the architecture of an ASP.NET MVC application. Unlike asp.net applications, a request is handled by a Controller. A Controller is class which can have public methods known as Controller Actions. The request invokes the controller action of a controller. The result of a controller action is of type Action Result. There are different types of action results. We can use methods of the base class Controller to return different types of results.
A view is similar to an asp.net page which renders html markup to a browser. In a view, delimiters <% %> mark the start and end of a script. To render markup on a page, HTML Helper methods are available in a view. These helper methods rid us of writing markup code by rendering controls on a page. The ViewData property of a view is used to pass down information from a controller to a view.
Models take care of business logic and data access in a MVC application. The models can consist of DALs, Entities and LINQ to SQL classes. To maintain clean separation of concern, we should favor fat models and thin controllers.
URL Routing is another important feature of a MVC application. It handles and supports user friendly URLs. To handle URLs, a route table is created in the Global.asax file using the Applicatin_Start event handler. This table defines the parameters for a URL which includes the controller, the controller action and any id passed to the controller action. Finally we can also setup custom routes to handle any special routing need.
I hope this post has given you sufficient insight into ASP.NET MVC architecture. In a future post, I will talk about creating and working with a MVC application. So stay tuned…