Advanced Controllers

As the workhorse of the ASP.NET MVC stack, it's no surprise that the controller has a lot of advanced features that were way beyond the scope of Chapter 2. In this section, you'll learn both how the controller internals work and how you can use it in some advanced scenarios.

Defining the Controller: The IController Interface

Now that you have the basics down, we'll take a more structured look at exactly how controllers are defined and used. Up to this point, we've kept thing simple by focusing on what a controller does; now it's time to look at what a controller is. To do that, you'll need to understand the IController interface. As discussed in Chapter 1, among the core focuses of ASP.NET MVC are extensibility and flexibility. When building software this way, it's important to leverage abstraction as much as possible by using interfaces.

For a class to be a controller in ASP.NET MVC, it must at minimum implement the IController interface, and by convention the name of the class must end with the suffix Controller. The naming convention is actually quite important — and you'll find that many of these small rules are in play with ASP.NET MVC, which will make your life just a little bit easier by not making you define configuration settings and attributes. Ironically, the IController interface is quite simple given the power it is abstracting:

public interface IController {    void Execute(RequestContext requestContext);}

It's a simple process really: When a request comes in, the Routing system identifies a controller, and it calls the Execute method. Let's look at a quick example (which assumes that you are using the default project template and thus have a standard route already configured):

1. Create a new MVC 3 application using the Internet Application template and add a new class in the Controllers folder.

note

This should be a normal class file, not a new controller named SimpleController.

2. Implement IController by adding IController after the class name and then press Ctrl+. (period) to implement the interface methods (this will stub out the Execute method for you). In the Execute method, have it simply write out Hello World as the response (it's not exactly groundbreaking, but it demonstrates how to write the simplest possible controller):

using System.Web.Mvc;using System.Web.Routing;public class SimpleController : IController {    public void Execute(RequestContext requestContext)    {        var response = requestContext.HttpContext.Response;        response.Write("<h1>Hello World!</h1>");    }}

3. Press Ctrl+F5 to compile the code and start your browser.

4. In the address bar, you'll need to navigate to /simple. Figure 14.7 shows the result.

Apart from the large font, this is not exactly breathtaking, but overall the process is pretty simple.

The point of the IController interface is to provide a very simple starting point for anyone who wants to hook in their own Controller framework into ASP.NET MVC. The Controller class, which is covered later in this chapter, layers much more interesting behavior on top of this interface. This is a common extensibility pattern within ASP.NET.

For example, if you're familiar with HTTP handlers, you might have noticed that the IController interface looks very similar to IHttpHandler:

public interface IHttpHandler{    void ProcessRequest(HttpContext context);    bool IsReusable { get; }}

Ignoring the IsReusable property for a moment, IController and IHttpHandler are pretty much equivalent in terms of responsibility. The IController.Execute and IHttpHandler.ProcessRequest methods both respond to a request and write some output to a response. The main difference between the two is the amount of contextual information provided to the method. The IController.Execute method receives an instance of RequestContext, which includes not just the HttpContext but also other information relevant to a request for ASP.NET MVC.

The Page class, which is probably the class most familiar to ASP.NET Web Forms developers because it is the default base class for an ASPX page, also implements IHttpHandler.

The ControllerBase Abstract Base Class

Implementing IController is pretty easy, as you've seen, but really all it's doing is providing a facility for Routing to find your controller and call Execute. This is the most basic hook into the system that you could ask for, but overall it provides little value to the controller you're writing. This may be a good thing to you — many custom tool developers don't like it when a system they're trying to customize imposes a lot of restrictions. Others may like to work a bit closer with the API, and for that there is ControllerBase.

Product Team Aside

UnFigure

The ASP.NET MVC product team debated removing the IController interface completely. Developers who wanted to implement that interface could implement their own implementation of MvcHandler instead, which decidedly handles a lot of the core execution mechanics based on the request coming in from Routing.

We decided to leave it in, however, because other features of the ASP.NET MVC framework (IControllerFactory and ControllerBuilder) can work with the interface directly — which provides added value to developers.

The ControllerBase class is an abstract base class that layers a bit more API surface on top of the IController interface. It provides the TempData and ViewData properties (which are ways of sending data to a view, discussed in Chapter 3), and the Execute method of ControllerBase is responsible for creating the ControllerContext, which provides the MVC-specific context for the current request much the same way that an instance of HttpContext provides the context for ASP.NET in general (providing request and response, URL, and server information, among elements).

This base class is still very lightweight and enables developers to provide extremely customized implementations for their own controllers, while benefiting from the action filter infrastructure in ASP.NET MVC (ways of filtering and working with request/response data, which are discussed in Chapter 13). What it doesn't provide is the ability to convert actions into method calls. That's where the Controller class comes in.

The Controller Class and Actions

In theory, you could build an entire site with classes that implement ControllerBase or IController, and it would work. Routing would look for an IController by name and then call Execute, and you would have yourself a very, very basic website.

This approach, however, is akin to working with ASP.NET using raw HttpHandlers — it would work, but you're left to reinvent the wheel and plumb the core framework logic yourself.

Interestingly, ASP.NET MVC itself is layered on top of HTTP handlers as you'll see later, and overall there was no need to make internal plumbing changes to ASP.NET to implement MVC. Instead, the ASP.NET MVC team layered this new framework on top of existing ASP.NET extensibility points.

The standard approach to writing a controller is to have it inherit from the System.Web.Mvc.Controller abstract base class, which implements the ControllerBase base class, and thus the IController interface. The Controller class is intended to serve as the base class for all controllers, because it provides a lot of nice behaviors to controllers that derive from it.

The relationship between IController, ControllerBase, the Controller abstract base class, and the two controllers which are included in a default ASP.NET MVC 3 application are shown in Figure 14.8.

Action Methods

All public methods of a class that derive from Controller are action methods, which are potentially callable via an HTTP request. Rather than one monolithic implementation of Execute, you can factor your controller into action methods, each of which responds to a specific user input.

Product Team Aside

UnFigure

Upon reading that every public method of your Controller class is publicly callable from the Web, you might have a gut reaction concerning the security of such an approach. The product team had a lot of internal and external debate concerning this.

Originally, each action method required that an attribute, ControllerActionAttribute, be applied to each callable method. However, many felt this violated the DRY principle (Don't Repeat Yourself). It turns out that the concern over these methods being web-callable has to do with a disagreement of what it means to opt in.

As far as the product team is concerned, multiple levels of opting in exist before a method is web-callable. The first level that you need to have opted in to is an ASP.NET MVC project. If you add a public Controller class to a standard ASP.NET Web Application project, that class is not going to suddenly be web-callable (although adding such a class to an ASP.NET MVC project is likely to make it callable). You would still need to define a route with a route handler (such as the MvcRouteHandler) that corresponds to that class.

The general consensus here is that by inheriting from Controller, you've opted in to this behavior. You can't do that by accident. And even if you did, you would still have to define routes that correspond to that class.

Let's walk through another simple controller example, but this time you'll add a public method.

For this example, follow these steps:

1. Open up the previous example and create a new controller by right-clicking the Controllers folder and selecting Add ⇒ Controller.

2. Name it Simple2Controller.

3. Replace the generated code with the following:

using System.Web.Mvc;public class Simple2Controller : Controller {    public void Hello()     {        Response.Write("<h1>Hello World Again!</h1>");    }}

4. Press Ctrl+F5 (or Debug ⇒ Run) and navigate to /simple2/hello in the browser. See Figure 14.9.

As before, this is not exactly breathtaking, but it is a bit more interesting. Notice that the URL in the address bar directly correlates to the action method of your controller. If you recall from the example earlier in this chapter, the default route for MVC breaks URLs into three main components: /{controller}/{action}/{id}. Let's look at how that applies to this example.

The simple2 portion of the URL corresponds to the controller name. The MVC framework appends the Controller suffix to the controller name and locates your Controller class, Simple2Controller.

/simple2/hello

The last portion of the URL corresponds to the action. The framework locates a public method with this name and attempts to call the method.

Working with Parameters

You can add any number of public methods (which we'll call actions from here on out to keep with convention) to a Controller class, which will all be callable via this pattern. Actions may also contain parameters. Going back to the previous example, add a new action method that takes in a parameter:

public class Simple2Controller : Controller {    public void Goodbye(string name)     {        Response.Write("Goodbye"  + HttpUtility.HtmlEncode(name));    }}

This method is callable via the URL:

/simple2/goodbye?name=World

Notice that you can pass in parameters to an action method by name via the query string. You can also pass in parameters via the URL segments, discoverable by position as defined in your routes (discussed in Chapter 4). For example, the following URL is more aesthetically pleasing to many developers and Internet users:

/simple2/goodbye/world

which provides more information about what you're looking at.

Product Team Aside

UnFigure

Many developers would also consider the second approach to be more search engine–friendly, but this isn't necessarily the case. Modern search engines do read the query string, and in this example, the URL with the query string actually provides more information.

Usually, when we're talking about optimizing for search engine (Search Engine Optimization, or SEO) issues surrounding URLs, we're talking about URLs that pass in opaque identifiers in the query string such as:

/products/view.aspx?id=45434

which tells us nothing compared to:

/products/view/shoes

Working with parameters passed by URL segment requires you to define how Routing will identify these parameters in the URL. Fortunately, the default route (created for you when you click File ⇒ New) is already set up for you and contains a pretty common URL pattern: {controller}/{action}/{id}.

Changing the action method signature a little bit (by renaming the parameter name to id) like so:

public class Simple2Controller : Controller {    public void Goodbye(string id)     {        Response.Write("Goodbye"  + HttpUtility.HtmlEncode(id));    }}

allows you to call that method using the cleaner URL, and Routing will pass the parameter by structured URL instead of a query string parameter:

 /simple2/goodbye/world

Working with Multiple Parameters

What if you have a method with more than one parameter? This is a very common scenario, and rest assured that you can still use query strings, but if you want to pass both parameters via the URL segments, you'll need to define a new route for this situation.

For example, suppose that you have an action method that calculates the distance between two points on a two-dimensional plane:

public void Distance(int x1, int y1, int x2, int y2) {    double xSquared = Math.Pow(x2 - x1, 2);    double ySquared = Math.Pow(y2 - y1, 2);    Response.Write(Math.Sqrt(xSquared + ySquared));}

Using the default MVC route, the request would need to look like this:

/simple2/distance?x2=1&y2=2&x1=0&y1=0

You can improve on this situation a bit by defining a route that allows you to specify the parameters in a cleaner format. This code goes inside the RegisterRoutes methods within the global.asax.cs file, and uses the MapRoute method (discussed in Chapter 4) to define a new route:

routes.MapRoute("distance",    "simple2/distance/{x1},{y1}/{x2},{y2}",    new { Controller = "Simple2", action = "Distance" });

Notice that you are using the comma character to separate x and y coordinates. Now this action method is callable via the URL:

/simple2/distance/0,0/1,2

The presence of commas in a URL might look strange, but routing is quite powerful! For more on routing, refer to Chapter 9.

Default Parameters

ASP.NET MVC simplifies this case with default parameters, using the DefaultValueAttribute from the System.ComponentModel namespace. The DefaultValueAttribute lets you specify a parameter value that the controller action will use if it's not contained in the route values.

The controller action in the following listing will respond to both /Dinners/DinnersNearMe/90210 and /Dinners/DinnersNearMe/90210?maxDinners=50. In the first case, the default value of 10 will be used:

public ActionResult DinnersNearMe(string location,     [DefaultValue(10)]int maxDinners) {}

Even better, you can use language support for optional parameters to eliminate the need for the [DefaultValue] attribute. Visual Basic has long had support for optional parameters, and C# 4.0 adds support for optional parameters as well. That allows you to simplify the controller action signature as follows:

public ActionResult DinnersNearMe(string location, int maxDinners = 10) {}

The ActionResult

In the previous action method examples, the action methods wrote text directly to the HTTP response using Response.Write. Though this is certainly a valid way to produce an HTTP response, it isn't the most efficient; it also defeats some of the neat features of ASP.NET such as Razor Layouts!

As mentioned before, the purpose of the controller within the MVC pattern is to respond to user input. In ASP.NET MVC, the action method is the granular unit of response to user input. The action method is ultimately responsible for handling a user request and outputting the response that is displayed to the user, which is typically HTML.

The pattern that an action method follows is to do whatever work is asked of it, and at the end, return an instance of a type that derives from the ActionResult abstract base class.

Taking a quick look at the source for the ActionResult abstract base class, you see:

public abstract class ActionResult{   public abstract void ExecuteResult(ControllerContext context);}

Notice that the class contains a single method, ExecuteResult. If you're familiar with the Command Pattern, this should look familiar to you. Action results represent commands that your action method wants the framework to perform on its behalf.

Action results generally handle framework-level work, while the action method handles your application logic. For example, when a request comes in to display a list of products, your action method will query the database and put together a list of the appropriate products to show. Perhaps it needs to perform some filtering based on business rules within your app. At this point, your action method is completely focused on application logic.

However, once the method is ready to display the list of products to the user, you may not want your code, which is focused on view logic, to have to worry about implementation details provided by the framework such as writing to the HTTP response directly. Perhaps you have a template defined that knows how to format a collection of products as HTML. You'd rather not have that information encapsulated in the action method because it would violate the separation of concerns the authors have so carefully cultivated up until this point.

One technique you have at your disposal is to have the action method return a ViewResult (which derives from ActionResult) and give the data to that instance, and then return that instance. At that point, your action method is done with its work, and the action invoker will call the ExecuteResult method on that ViewResult instance, which does the rest. Here's what the code might look like:

public ActionResult ListProducts(){  //Pseudo code  IList<Product> products = SomeRepository.GetProducts();  ViewData.Model = products;  return new ViewResult {ViewData = this.ViewData };}

In practice, you'll probably never see code that instantiates an ActionResult instance directly like that. Instead, you would use one of the helper methods of the Controller class such as the View method like so:

public ActionResult ListProducts(){  //Pseudo code  IList<Product> products = SomeRepository.GetProducts();  return View(products);}

The next chapter covers the ViewResult in more depth and tells how it relates to views.

Action Result Helper Methods

If you take a close look at the default controller actions in the default ASP.NET MVC project template, you'll notice that the action methods don't directly instantiate instances of ViewResult. For example, here's the code for the About method:

public ActionResult About() {    ViewData["Title"] = "About Page";    return View();}

Notice that it returns the result of a call to the View method. The Controller class contains several convenience methods for returning ActionResult instances. These methods are intended to help make action method implementations a bit more readable and declarative. Instead of creating new instances of action results, it is more common to return the result of one of these convenience methods.

These methods are generally named after the action result type that they return, with the Result suffix omitted. Hence the View method returns an instance of ViewResult. Likewise, the Json method returns an instance of JsonResult. The one exception in this case is the RedirectToAction method, which returns an instance of RedirectToRoute.

Table 14.1 lists the existing methods and which types they return.

Table 14.1: Controller Convenience Methods That Return ActionResult Instances

MethodDescription
RedirectReturns a RedirectResult, which redirects the user to the appropriate URL.
RedirectToActionReturns a RedirectToRouteResult, which redirects the user to an action using the supplied route values.
RedirectToRouteReturns a RedirectToRouteResult, which redirects the user to the URL that matches the specified route values.
ViewReturns a ViewResult, which renders the View to the response.
PartialViewReturns a PartialViewResult, which renders a partial View to the response.
ContentReturns a ContentResult, which writes the specified content (string) to the response.
FileReturns a class that derives from FileResult, which writes binary content to the response.
JsonReturns a JsonResult containing the output from serializing an object to JSON.
JavaScriptReturns a JavaScriptResult containing JavaScript code that is immediately executed when returned to the client.

Action Result Types

ASP.NET MVC includes several ActionResult types for performing common tasks. These are listed in Table 14.2. Each type is discussed in more detail in the sections that follow.

Table 14.2: Descriptions of ActionResult Types

ActionResult TypeDescription
ContentResultWrites the specified content directly to the response as text.
EmptyResultRepresents a null or empty response. It doesn't do anything.
FileContentResultDerives from FileResult and writes a byte array to the response.
FilePathResultDerives from FileResult and writes a file to the response based on a file path.
FileResultServes as the base class for a set of results that writes a binary response to the stream. Useful for returning files to the user.
FileStreamResultDerives from FileResult and writes a stream to the response.
HttpNotFoundDerives from HttpStatusCodeResult. Returns an HTTP 404 response code to the client, indicating that the requested resource is not found.
HttpStatusCodeResultReturns a user-specified HTTP code.
HttpUnauthorizedResultDerives from HttpStatusCodeResult. Returns an HTTP 401 response code to the client, indicating that the requestor does not have authorization to the resource at the requested URL.
JavaScriptResultUsed to execute JavaScript code immediately on the client sent from the server.
JsonResultSerializes the objects it is given into JSON and writes the JSON to the response, typically in response to an Ajax request.
PartialViewResultThis is similar to ViewResult, except it renders a partial View to the response, typically in response to an Ajax request.
RedirectResultRedirects the requestor to another URL by returning either a temporary redirect code 302 or permanent redirect code 301 depending upon a Boolean Permanent flag.
RedirectToRouteResultSimilar to RedirectResult, but redirects the user to a URL specified via Routing parameters.
ViewResultCalls into a View engine to render a View to the response.

ContentResult

The ContentResult writes its specified content (via the Content property) to the response. This class also allows for specifying the content encoding (via the ContentEncoding property) and the content type (via the ContentType property).

If the encoding is not specified, the content encoding for the current HttpResponse instance is used. The default encoding for HttpResponse is specified in the globalization element of web.config.

Likewise, if the content type is not specified, the content type set on the current HttpResponse instance is used. The default content type for HttpResponse is text/html.

EmptyResult

As the name implies, the EmptyResult is used to indicate that the framework should do nothing. This follows a common design pattern known as the Null Object pattern, which replaces null references with an instance. In this instance, the ExecuteResult method has an empty implementation. This design pattern was introduced in Martin Fowler's Refactoring book. You can learn more at http://martinfowler.com/bliki/refactoring.html.

FileResult

The FileResult is very similar to the ContentResult except that it is used to write binary content (for example, a Microsoft Word document on disk or the data from a blob column in SQL Server) to the response. Setting the FileDownloadName property on the result will set the appropriate value for the Content-Disposition header, causing a file download dialog to appear for the user.

Note that FileResult is an abstract base class for three different file result types:

  • FilePathResult
  • FileContentResult
  • FileStreamResult

Usage typically follows the factory pattern in which the specific type returned depends on which overload of the File method (discussed later) is called.

HttpStatusCodeResult

The HttpStatusCodeResult provides a way to return an action result with a specific HTTP response status code and description. For example, to notify the requestor that a resource is permanently unavailable, you could return a 410 (Gone) HTTP status code. Suppose you'd made the firm decision that your store would stop carrying disco albums. You could update your StoreController Browse action to return a 410 if a user searched for disco:

public ActionResult Browse(string genre) {       if(genre.Equals("disco",StringComparison.InvariantCultureIgnoreCase))             return new HttpStatusCodeResult(410);       var genreModel = new Genre { Name = genre };    return View(genreModel); }

Note that there are five specific ActionResults based on common HTTP Status Codes, which were previously described in the Table 14.2:

  • HttpNotFoundResult
  • HttpStatusCodeResult
  • HttpUnauthorizedResult
  • RedirectResult
  • RedirectToRouteResult

Both RedirectResult and RedirectToRouteResult (described later in this section) are based on the common HTTP 302 response code.

JavaScriptResult

The JavaScriptResult is used to execute JavaScript code on the client sent from the server. For example, when using the built-in Ajax helpers to make a request to an action method, the method could return a bit of JavaScript that is immediately executed when it gets to the client:

public ActionResult DoSomething() {    script s = "$(‘#some-div’).html(‘Updated!’);";    return JavaScript(s);}

This would be called by the following code:

    <%: Ajax.ActionLink("click", "DoSomething", new AjaxOptions()) %>    <div id="some-div"></div>

This assumes that you've referenced the Ajax libraries and jQuery.

JsonResult

The JsonResult uses the JavaScriptSerializer class to serialize its contents (specified via the Data property) to the JSON (JavaScript Object Notation) format. This is useful for Ajax scenarios that have a need for an action method to return data in a format easily consumable by JavaScript.

As for ContentResult, the content encoding and content type for the JsonResult can both be set via properties. The only difference is that the default ContentType is application/json and not text/html for this result.

Note that the JsonResult serializes the entire object graph. Thus, if you give it a ProductCategory object, which has a collection of 20 Product instances, every Product instance will also be serialized and included in the JSON sent to the response. Now imagine if each Product had an Orders collection containing 20 Order instances. As you can imagine, the JSON response can grow huge quickly.

There is currently no way to limit how much to serialize into the JSON, which can be problematic with objects that contain a lot of properties and collections, such as those typically generated by LINQ to SQL. The recommended approach is to create a type that contains the specific information you want included in the JsonResult. This is one situation in which an anonymous type comes in handy.

For example, in the preceding scenario, instead of serializing an instance of ProductCategory, you can use an anonymous object initializer to pass in just the data you need, as the following code sample demonstrates:

public ActionResult PartialJson(){       var category = new ProductCategory { Name="Partial"};       var result = new {              Name = category.Name,             ProductCount = category.Products.Count       };       return Json(result);}
note

Rather than instantiating a JsonResult directly, this method uses the JSON helper method. Helper methods are covered later in this chapter.

In this example, all you needed was the category name and the product count for the category. Rather than serializing the entire object graph, you pulled the information you needed from the actual object and stored that information in an anonymous type instance named result. You then sent that instance to the response, rather than the entire object graph.

RedirectResult

The RedirectResult performs an HTTP redirect to the specified URL (set via the Url property). Internally, this result calls the HttpResponse.Redirect method, which sets the HTTP status code to HTTP/1.1 302 Object Moved, causing the browser to immediately issue a new request for the specified URL.

Technically, you could just make a call to Response.Redirect directly within your action method, but using the RedirectResult defers this action until after your action method finishes its work. This is useful for unit testing your action method and helps keep underlying framework details outside of your action method.

RedirectToRouteResult

RedirectToRouteResult performs an HTTP redirect in the same manner as the RedirectResult, but instead of specifying a URL directly, this result uses the Routing API to determine the redirect URL.

Note that there are two convenience methods (defined in Table 14.1) that return a result of this type: RedirectToRoute and RedirectToAction.

ViewResult

The ViewResult is the most widely used action result type. It calls the FindView method of an instance of IViewEngine, returning an instance of IView. The ViewResult then calls the Render method on the IView instance, which renders the output to the response. In general, this inserts the specified view data (the data that the action method has prepared to be displayed in the view) into a view template that formats the data for displaying.

PartialViewResult

PartialViewResult works in exactly the same way that ViewResult does, except that it calls the FindPartialView method to locate a view rather than FindView. It's used to render partial views and is useful in partial update scenarios when using Ajax to update a portion of the page with new HTML.

Implicit Action Results

One constant goal with ASP.NET MVC, and software development in general, is to make the intentions of the code as clear as possible. There are times when you have a very simple action method only intended to return a single piece of data. In this case, it is helpful to have your action method signature reflect the information that it returns.

To highlight this point, consider a Distance method which calculates the distance between two points. This action could write directly to the response — as shown in the first controller actions in Chapter 2, in the section titled “Writing Your First (Outrageously Simple) Controller.” However, an action that returns a value can also be written as follows:

public double Distance(int x1, int y1, int x2, int y2){    double xSquared = Math.Pow(x2 - x1, 2);    double ySquared = Math.Pow(y2 - y1, 2);    return Math.Sqrt(xSquared + ySquared);}

Notice that the return type is a double and not a type that derives from ActionResult. This is perfectly acceptable. When ASP.NET MVC calls that method and sees that the return type is not an ActionResult, it automatically creates a ContentResult containing the result of the action method and uses that internally as the ActionResult.

One thing to keep in mind is that the ContentResult requires a string value, so the result of your action method needs to be converted to a string first. To do this, ASP.NET MVC calls the ToString method on the result, using InvariantCulture, before passing it to the ContentResult. If you need to have the result formatted according to a specific culture, you should explicitly return a ContentResult yourself.

In the end, the preceding method is roughly equivalent to the following method:

public ActionResult Distance(int x1, int y1, int x2, int y2) {    double xSquared = Math.Pow(x2 - x1, 2);    double ySquared = Math.Pow(y2 - y1, 2);    double distance = Math.Sqrt(xSquared + ySquared);    return Content(Convert.ToString(distance, CultureInfo.InvariantCulture));}

The advantages of the first approach are that it makes your intentions clearer, and the method is easier to unit test.

Table 14.3 highlights the various implicit conversions you can expect when writing action methods that do not have a return type of ActionResult.

Table 14.3: Implicit Conversions with Action Methods

Return ValueDescription
NullThe action invoker replaces null results with an instance of EmptyResult. This follows the Null Object Pattern. As a result, implementers writing custom action filters don't have to worry about null action results.
VoidThe action invoker treats the action method as if it returned null, and thus an EmptyResult is returned.
Other objects that don't derive from ActionResultThe action invoker calls ToString using InvariantCulture on the object and wraps the resulting string in a ContentResult instance.
note

The code to create a ContentResult instance is encapsulated in a virtual method on the action invoker called CreateActionResult. For those who want to return a different implicit action result type, you can write a customer action invoker that derives from ControllerActionInvoker and override that method.

One example might be to have return values from action methods automatically

be wrapped by a JsonResult.

Action Invoker

We've made several references in this chapter to the action invoker without giving any details about it. Well, no more arm waving! This section covers the role of a critical element in the ASP.NET MVC request processing chain: the thing that actually invokes the action you're calling — the action invoker. When we first defined the controller earlier in this chapter, we looked at how Routing maps a URL to an action method on a Controller class. Diving deeper into the details, you learned that routes themselves do not map anything to controller actions; they merely parse the incoming request and populate a RouteData instance stored in the current RequestContext.

It's the ControllerActionInvoker, set via the ActionInvoker property on the Controller class that is responsible for invoking the action method on the controller based on the current request context. The invoker performs the following tasks:

  • It locates the action method to call.
  • It gets values for the parameters of the action method by using the model binding system
  • It invokes the action method and all of its filters.
  • It calls ExecuteResult on the ActionResult returned by the action method. For methods that do not return an ActionResult, the invoker creates an implicit action result as described in the previous section and calls ExecuteResult on that.

In the next section, you'll take a closer look at how the invoker locates an action method.

How an Action Is Mapped to a Method

The ControllerActionInvoker looks in the route values dictionary associated with the current request context for a value corresponding to the action key. As an example, here is the URL pattern for the default route:

{controller}/{action}/{id}

When a request comes in and matches that route, you populate a dictionary of route values (accessible via the RequestContext) based on this route. For example, if a request comes in for:

/home/list/123

Routing adds the value list with a key of action to the route values dictionary.

At this point within the request, an action is just a string extracted from the URL; it is not a method. The string represents the name of the action that should handle this request. Though it may commonly be represented by a method, the action really is an abstraction. There might be more than one method that can respond to the action name. Or it might not even be a method but a workflow or some other mechanism that can handle the action.

The point of this is that, while in the general case an action typically maps to a method, it doesn't have to. We'll see an example of this later in the chapter where we discuss asynchronous actions where there are two methods per action.

Action Method Selection

Once the invoker has determined the action's name, it attempts to identify a method that can respond to that action. By default, the invoker uses reflection to find a public method on a class that derives from Controller that has the same name (case-insensitive) as the current action. Such a method must meet the following criteria:

  • An action method must not have the NonActionAttribute defined.
  • Special methods such as constructors, property accessors, and event accessors cannot be action methods.
  • Methods originally defined on Object (such as ToString) or on Controller (such as Dispose) cannot be action methods.

Like many features of ASP.NET MVC, you can tweak this default behavior to suit any special needs your applications might have.

ActionNameAttribute

Applying the ActionNameAttribute attribute to a method allows you to specify the action that the method handles. For example, suppose that you want to have an action named View. Unfortunately this would conflict with the built-in View method of Controller that's used to return a ViewResult. An easy way to work around this issue is to do the following:

[ActionName("View")]public ActionResult ViewSomething(string id){  return View();}

The ActionNameAttribute redefines the name of this action as View. Thus, this method is invoked in response to requests for /home/view, but not for /home/viewsomething. In the latter case, as far as the action invoker is concerned, an action method named ViewSomething does not exist.

One consequence of this is that if you're using our conventional approach to locate the view that corresponds to this action, the view should be named after the action, not after the method. In the preceding example (assuming that this is a method of HomeController), you would look for the view ∼/Views/Home/View.cshtml by default.

This attribute is not required for an action method. There is an implicit rule that the name of the action method serves as the action name if this attribute is not applied.

ActionSelectorAttribute

You're not done matching the action to a method yet. Once you've identified all methods of the Controller class that match the current action name, you need to whittle the list down further by looking at all instances of the ActionSelectorAttribute applied to the methods in the list.

This attribute is an abstract base class for attributes that provide fine-grained control over which requests an action method can respond to. The API for this method consists of a single method:

public abstract class ActionSelectorAttribute : Attribute{  public abstract bool IsValidForRequest(ControllerContext controllerContext,     MethodInfo methodInfo);}

At this point, the invoker looks for any methods in the list that contain attributes that derive from this attribute and calls the IsValidForRequest method on each attribute. If any attribute returns false, the method that the attribute is applied to is removed from the list of potential action methods for the current request.

At the end, you should be left with one method in the list, which the invoker then invokes. If more than one method can handle the current request, the invoker throws an exception indicating that there is an ambiguity in the method to call. If no method can handle the request, the invoker calls HandleUnknownAction on the controller.

The ASP.NET MVC framework includes two implementations of this base attribute: the AcceptVerbsAttribute and the NonActionAttribute.

AcceptVerbsAttribute

AcceptVerbsAttribute is a concrete implementation of ActionSelectorAttribute that uses the current HTTP request's HTTP method (verb) to determine whether or not a method is the action that should handle the current request. This allows you to have method overloads, both of which are actions but respond to different HTTP verbs.

ASP.NET MVC 2 introduced a more terse syntax for HTTP method restriction with the [HttpGet], [HttpPost], [HttpDelete], and [HttpPut] attributes. These are simple aliases for the previous [AcceptVerbs(HttpVerbs.Get)], [AcceptVerbs(HttpVerbs.Post)], [AcceptVerbs(HttpVerbs.Delete)], and [AcceptVerbs(HttpVerbs.Put)] attributes, but are easier to both type and read.

For example, you may want two versions of the Edit method: one that renders the edit form and the other that handles the request when that form is posted:

[HttpGet]public ActionResult Edit(string id){  return View();}[HttpPost]public ActionResult Edit(string id, FormCollection form){  //Save the item and redirect…}

When a POST request for /home/edit is received, the action invoker creates a list of all methods of the controller that match the edit action name. In this case, you would end up with a list of two methods. Afterward, the invoker looks at all of the ActionSelectorAttribute instances applied to each method and calls the IsValidForRequest method on each. If each attribute returns true, the method is considered valid for the current action.

For example, in this case, when you ask the first method if it can handle a POST request, it will respond with false because it handles only GET requests. The second method responds with true because it can handle the POST request, and it is the one selected to handle the action.

If no method is found that meets these criteria, the invoker will call the HandleUnknownAction method on the controller, supplying the name of the missing action. If more than one action method meeting these criteria is found, an InvalidOperationException is thrown.

Simulating RESTful Verbs

Most browsers support only two HTTP verbs during normal web browsing: GET and POST. However, the REST architectural style also makes use of a few additional standard verbs: DELETE, HEAD, and PUT. ASP.NET MVC allows you to simulate these verbs via the Html.HttpMethodOverride helper method, which takes a parameter to indicate one of the standard HTTP verbs (DELETE, GET, HEAD, POST, and PUT). Internally, this works by sending the verb in an X-HTTP-Method-Override form field.

The behavior of HttpMethodOverride is complemented by the [AcceptVerbs] attribute as well as the new shorter verb attributes:

  • HttpPostAttribute
  • HttpPutAttribute
  • HttpGetAttribute
  • HttpDeleteAttribute

Though the HTTP method override can be used only when the real request is a POST request, the override value can also be specified in an HTTP header or in a query string value as a name/value pair.

More on Overriding HTTP Verbs

Overriding HTTP verbs via X-HTTP-Method-Override is not an official standard, but it has become a common convention. It was first introduced by Google as part of the Google Data Protocol in 2006 (http://code.google.com/apis/gdata/docs/2.0/basics.html), and has since been implemented in a variety of RESTful web APIs and web frameworks. Ruby on Rails follows the same pattern, but uses a _method form field instead of X-HTTP-Method-Override.

Invoking Actions

Next the invoker uses the model binder (discussed in depth in Chapter 4, in the “Model Binding” section) to map values for each parameter of the action method, and is then finally ready to invoke the action method itself. At this point, the invoker builds up a list of filters associated with the current action method and invokes the filters along with the action method, in the correct order. For more detailed coverage of this, see the “Action Filters” section of Chapter 13.

Using Asynchronous Controller Actions

ASP.NET MVC 2 and later include full support for an asynchronous request pipeline. This is made possible by the introduction of the AsyncController and supporting infrastructure. The purpose of this pipeline is to allow the web server to handle long-running requests — such as those that spend a large amount of time waiting for a network or database operation to complete — while still remaining responsive to other requests. In this regard, asynchronous code is about servicing requests more efficiently than it is about servicing an individual request more quickly.

To understand the difference between asynchronous and synchronous ASP.NET code, one must first have a basic knowledge of how requests are processed by the web server. IIS maintains a collection of idle threads (the thread pool) that are used to service requests. When a request comes in, a thread from the pool is scheduled to process that request. While a thread is processing a request, it cannot be used to process any other requests until it has finished with the first. The ability of IIS to service multiple requests simultaneously is based on the assumption that there will be free threads in the pool to process incoming requests.

Now consider an action that makes a network call as part of its execution, and consider that the network call might take two seconds to complete. From the site visitor's point of view, the server takes about two seconds to respond to his or her request, if you take into account a little bit of overhead on the web server itself. In a synchronous world, the thread processing the request is blocked for the two seconds that the network call is taking place. That is, the thread cannot perform useful work for the current request because it's waiting for the network call to complete, but it also can't do any useful work for any other request because it's still scheduled to work on the first request. A thread in this condition is known as a blocked thread. Normally this isn't a problem because the thread pool is large enough to accommodate such scenarios. However, in large applications that process multiple simultaneous requests this can lead to many threads being blocked waiting for data and not enough idle threads left in the thread pool available for dispatch for servicing new incoming requests. This condition is known as thread starvation, and it can severely affect the performance of a website. See Figure 14.10.

In an asynchronous pipeline, threads are not blocked waiting for data. When a long-running application such as a network call begins, the action is responsible for voluntarily relinquishing control of the thread for the duration of the operation. Essentially, the action tells the thread, “It'll be a while before I can continue, so don't bother waiting for me right now. I'll notify IIS when the data I need is available.” The thread is then returned to the thread pool so that it can handle another request, and the current request is essentially paused while waiting for data. Importantly, while a request is in this state, it is not assigned to any thread from the thread pool, so it is not blocking other requests from being processed. When the action's data becomes available, the network request completion event notifies IIS and a free thread from the thread pool is dispatched to continue processing the request. The thread that continues processing the request may or may not be the same thread that originated the request, but the pipeline takes care of this so that developers don't have to worry about it. See Figure 14.11.

It is important to note that in the previous example, the end user still sees a two-second delay between the time he sends the request and the time he receives a response from the server. This is what is meant by the earlier statement about asynchronous being primarily for efficiency rather than the response speed for an individual request. Even though it takes the same amount of time to respond to the user's request regardless of whether the operation is synchronous or asynchronous, in an asynchronous pipeline the server is not blocked from doing other useful work while waiting for the first request to complete.

Choosing Synchronous versus Asynchronous Pipelines

The following are some guidelines for deciding whether to use synchronous or asynchronous pipelines. Note that these are just guidelines and each application will have its own requirements.

Use synchronous pipelines when:

  • The operations are simple or short-running.
  • Simplicity and testability are important.
  • The operations are CPU-bound rather than IO-bound.

Use asynchronous pipelines when:

  • Testing shows that blocking operations are bottlenecking site performance.
  • Parallelism is more important than simplicity of code.
  • The operations are IO-bound rather than CPU-bound.

Because asynchronous pipelines have more infrastructure and overhead than synchronous pipelines, asynchronous code is somewhat more difficult to reason about than synchronous code. Testing such code would require mocking more of the infrastructure, and it would also require taking into account that the code can execute in many different orderings. Finally, it's not really beneficial to convert a CPU-bound operation to an asynchronous operation, because all that does is add overhead to an operation that probably wasn't blocked to begin with. In particular, this means that code that performs CPU-bound work within ThreadPool.QueueUserWorkItem() method will not benefit from an asynchronous pipeline.

Writing Asynchronous Action Methods

Asynchronous actions are written in a similar fashion to standard synchronous actions. In much the same way that the Controller type serves as the base class for synchronous controllers, the AsyncController type serves as the base class for asynchronous controllers. For example, consider a portal site that displays news for a given area. The news in this example is provided via a GetNews() method which involves a network call which could be long-running. A typical synchronous action might look like this:

public class PortalController : Controller {    public ActionResult News(string city) {        NewsService newsService = new NewsService();        NewsModel news = newsService.GetNews(city);        return View(news);    }}

Accessing /Portal/News?city=Seattle will show local news for Seattle. This can be rewritten as an asynchronous action method as follows:

public class PortalController : AsyncController {    public void NewsAsync(string city) {        AsyncManager.OutstandingOperations.Increment();        NewsService newsService = new NewsService();        newsService.GetNewsCompleted += (sender, e) => {            AsyncManager.Parameters["news"] = e.News;            AsyncManager.OutstandingOperations.Decrement();        };        newsService.GetNewsAsync(city);    }    public ActionResult NewsCompleted(NewsModel news) {        return View(news);    }}

Note a few patterns here:

  • Asynchronous controller's base class is AsyncController rather than Controller. This tells the MVC pipeline to allow asynchronous requests.
  • Instead of a single News() action method there are two methods: NewsAsync() and NewsCompleted(), with the second method returning an ActionResult. This method pair is logically seen as a single action News, so it is accessed using the same URL as the synchronous action: /Portal/News?city=Seattle.
  • Observe the parameters passed to each method. The parameters passed to NewsAsync() are provided using the normal parameter binding mechanisms, while the parameters passed to NewsCompleted() are provided using the AsyncManager.Parameters dictionary. The NewsService consumed by the NewsAsync() method is an example of a service that exposes methods using an event-based asynchronous pattern (http://msdn.microsoft.com/en-us/library/wewwczdw.aspx).
  • Using AsyncManager.OutstandingOperations notifies the MVC pipeline of how many operations are pending completion. This is necessary because MVC otherwise has no way of knowing what operations were kicked off by the action method or when those operations are complete. When this counter hits zero, the MVC pipeline completes the overall asynchronous operation by calling the NewsCompleted() method.

The MVC Pattern for Asynchronous Actions

If the action name is Sample, the framework will look for SampleAsync() and SampleCompleted() methods.

The view page should be named Sample.cshtml rather than SampleAsync.cshtml or SampleCompleted.cshtml. (Remember, the action name is Sample.)

  • Normal parameter binding mechanisms are responsible for providing parameters to the SampleAsync() method.
  • Parameters to SampleCompleted() (if any) are provided via the AsyncManager.Parameters dictionary.
  • Use AsyncManager.OutstandingOperations to notify the MVC pipeline of how many operations are pending completion.
  • The SampleCompleted() method is responsible for returning the ActionResult that will eventually be executed.

Performing Multiple Parallel Operations

The true benefit of asynchronous code can be seen when an action wants to perform several asynchronous operations at a time. For example, a typical portal site would show not only news, but also sports, weather, stocks, and other information. A synchronous version of such an action method might take the following form:

public class PortalController : Controller {    public ActionResult Index(string city) {        NewsService newsService = new NewsService();                NewsModel newsModel = newsService.GetNews(city);        WeatherService weatherService = new WeatherService();        WeatherModel weatherModel = weatherService.GetWeather(city);        SportsService sportsService = new SportsService();        SportsModel sportsModel = sportsService.GetScores(city);        PortalViewModel model = new PortalViewModel {            News = newsModel,            Weather = weatherModel,            Sports = sportsModel        };        return View(model);    }}

Note that the calls are performed sequentially, so the time required to respond to the user is equal to the sum of the times required to make each individual call. If the calls are 200, 300, and 400 milliseconds (ms), then the total action execution time is 900 ms (plus some insignificant overhead).

Similarly, an asynchronous version of that action would take the following form:

public class PortalController : AsyncController {    public void IndexAsync(string city) {        AsyncManager.OutstandingOperations.Increment(3);        NewsService newsService = new NewsService();        newsService.GetNewsCompleted += (sender, e) => {            AsyncManager.Parameters["news"] = e.News;            AsyncManager.OutstandingOperations.Decrement();        };        newsService.GetNewsAsync(city);        WeatherService weatherService = new WeatherService();        weatherService.GetWeatherCompleted += (sender, e) => {            AsyncManager.Parameters["weather"] = e.Weather;            AsyncManager.OutstandingOperations.Decrement();        };        weatherService.GetWeatherAsync(city);        SportsService sportsService = new SportsService();        sportsService.GetScoresCompleted += (sender, e) => {            AsyncManager.Parameters["sports"] = e.Scores;            AsyncManager.OutstandingOperations.Decrement();        };        SportsModel sportsModel = sportsService.GetScoresAsync(city);    }    public ActionResult IndexCompleted(NewsModel news,        WeatherModel weather, SportsModel sports) {        PortalViewModel model = new PortalViewModel {            News = news,            Weather = weather,            Sports = sports        };        return View(model);    }}

Note that the operations are all kicked off in parallel, so the time required to respond to the user is equal to the longest individual call time. If the calls are 200, 300, and 400 ms, then the total action execution time is 400 ms (plus some insignificant overhead).

In both of the preceding examples, the URL to access the action is /Portal/Index?city=Seattle (or /Portal?city=Seattle, using the default route), and the view page name is Index.cshtml (because the action name is Index).

Using Filters with Asynchronous Controller Actions

Any filters (such as [Authorize], [OutputCache], [ActionName], and [AcceptVerbs]) should be placed on the ActionAsync() method rather than the ActionCompleted() method. Filters placed on the ActionCompleted() method will be ignored.

[Authorize] // correctpublic void ActionAsync() {    // ...}[Authorize] // incorrectpublic ActionResult ActionCompleted() {    // ...}

Furthermore, the method pair SampleAsync() and SampleCompleted() must share the same prefix (in this case, Sample), even if an [ActionName] attribute is applied. Consider the following action:

[ActionName("Bravo")]public void AlphaAsync() {    // ...}public ActionResult AlphaCompleted() {    // ...}

In this example, accessing /controller/Alpha will result in a 404, because the action has been renamed to Bravo. The correct URL is /controller/Bravo. The view page should be named Bravo.cshtml.

Timeouts

The default time-out for an asynchronous action is 45 seconds. If the time-out period expires, a TimeoutException will be thrown, and action filters will be able to respond to this from within OnActionExecuted(). The [HandleError] filter can also respond to this exception.

[HandleError(ExceptionType = typeof(TimeoutException))]

MVC provides two attributes to control the time-out period: [AsyncTimeout] and [NoAsyncTimeout]. [AsyncTimeout] specifies a time-out period in milliseconds, and [NoAsyncTimeout] specifies that TimeoutException should never be thrown. Because these attributes are action filters, they go on an ActionAsync() method to control that individual action, or they can go on the controller to apply to every action within that controller.

[AsyncTimeout(60000)] // this method times out after 60 secondspublic void ActionAsync() {     // ...}[NoAsyncTimeout] // infinite timeout for all actions in controllerpublic class PortalController : AsyncController {    // ...}

Additional Considerations for Asynchronous Methods

Controllers that derive from AsyncController may mix and match synchronous and asynchronous methods. That is, it is perfectly legal to have methods such as Index(), ListAsync(), ListCompleted(), and the like on the same controller.

The AsyncController will not allow direct access to ActionAsync() or ActionCompleted() methods. That is, the URL to access this action must be /controller/Action rather than /controller/ActionAsync or /controller/ActionCompleted. In particular, this means that RedirectToAction(“ActionAsync”) is incorrect; use RedirectToAction(“Action”) instead. The same rule applies to Html.ActionLink() and other APIs that accept action names as parameters.

Synchronous action methods on controllers that derive from AsyncController cannot have an Async or Completed suffix. For example, in an air travel booking site, the following is invalid unless there's a matching ReservationAsync() method:

// will be blockedpublic ActionResult ReservationCompleted() {}

If you want ReservationCompleted() to be exposed as a standard synchronous method, it needs to be moved to a synchronous controller class, or the method name must be changed. You can restore the original action name using an alias:

[ActionName("ReservationCompleted")]public ActionResult SomeOtherName() {}

If your asynchronous action method calls a service that exposes methods using the BeginMethod()/EndMethod() pattern (http://msdn.microsoft.com/en-us/library/ms228963.aspx), your callback will be executed on a thread that is not under the control of ASP.NET. Some consequences of this are that HttpContext.Current will be null, and there will be race conditions accessing members like AsyncManager.Parameters. To restore HttpContext.Current and eliminate the race condition, call AsyncManager.Sync() from within your callback.

public void NewsAsync(string city) {    AsyncManager.OutstandingOperations.Increment();    NewsService newsService = new NewsService();    newsService.BeginGetNews(city, ar => {        AsyncManager.Sync(() => {            AsyncManager.Parameters["news"] =                newsService.EndGetNews(ar);            AsyncManager.OutstandingOperations.Decrement();        });    }, null);}

Alternatively, the ASP.NET Futures assembly provides an AsyncManager.RegisterTask() extension method, which handles synchronization on your behalf. It also handles incrementing and decrementing the OutstandingOperations counter so that you don't have to.

public void NewsAsync(string city) {    NewsService newsService = new NewsService();    AsyncManager.RegisterTask(        callback =>            newsService.BeginGetNews(city, callback, null),        ar => { AsyncManager.Parameters["news"] =                    newsService.EndGetNews(ar); }    );}

ASP.NET MVC Futures

The futures project contains features that the ASP.NET MVC team is considering for a future release of ASP.NET MVC. It is available from http://aspnet.codeplex.com or via NuGet (named Mvc3Futures). Be sure to reference the Futures assembly and import the Microsoft.Web.Mvc namespace if you want to use this extension method.

You can call AsyncManager.Finish() to force the ActionCompleted() method to be called, even before the OutstandingOperations counter has reached zero.

The Html.Action() and Html.RenderAction() helpers can call asynchronous action methods, but they will execute synchronously. That is, the thread servicing the request will not be released back to the thread pool between the calls to ActionAsync() and ActionCompleted().

note

Remember to drop the Async suffix when passing the action name parameter to Html.Action() or Html.RenderAction().

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

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