ASP.NET MVC controllers are responsible for controlling the flow of application execution. When you make a browser request against an ASP.NET MVC application, a controller is responsible for returning a response to that request.
Controllers expose one or more actions. A controller action can return different types of action results to a browser. For example, a controller action might return a view, a controller action might return a file, or a controller action might redirect you to another controller action.
In this chapter, you learn how to create controllers and controller actions. You learn how to return different types of controller action results. You also learn how to use attributes to control when a particular controller action gets invoked. We complete this chapter by discussing how you can write unit tests for your controllers and actions.
The easiest way to create a controller is to right-click the Controllers folder in the Visual Studio Solution Explorer window and select the menu option Add, Controller. Selecting this menu option displays the Add Controller dialog (see Figure 3.1). If you enter the name ProductController
, you get the code in Listing 3.1.
A controller name must end with the suffix Controller. If you forget to include the Controller suffix, you can’t invoke the controller.
Notice that a controller is just a class (a Visual Basic or C# class) that inherits from the base System.Web.Mvc.Controller
class.
Any public method exposed by a controller is exposed as a controller action. The controller class in Listing 3.1 exposes one action named Index()
. The Index()
action is the default action that is invoked on a controller when no explicit action is specified.
By default, any public method contained in a controller class can be invoked by anyone located anywhere on the Internet. Be careful about the methods that you publicly expose from a controller. If you want to prevent a public controller method from being invoked, you can decorate the method with the NonAction
attribute.
Notice that the Index()
action returns an ActionResult
. A controller action always returns an ActionResult
(even if it doesn’t appear to be returning an ActionResult
). The ActionResult
determines the response returned to the browser. The Index()
controller returns a view as its ActionResult
.
A controller typically exposes multiple actions. You add actions to a controller by adding new methods to the controller. For example, the modified Product
controller in Listing 3.2 exposes three actions named Index()
, Help()
, and Details()
.
Here’s what you would type into a browser address bar to invoke the different actions:
• /Product/Index
—Invokes the ProductController Index()
action
• /Product
—Invokes the ProductController Index()
action
• /Product/Help
—Invokes the ProductController Help()
action
• /Product/Details/34
—Invokes the ProductController Details()
action with the value 34 for the Id
parameter
You invoke a controller action by following a particular pattern that looks like this:
{controller}/{action}/{id}
Notice that when you invoke a controller, you don’t include the Controller suffix in the URL. For example, you invoke the Product
controller with the URL /Product/Index and not the URL /ProductController/Index.
The default controller action is the Index()
action. Therefore, the URL /Product/Index and the URL /Product both invoke the Product
controller Index()
action.
When you invoke a controller, you can supply an optional Id
parameter. For example, the Details()
action accepts an Id
parameter. The URL /Product/Details/2 invokes the Details()
action and passes the value 2 for the Id
parameter. The name of the parameter is important. You must name the parameter Id
.
The default pattern for invoking controller actions is defined by the default route in the Global.asax file. If you want to modify the URL pattern for invoking actions, you can modify this default route. To learn more about creating custom routes, see Chapter 9, “Understanding Routing.”
A controller action always returns an ActionResult
. The ASP.NET MVC framework includes the following types of ActionResults
:
• ViewResult
—Represents an ASP.NET MVC view.
• PartialViewResult
—Represents a fragment of an ASP.NET MVC view.
• RedirectResult
—Represents a redirection to another controller action or URL.
• ContentResult
—Represents raw content sent to the browser.
• JsonResult
—Represents a JavaScript Object Notation result (This is useful in Ajax scenarios).
• FileResult
—Represents a file to be downloaded.
• EmptyResult
—Represents no result returned by an action.
• HttpUnauthorizedResult
—Represents an HTTP Unauthorized status code.
• JavaScriptResult
—Represents a JavaScript file.
• RedirectToRouteResult
—Represents a redirection to another controller action or URL using route values.
Typically, you don’t directly return an ActionResult
from a controller action. Instead, you call a controller method that returns an ActionResult
. For example, if you want to return a ViewResult
, you call the controller View()
method.
Here’s a list of controller methods that return ActionResults
:
• View()
—Returns a ViewResult
• PartialView()
—Returns a PartialViewResult
• RedirectToAction()
—Returns a RedirectToRouteResult
• Redirect()
—Returns a RedirectResult
• Content()
—Returns a ContentResult
• Json()
—Returns a JsonResult
• File()
—Returns a FileResult
• JavaScript()
—Returns a JavaScriptResult
• RedirectToRoute()
—Returns a RedirectToRouteResult
In the following sections, we examine several of these ActionResults
in more detail.
We examine partial view results (AKA view user controls or partials) in Chapter 10, “Understanding View Master Pages and View User Controls.”
The most common ActionResult
returned by a controller action is a ViewResult
. A ViewResult
represents an ASP.NET MVC view. You return a ViewResult
when you want to return HTML to the browser.
The Details()
action exposed by the Customer
controller in Listing 3.3 returns a ViewResult
.
The Details()
method calls the View()
method to return a ViewResult
. There are two ways that you can specify a view when calling the View()
method: You can specify a view implicitly or explicitly.
In Listing 3.3, the name of the view is specified implicitly. The ASP.NET MVC framework determines the name of the view from the name of the action. In this case, the action returns a view at the following location:
ViewsCustomerDetails.aspx
The ASP.NET MVC framework follows this pattern to determine the location of a view:
Views{controller}{action}.aspx
If you prefer, you can specify the name of a view explicitly. In Listing 3.4, the View()
method includes an explicit view name.
The View()
method in Listing 3.4 returns the same view. However, it is explicit about the view name. Notice that you don’t include the .aspx extension when providing the name of the view.
If you plan to build unit tests for your ASP.NET MVC application, it is a good idea to be explicit about your view names. Otherwise, you cannot test to see if the view with the right view name has been returned from a controller action.
A view name can contain a relative or absolute path. If you specify a relative path, then the location of the view is calculated relative to its normal location. For example, calling View("Subfolder/Details")
from the Details()
action would return a view from this location:
ViewsDetailsSubfolderDetails.aspx
You also can provide an absolute path to a view. If you call View("~/Details.aspx")
from the Details()
action, then a view from the following location is returned:
Details.aspx
Notice that when you provide an absolute path, you provide the .aspx extension.
There are multiple overloads of the View()
method that accept different parameters. Here is a list of all the possible parameters that you can pass to the View()
method:
• viewName
—The name of the view (or path to the view)
• masterName
—The name of a view master page
• model
—The model class passed to the view
We discuss view master pages in Chapter 10. We discuss passing models to views in Chapter 4, “Understanding Views.”
Often, you need to redirect from one controller action to a second controller action. You can use the RedirectToAction()
method to return a RedirectResult
that redirects a user from one controller action to another.
For example, the Widget
controller in Listing 3.5 contains a Details()
action. If the Details()
action is invoked without a value for the id
parameter, the user is redirected to the Index()
action.
The id
parameter in Listing 3.5 is a nullable type. A nullable integer can have any value of an integer or the value null. You create a nullable type by placing a question mark (?
) after the type
keyword.
There are multiple overloads of the RedirectToAction()
method. Here’s a list of all the possible parameters that you can use with the RedirectToAction()
method:
• actionName
—The name of a controller action
• controllerName
—The name of a controller
• routeValues
—The route values passed to the action
You can use the controllerName
parameter to redirect from an action in one controller to another controller. When you specify the controllerName
, you do not include the Controller suffix. For example, use Product
and not ProductController
like this:
(C#)
return RedirectToAction("Index", "Product");
(VB)
Return RedirectToAction("Index", "Product")
Providing a value for routeValues
is particularly important when you need to pass an id
to an action. For example, imagine that you want to redirect to the Details()
action from another action and pass a value for the id
parameter. In that case, you can call the RedirectToAction()
method like this:
(C#)
return RedirectToAction("Details", new {id=53});
(VB)
Return RedirectToAction("Details", New With {.id=53})
This call to the RedirectToAction()
method passes the value 53 as the id
parameter to the Index()
action.
The Say()
action exposed by the Hello
controller in Listing 3.6 does not return an ActionResult
. Instead, the action returns a string. If you invoke this action, the string is rendered to your browser (see Figure 3.2).
An action method can also return DateTime values, integer values, or any type of values from the .NET framework.
Behind the scenes, the ASP.NET MVC framework converts any value that is not an ActionResult
into an ActionResult
. In particular, the ASP.NET MVC framework converts any value that is not an ActionResult
into a ContentResult
. The ASP.NET MVC framework calls the ToString()
method on the value and wraps the resulting value in a ContentResult
.
If you prefer, you can explicitly return a ContentResult
like this:
(C#)
(VB)
There are multiple overloads of the Content()
method. Here is a list of all the possible parameters that you can pass to this method:
• string
—The string to render to the browser
• contentype
—The MIME type of the content (defaults to text/html)
• contentEncoding
—The text encoding of the content (for example, Unicode or ASCII)
JavaScript Object Notation (JSON) was invented by Douglas Crockford as a lightweight alternative to XML appropriate for sending data across the Internet in Ajax applications. For example, you can convert a set of database records into a JSON representation and pass the data from the server to the browser.
You return JSON from an action by calling the Json()
method. For example, the controller in Listing 3.7 returns a collection of quotations.
Behind the scenes, the Json()
method uses a class in the .NET framework called the JavaScriptSerializer
class to serialize an object into a JSON representation. You can control how this class serializes objects by registering custom converters.
When the List()
action is invoked, the action returns the following JSON representation of the collection of quotations:
["Look before you leap", "The early bird gets the worm", "All hat, no cattle"]
You can invoke the Index()
method from a view by performing an Ajax call against the server. The view in Listing 3.8 grabs the collection of quotations and randomly displays one of them.
The view in Listing 3.8 uses jQuery to retrieve the JSON result from the server. We discuss jQuery in detail in Chapter 15, “Using jQuery.”
The Json()
method has several overloads and supports the following parameters:
• data
—The content to serialize
• contentType
—The MIME type of the content (defaults to application/json)
• contentEncoding
—The text encoding of the content (for example, Unicode or ASCII)
You can return a file from an action. For example, you can return an image file, a Microsoft Word file, or a Microsoft Excel file.
For example, the controller in Listing 3.9 exposes two actions named Index()
and Download()
. The Index()
action displays a view with a link to the Download()
action. When you click the link, you are prompted with a dialog to view or save the file (see Figure 3.4).
The Download()
action returns a Microsoft Word document named CompanyPlans.docx. Notice that the File()
method requires three parameters: the path to the file, the content type of the file, and the name of the file. The proper MIME type for a Microsoft Word DOCX file is
application/vnd.openxmlformats-officedocument.wordprocessingml.document
The File()
method has multiple overloads and accepts the following parameters:
• filename
—The path to the file to download.
• contentType
—The MIME type of the file to download.
• fileDownloadName
—The name of the file as it appears in the browser dialog.
• fileContents
—Instead of providing the path to the file to download, you can provide the actual file contents as a byte array.
• fileStream
—Instead of providing the path to the file to download, you can provide the actual file contents as a file stream.
The default algorithm for how the ASP.NET MVC framework invokes actions is quite simple. If you type /Product/Details
, for example, the Details()
method of the ProductController
class is executed.
However, things can quickly become more complicated. What happens when you have multiple methods with the same name? How do you invoke an action when posting form data but not otherwise? How do you invoke a particular action when an Ajax request is made?
In this section, you learn how to use the AcceptVerbs
, ActionName
, and ActionMethodSelector
attributes to specify when a particular action gets invoked.
The AcceptVerbs
attribute enables you to prevent an action from being invoked unless a particular HTTP operation is performed. For example, you can use the AcceptVerbs
attribute to prevent an action from being invoked unless an HTTP POST
operation is performed.
The Employee
controller in Listing 3.10 exposes two actions named Create()
. The first Create()
action is used to display an HTML form for creating a new employee. The second Create()
action inserts the new employee into the database.
Both Create()
methods are decorated with the AcceptVerbs
attribute. The first Create()
action can be invoked only by an HTTP GET
operation and the second Create()
action can be invoked only by an HTTP POST
operation.
Most people are familiar with HTTP GET
and HTTP POST
operations. You perform an HTTP GET
operation whenever you request a page from a website by typing the address of the page in your web browser. You perform an HTTP POST
operation when you submit an HTML form that has a method="post"
attribute.
Most people don’t realize that the HTTP protocol supports a number of additional types of HTTP operations:
• OPTIONS
—Returns information about the communication options available
• GET
—Returns whatever information is identified by the request
• HEAD
—Performs the same operation as GET
without returning the message body
• POST
—Posts new information or updates existing information
• PUT
—Posts new information or updates existing information
• TRACE
—Performs a message loop back
• CONNECT
—Used for SSL tunneling
The HTTP operations are defined as part of the HTTP 1.1 standard that you can read about at www.w3.org/Protocols/rfc2616/rfc2616-sec9.html.
You can perform these additional HTTP operations when performing Ajax requests. The controller in Listing 3.10 includes a Delete()
action that can be invoked only with an HTTP DELETE
operation. The view in Listing 3.11 includes a delete link that uses Ajax to perform an HTTP DELETE
operation.
In Listing 3.11, the Ajax.ActionLink()
helper renders a link that performs an HTTP DELETE
operation. The link deletes the employee with ID 39. You can verify that the link performs an HTTP DELETE
operation in Firebug (see Figure 3.5).
Firebug is an essential tool for debugging Ajax applications. Firebug is a Mozilla Firefox extension that you can download from http://getFirebug.com.
The ActionName
attribute enables you to expose an action with a different name than its method name. There are two situations in which the ActionName
attribute is useful.
First, when a controller has overloaded methods, you can use the ActionName
attribute to distinguish the two methods. In other words, you can use the ActionName
attribute to expose two methods with the same name as actions with different names.
For example, imagine that you have created a Product
controller that has two overloaded methods named Details()
. The first Details()
method accepts an id
parameter, and the second Details()
method does not. In that case, you can use the ActionName
attribute to distinguish the two Details()
methods by exposing the two Details()
methods with different action names.
Second, using the ActionName
attribute is useful when a controller has methods with different names and you want to expose these methods as actions with the same name. For example, the controller in Listing 3.12 exposes two actions named Edit()
that accept the same parameter.
You can’t have two methods with the same name and the same parameters in the same class. However, you can have two actions that have the same name and the same parameters.
The two Edit()
actions in Listing 3.12 are distinguished by the AcceptVerbs
attribute. The first Edit()
action can be invoked only by an HTTP GET
operation, and the second Edit()
action can be invoked only by an HTTP POST
operation. The ActionName
attribute enables you to expose these two actions with the same name.
You can build your own attributes that you can apply to controller actions to control when the controller actions are invoked. You build your own attributes by deriving a new attribute from the abstract ActionMethodSelectorAttribute
class.
This is an extremely simple class. It has a single method that you must implement named IsValidForRequest()
. If this method returns false, the action method won’t be invoked.
You can use any criteria that you want when implementing the IsValidForRequest()
method including the time of day, a random number generator, or the current temperature outside. The AjaxMethod
attribute in Listing 3.13 is a more practical sample of how you can use the ActionMethod
attribute. This attribute prevents a method from being called in cases in which the request is not an Ajax request.
The selector in Listing 3.13 simply returns the value of the IsAjaxRequest()
method as its selection criterion.
The controller in Listing 3.14 illustrates how you can use the AjaxMethod
attribute.
The controller in Listing 3.14 exposes two actions named Index()
. The first Index()
action is intended to be invoked by a normal browser request. The second action is intended to be invoked by an Ajax request.
The AjaxMethod
attribute is applied to the second Index()
action. If this action were not decorated with the AjaxMethod
attribute, you would get an Ambiguous Match exception because the ASP.NET MVC framework could not decide which of the two actions to execute (see Figure 3.6).
The view in Listing 3.15 uses the Ajax.ActionLink()
helper method to render a Get News link for displaying the news. If you use an uplevel browser—a browser that supports basic JavaScript—then clicking the link performs an Ajax request against the server. The Index()
method decorated with the AjaxMethod
attribute is invoked, and the page is updated without performing a postback.
If, on the other hand, you use a downlevel browser—a browser that does not support basic JavaScript—then clicking the Get News link performs a normal postback. The page still gets updated with a news item, but the user must undergo the awful experience of a postback (see Figure 3.7).
A controller has a special method named HandleUnknownAction()
. This method is called automatically when a controller cannot find an action that matches a browser request. For example, if you request the URL /Product/DoSomethingCrazy and the Product
controller does not have an action named DoSomethingCrazy()
, then the Product
controller HandleUnknownAction()
method is invoked.
By default, this method throws a 404 Resource Not Found HTTP exception. However, you can override this method and do anything you want. For example, the controller in Listing 3.16 displays a custom error message.
If you request the URL /Catalog/Create or /Catalog/Delete, then the Catalog
controller returns the Create or Delete view. If you request a URL that contains an unknown action such as /Catalog/Wow or /Catalog/Eeeks, then the HandleUnknownAction()
method executes.
In Listing 3.16, the HandleUnknownAction()
method adds the name of the action to view data and then renders a view named Unknown (see Figure 3.8).
The ASP.NET MVC team worked hard to make sure that controller actions were extremely easy to test. If you want to test a controller action, you simply need to instantiate the controller and call the action method.
For example, the controller in Listing 3.17 exposes two actions named Index()
and Details()
. If you invoke the Details()
action without passing a value for the id
parameter, you should be redirected to the Index()
action.
When returning a view, you must be explicit about the view name, or you can’t verify the name of the view in a unit test. For example, in Listing 3.17, the Index()
method returns View("Index")
and not View()
.
The unit tests in Listing 3.18 illustrate how you can test the actions exposed by the Person
controller. The first unit test, named DetailsWithId()
, verifies that calling the Details()
method with a value for the id
parameter returns the Details view.
The second unit test, named DetailsWithoutId()
, verifies that calling the Details()
method with no value for the id
parameter causes a RedirectToRouteResult
to be returned that redirects to the Index view.
To learn more about creating and running unit tests, see Appendix B, “Using a Unit Testing Framework,” in this book.
This chapter was devoted to the topic of ASP.NET MVC controllers. The goal of this chapter was to provide an in-depth explanation of how you can create controllers and controller actions.
In the first part of this chapter, you were provided with an overview of the different types of ActionResults
that can be returned from a controller action. You learned how to returns views, redirect users to other actions, return JSON, and return downloadable files.
Next, we examined the different attributes that you can apply to a controller action to control when the controller action is invoked. You learned how to use the AcceptVerbs
and ActionName
attributes. You also learned how to create a custom ActionSelect
attribute that enables you to execute an action only within the context of an Ajax request.
Finally, you learned how to build unit tests for your controllers. You learned how to test whether a controller returns different ActionResults
such as a ViewResult
or a RedirectToRouteResult
.
18.116.69.53