Chapter 3. Understanding Controllers and Actions

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.

Creating a Controller

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.

Figure 3.1. The Add Controller dialog

image

Warning

A controller name must end with the suffix Controller. If you forget to include the Controller suffix, you can’t invoke the controller.

Listing 3.1. ControllersProductController.cs (C#)

image

Listing 3.1. ControllersProductController.vb (VB)

image

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.

Warning

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().

Listing 3.2. ControllersProductController.cs with Additional Methods (C#)

image

Listing 3.2. ControllersProductController.vb with Additional Methods (VB)

image

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.

Note

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.”

Returning Action Results

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.

Note

We examine partial view results (AKA view user controls or partials) in Chapter 10, “Understanding View Master Pages and View User Controls.”

Returning a View Result

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.

Listing 3.3. ControllersCustomerController.cs (C#)

image

Listing 3.3. ControllersCustomerController.vb (VB)

image

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.

Listing 3.4. ControllersCustomerController.cs with Explicit View (C#)

image

Listing 3.4. ControllersCustomerController.vb with Explicit View (VB)

image

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.

Tip

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.”

Returning a Redirect Result

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.

Listing 3.5. ControllersWidgetController.cs (C#)

image

Listing 3.5. ControllersWidgetController.cs (VB)

image

Note

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.

Note

The RedirectToAction() method returns a 302 Found HTTP status code to the browser to perform the redirect to the new action. One advantage of performing a browser redirect is that it updates the browser address bar with the new URL. The disadvantage is that a browser must do a second request.

Returning a Content Result

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).

Figure 3.2. Results of invoking Say() action

image

Listing 3.6. ControllersHelloController.cs (C#)

image

Listing 3.6. ControllersHelloController.vb (VB)

image

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#)

image


(VB)

image

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)

Returning a JSON Result

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.

Note

You can learn more about JSON by visiting JSON.org.

You return JSON from an action by calling the Json() method. For example, the controller in Listing 3.7 returns a collection of quotations.

Listing 3.7. ControllersQuotationController.cs (C#)

image

Listing 3.7. ControllersQuotationController.vb (VB)

image


Note

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.

Figure 3.3. Using JSON to retrieve quotations

image

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.

Listing 3.8. ViewsQuotationIndex.aspx

image

Note

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)

Returning a File Result

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).

Figure 3.4. Downloading a file

image

Listing 3.9. ControllersContentManagerController.cs (C#)

image

Listing 3.9. ControllersContentManagerController.vb (VB)

image


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.

Note

The File() method uses the HTTP Content-Disposition header to set the file download name.

Controlling How Actions Are Invoked

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.

Using AcceptVerbs

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.

Listing 3.10. ControllersEmployeeController.cs (C#)

image

image


Listing 3.10. ControllersEmployeeController.vb (VB)

image


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

DELETE—Deletes information

TRACE—Performs a message loop back

CONNECT—Used for SSL tunneling

Note

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.

Listing 3.11. ViewsEmployeeDelete.aspx (C#)

image


Listing 3.11. ViewsEmployeeDelete.aspx (VB)

image


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).

Figure 3.5. Performing an HTTP DELETE operation

image

Note

Firebug is an essential tool for debugging Ajax applications. Firebug is a Mozilla Firefox extension that you can download from http://getFirebug.com.

Using ActionName

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.

Listing 3.12. ControllersMerchandiseController.cs (C#)

image


Listing 3.12. ControllersMerchandiseController.vb (VB)

image


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.

Using ActionMethodSelector

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.

Listing 3.13. SelectorsAjaxMethodAttribute.cs (C#)

image


Listing 3.13. SelectorsAjaxMethodAttribute.vb (VB)

image


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.

Listing 3.14. ControllersNewsController.cs (C#)

image


Listing 3.14. ControllersNewsController.vb (VB)

image


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).

Figure 3.6. An Ambiguous Match exception

image

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).

Figure 3.7. Displaying the news

image

Listing 3.15. ViewsNewsIndex.aspx (C#)

image


Listing 3.15. ViewsNewsIndex.aspx (VB)

image


Handling Unknown Actions

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.

Listing 3.16. ControllersCatalogController.cs (C#)

image


Listing 3.16. ControllersCatalogController.vb (VB)

image


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).

Figure 3.8. Displaying the Unknown view

image

Testing Controllers and Actions

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.

Listing 3.17. ControllersPersonController.cs (C#)

image


Listing 3.17. ControllersPersonController.vb (VB)

image


Warning

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.

Listing 3.18. ControllersPersonControllerTest.cs (C#)

image


Listing 3.18. ControllersPersonControllerTest.vb (VB)

image


Note

To learn more about creating and running unit tests, see Appendix B, “Using a Unit Testing Framework,” in this book.

Summary

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.116.69.53