Introduction
In my first article, I gave you an overview of the ASP.NET MVC5 framework and described it as Microsoft's implementation of the Model-View-Controller (MVC) pattern. The MVC pattern is an architectural pattern that divides the application into three interconnected parts based on the "Separation of Concerns" principle. These three parts are called Model, View and Controller. This article focus on the Controller part of the pattern and provides you a detailed description of the role and importance of controllers in ASP.NET MVC5.
Roadmap
This is the second article of the series “Learn ASP.NET in MVC Style” and discusses controllers in ASP.NET MVC5. The following is the roadmap of the series so far:
Getting Started
A controller is the most important part of the ASP.NET MVC5 framework and is responsible for the overall flow of a web application. If we look at the Request/Response pattern of an ASP.NET MVC application, it works slightly differently from the traditional Web Forms applications where the requests are mapped directly to the (.aspx) files placed on the web server.
To simplify it, consider a web application named "Developer Cafe" that has been developed using ASP.NET Web Forms and is deployed over the web server. If a user wants to access the Contact Page of the application, he should type the URL as [www.developercafe.com/contact.aspx]. The "contact.aspx" refers to the web page placed at the root directory of the web server and contains the contact information about the application. This is the traditional approach used for web applications developed using ASP.NET Web Forms.
If we develop the same application using the ASP.NET MVC5 framework, its Request/Response pattern is completely different. Instead of targeting files placed on the root directory of the web server, it maps the URLs to special classes referred to as Controllers. These classes contain the targeting methods termed "Action Methods" or simply "Actions" that are responsible for processing the incoming requests. So, if the same user must access the contact information of the "Developer Cafe" application developed in ASP.NET MVC5, he should type something like this: [www.developercafe.com/Contact/Index].
Here, "Contact" refers to the Controller class that processes the user requests and Index refers to the Action Method present inside the controller class that contains the contact information about the application.This mapping of URL requests to Controller classes is done using the Routing Mechanism of the ASP.NET MVC5 Framework. The major benefit of this URL approach is better Search Engine Optimization (SEO). I'll not discuss the routing mechanism in this article, instead focus on the anatomy of a Controller class.
Controller in ASP.NET MVC5
Controller
A Controller is a special class that inherits from an abstract ControllerBase class and is responsible for the overall flow of an ASP.NET MVC application. Every request that comes to an ASP.NET MVC5 application is handled by the controller. It handles the HTTP requests, retrieves and stores the data, returns the appropriate responses and executes the application logic.
The ASP.NET MVC5 framework follows the "
Convention over Configuration" principle, so all the controller classes should be suffixed with the word "Controller" and placed inside the Controllers folder of the application.
Action Methods
A controller provides public methods that respond to the incoming HTTP requests made to an ASP.NET MVC application. These methods are known as Action Methods and generally return the ActionResult objects. ActionResult is an abstract class and these action methods can return various types of objects to incoming requests that all inherit from the ActionResult class. The results could be a simple file to download, a plain text, JSON to process or an HTML page depending upon the request of the user.
Action Results
The various types of action results that can be returned from an action method are described in the following table. The table is taken from the official MSDN documentation of ActionResult class [
^] :
How to Create a Controller
Step 1: In order to dig deep into Controllers, let's create an ASP.NET MVC5 application. Open Visual Studio and create a new project by going to File => New => Project.
A dialog window appears that asks for the type of the project. Click "Web" on the left side of the dialog window for ASP.NET Web Application. Give it a suitable name and Click OK.
Step 2: A new dialog window asks you to select the template for your web application. Since we are creating a web application using the ASP.NET MVC framework and must learn everything from scratch, choose the Empty template.
Also, for the basic essential files for our application, check the MVC checkbox and click OK.
Step 3: The Visual Studio creates a project for us with minimal layout having directories like Models, Views and Controllers along with
Web.config and
Global.asax files.
One of the interesting things to be noticed here is the
App_Start folder that was also created having a
RouteConfig.cs file in it. This file contains the routing configuration of the entire application. It defines the classes and methods to start looking for, when the incoming requests came to the server. I'll explain in complete detail about the routing mechanism of an ASP.NET MVC application in future articles in the series. The only thing to remember right now is that the file contains routing configuration needed by the application to execute the Request/Response pattern.
Step 4: The ASP.NET MVC5 framework works heavily on the "Convention over Configuration" principle. By default, every controller class should be suffixed with the
Controller keyword and placed inside the Controllers folder of the ASP.NET MVC application. We'll follow the same principle in this article.
To create a Controller, right-click on the Controllers Folder and select Add and then Controller.
Step 5: A dialog window appears with several options to choose from. Select the "MVC 5 Controller - Empty" option and click Add.
Step 6: Since I already told you that ASP.NET MVC5 follows the "Convention over Configuration" principle, it gives you a dialog window to name your controller. You should note that the Controller keyword should be suffixed with the name.
Initially, I told you about the
RouteConfig.cs file. This class is used by the ASP.NET MVC framework to route incoming user requests to controllers. It has a default route configuration that is followed by the ASP.NET MVC application. If we open the RouteConfig.cs file, you'll notice a static method
RegisterRoutes() that contains the routing information.
- public class RouteConfig
- {
- 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 }
- );
- }
- }
It contains the two properties routes.IgnoreRoute and routes.MapRoute. If this scares you, don't worry. You don't need to understand this right now. For now, just remember that the routes.MapRoute property contains the routing information of the application.
The three parameters of routes.MapRoute are described as follows.
- name: It describes the name of the Route
- url: It describes the syntax of the requested URL that user should type in the browser.
- defaults: It contains the default configurations for the incoming URLs. The defaults are:
{
controller = "Home",
action = "Index",
id = UrlParameter.Optional
}
It means that the default controller to be mapped for all incoming requests is Home, the default action method to be targeted inside the controller class should be Index and the parameter is optional to be passed with the URL.
After looking at the default routing configuration for an ASP.NET MVC application, we should name our controller as HomeController. However, this is only the default configuration and can be changed depending upon the requirements of your application. But for the sake of simplicity, we will stick with the defaults and type the name as Home suffixed with the Controller keyword and click Add.
Step 7: Voila, the HomeController is created inside the Controllers folder. An interesting point to be noted is that a folder with the same name of controller is created inside the Views folder. This is because the Controller is responsible for responding to incoming requests and generally the user wants HTML pages to be rendered down to the browser. To respond with HTML pages to the user, we make use of views.
A View is nothing but an HTML file returned to the user as a response and by the "Convention over Configuration" principle, for every controller, a View folder should be created with the same name in the Views folder.
Step 8: Open the HomeController.cs file and get ready to work with it:
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Web;
- using System.Web.Mvc;
-
- namespace ControllerDemo.Controllers
- {
- public class HomeController : Controller
- {
-
- public ActionResult Index()
- {
- return View();
- }
- }
- }
Working with Controller
Until now, we have learned what a controller is exactly and how to create one. Let's look inside the controller class and play with the methods provided by the controller that responds to the incoming requests.
- public class HomeController : Controller
- {
- public ActionResult Index()
- {
- return View();
- }
- }
When we create a controller class, it inherits the System.Web.Mvc.Controller class and gives us a public method named "Index". I mentioned the word "public" because in C#, by default all the methods in a class are private. So, if we must manually create a method inside a controller to be treated as an action method, we should declare it as public.
Types of Action Results
The public methods inside a controller class are termed Action Methods. By default, these methods return the ActionResult objects to incoming requests of the user. The ActionResult is an abstract class and the instance of this class represents the result of an action method in response to a user request. There are several types of results an action method can return. The following describes some of the common results.
1. ViewResult: This is the most common and default result returned to the user. It renders a web page to the user from the Views folder. If we look at the HomeController class, it calls the helper method View() that looks for the associated "Index" view to be rendered on the browser.
- public class HomeController : Controller
- {
- public ActionResult Index()
- {
- return View();
- }
- }
If we execute the code, it shows the following output.
This error occurs because we didn't have any view with the name Index to be rendered on the browser, but you get the idea about the ViewResult class that is used to render the View to the user.
2. ContentResult: It is used to return a user-defined content type (text, xml). We can return a simple string to the user using the Content() method. Consider the following code:
- public class HomeController : Controller
- {
- public ActionResult Index()
- {
- return Content("This method returns plain text.");
- }
- }
If we execute the code, it shows the following output.
3. JsonResult: We can use this class to return a JavaScript Object Notation (JSON) object to the user. We need to use the Json() helper method to return JSON. Consider the following code:
- public class HomeController : Controller
- {
- public ActionResult Index()
- {
- var employee = new
- {
- Name = "Sahil Sharma",
- Age = "25",
- Occupation = "Software Developer"
- };
-
- return Json(employee,JsonRequestBehavior.AllowGet);
- }
- }
Here, we need to provide an extra parameter "JsonRequestBehaviour.AllowGet" with the Json() method to allow it to work on the GET requests.
4. FileResult: We can also provide a facility to the user to request a file to download from the application. To do this, we need to return the FileResult object using the File() method. Consider the following code:
- public class HomeController : Controller
- {
- public ActionResult Index()
- {
- return File(Server.MapPath("~/App_Data/documentation.pdf"),
- contentType: "application/pdf",
- fileDownloadName: "documentation.pdf");
- }
- }
If we run the following code, it gives us an option for a PDF file to be downloaded to our local system.
5. RedirectToAction: This class is used to redirect to another action method of the controller class. Consider the following code:
- public class HomeController : Controller
- {
- public ActionResult Index()
- {
- return RedirectToAction("Contact");
- }
-
- public ActionResult Contact()
- {
- return Content("This is the redirected Contact method.");
- }
- }
Here, I'm using the Contact() action method as the redirected method to be displayed whenever a request comes to Index() method.
The Useful Attributes
There are several attributes that can be applied to action methods of a controller for various tweaks. These are termed Action Selectors. As the MSDN says, "An action selector represents an attribute that is used to influence the selection of an action method". The following describes some of the commonly used selectors.
1. ActionName: This attribute allows us to specify a different name to an action method irrespective of its original name. Consider the following code:
- public class HomeController : Controller
- {
- [ActionName("Contact")]
- public ActionResult Index()
- {
- return Content("This method is originally named Index");
- }
- }
If we execute the following code and look for the Index action method, it gives us a 404 error.
This is because the name of the method is now changed to Contact and we should look for the Contact method of the Home controller.
2. NonAction: Suppose we want a method of the controller class to be treated as a normal method instead of an action method. How can we do that? We learned in the beginning of the article that only public methods are treated as action methods. So, we can simply convert an action method into a normal method by declaring it as private or protected.
But there may be other situations. What if there is a requirement that demands the public methods of a controller class not to be treat as action methods. Can we do that as well? The answer is Yes, we can. There is an attribute called NonAction that indicates the public method should not be treated as an action method. Consider the following code:
- public class HomeController : Controller
- {
- [NonAction]
- public ActionResult Index()
- {
- return View();
- }
- }
If we execute the following code, we'll get a HTTP 404 error because the Index method is no longer an action method and cannot be invoked.
3. HttpGet: This attribute is used to restrict an action method to respond only to the HTTP GET requests. It is also the default request attribute for action methods. Consider the following code:
- public class HomeController : Controller
- {
- [HttpGet]
- protected ActionResult Index()
- {
- return View();
- }
- }
If we specify an HttpGet attribute to an action method, it works only for the GET requests and otherwise returns a 404 error.
4. HttpPost: This attribute is similar to the HttpGet attribute with the only difference that instead of responding to GET requets, it responds to POST requests. Consider the following code:
- public class HomeController : Controller
- {
- [HttpPost]
- public ActionResult Index()
- {
- return View();
- }
- }
A Trivial Question About Action Methods?
There is an interesting question frequently asked in the MVC interviews about Action Methods. We learn in this article that the Controller is a class and a class can have multiple methods. According to the
OOP principles, a class can have two methods with the same name. This principle is known as
Function Overloading that is an example of Static or Compile Time Polymorphism.
If we follow the same principle here, it concludes that we can have two action methods with the same name inside a controller. Let's have a look at the following code:
- public class HomeController : Controller
- {
-
- public ActionResult Index()
- {
- return Content("This is the first Index action");
- }
-
-
- public ActionResult Index(string value)
- {
- return Content("This is the second Index action");
- }
- }
What do you think? Does the code work? If we execute the code, it gives us the following:
Whoa, it throws a System.Reflection.AmbiguousMatchException instead of executing the code. The error says, "The current request for action "Index" on controller type "HomeController" is ambiguous".
So, how can we fix this error? There are two options by which we can fix this situation and work with two action methods having the same name and not get an exception.
Option 1: Using the ActionName attribute: In order to have two action methods with the same name inside a controller, we'll use the ActionName attribute. Consider the following code:
- public class HomeController : Controller
- {
- public ActionResult Index()
- {
- return Content("This is the first Index action");
- }
-
- [ActionName("IndexNew")]
- public ActionResult Index(string value)
- {
- return Content("This is the second Index action");
- }
- }
Here, I'm giving the second action method an alternate name IndexNew to distinguish it from the first one. In this way, we can have two action methods with the same name inside a controller.
Option 2: Using the HTTP request attributes: This approach makes use of HttpGet and HttpPost attributes to have two action methods with the same name inside a controller. Consider the following code:
- public class HomeController : Controller
- {
- [HttpGet]
- public ActionResult Index()
- {
- return Content("This is the first Index action called using GET method.");
- }
-
- [HttpPost]
- public ActionResult Index(string value)
- {
- return Content("This is the second Index action called using POST method.");
- }
- }
If we execute the following code, we won't get any error and the code works properly. When a GET request is received by the application, it is automatically handled by the first method and for a POST request, the second method is invoked automatically.
Point Of Interest
In this article, we learned about Controllers, their role and how they work in an ASP.NET MVC application. In the next articles of this series, we'll dig deep into the world of ASP.NET MVC5. Your feedback and constructive criticism is always appreciated, keep it coming. Until then, try to put a ding in the Universe.