Chapter 10. Controller Extensibility

In this chapter, you'll see how to inject extra logic into the request processing pipeline using filters, and how as an advanced user you can customize the mechanisms for locating and instantiating controllers and invoking their action methods. Finally, you'll see how to use asynchronous controllers to cope with very high volumes of traffic.

Using Filters to Attach Reusable Behaviors

You can tag extra behaviors onto controllers and action methods by decorating them with filters. Filters are .NET attributes that add extra steps to the request processing pipeline, letting you inject extra logic before and after action methods run, before and after action results are executed, and in the event of an unhandled exception.

Tip

Here's a quick refresher for anyone not totally familiar with .NET's concept of attributes. Attributes are special .NET classes derived from System.Attribute, which you can attach to other classes, methods, properties, and fields. The purpose of this is to embed additional information into your classes that you can later read back at runtime. In C#, they're attached using a square bracket syntax, and you can populate their public properties with a named parameter syntax (e.g., [MyAttribute(SomeProperty=value)]). Also, in the C# compiler's naming convention, if the attribute class name ends with the word Attribute, you can omit that portion (e.g., you can apply AuthorizeAttribute by writing just [Authorize]).

Filters are a clean and powerful way to implement cross-cutting concerns. This means behavior that gets reused all over the place, not naturally fitting at any one place in a traditional object-oriented hierarchy. Classic examples of this include logging, authorization, and caching. You've already seen examples of filters earlier in the book (e.g., in Chapter 6, we used [Authorize] to secure SportsStore's AdminController).

Note

They are called filters because the same term is used for the equivalent facility in other web programming frameworks, including Ruby on Rails. However, they are totally unrelated to the core ASP.NET platform's Request.Filter and Response.Filter objects, so don't get confused! You can still use Request.Filter and Response.Filter in ASP.NET MVC (to transform the output stream—it's an advanced and unusual activity), but when ASP.NET MVC programmers talk about filters, they normally mean something totally different.

Introducing the Four Basic Types of Filter

The MVC Framework understands four basic types of filters. These different filter types, shown in Table 10-1, let you inject logic at different points in the request processing pipeline.

Table 10-1. The Four Basic Filter Types

Filter Type

Interface

When Run

Default Implementation

Authorization filter

IAuthorizationFilter

First, before running any other filters or the action method

AuthorizeAttribute

Action filter

IActionFilter

Before and after the action method is run

ActionFilterAttribute

Result filter

IResultFilter

Before and after the action result is executed

ActionFilterAttribute

Exception filter

IExceptionFilter

Only if another filter, the action method, or the action result throws an unhandled exception

HandleErrorAttribute

Notice that ActionFilterAttribute is the default implementation for both IActionFilter and IResultFilter—it implements both of those interfaces. It's meant to be totally general purpose, so it doesn't provide any implementation (in fact, it's marked abstract, so you can only use it by deriving a subclass from it). However, the other default implementations (AuthorizeAttribute and HandleErrorAttribute) are concrete, contain useful logic, and can be used without deriving a subclass.

To get a better understanding of these types and their relationships, examine Figure 10-1. It shows that all filter attributes are derived from FilterAttribute and also implement one or more of the filter interfaces. The dark boxes represent ready-to-use concrete filters; the rest are interfaces or abstract base classes. Later in this chapter, you'll learn more about each built-in filter type.

Class hierarchy of ASP.NET MVC's built-in filters

Figure 10-1. Class hierarchy of ASP.NET MVC's built-in filters

To implement a custom filter, you can create a class derived from FilterAttribute (the base class for all filter attributes), and then also implement one or more of the four filter interfaces. For example, AuthorizeAttribute inherits from FilterAttribute and also implements IAuthorizationFilter. However, you don't normally have to bother with that, because in most cases you can use the default concrete implementations directly or derive subclasses from them.

Applying Filters to Controllers and Action Methods

You can apply filters either to individual action methods or to all the action methods on a given controller—for example:

[Authorize(Roles="trader")] // Applies to all actions on this controller
public class StockTradingController : Controller
{
    [OutputCache(Duration=60)] // Applies only to this action method
    public ViewResult CurrentRiskSummary()
    {
        // ... etc.
    }
}

You can apply multiple filters at any level, and you can control their order of execution using the FilterAttribute base class's Order property. You'll learn more about how to control filter ordering and exception bubbling later in this section. In theory, this can be quite complex, but in practice, you should be able to keep your filter usage reasonably simple.

Note

If all your controllers derive from a custom base class, then filter attributes applied to the base class (or methods on it) will also apply to your derived controllers (or overridden methods on them). This is simply because FilterAttribute is marked with Inherited = true—it's a mechanism in .NET itself rather than a feature of ASP.NET MVC.

To clarify how these four filter types fit around executing an action method, consider the following pseudocode. It roughly represents what the default ControllerActionInvoker does in its InvokeAction() method.

try
{
   Run each IAuthorizationFilter's OnAuthorization() method

   if(none of the IAuthorizationFilters cancelled execution)
   {
      Run each IActionFilter's OnActionExecuting() method
      Run the action method
      Run each IActionFilter's OnActionExecuted() method (in reverse order)

      Run each IResultFilter's OnResultExecuting() method
      Run the action result
      Run each IResultFilter's OnResultExecuted() method (in reverse order)
   }
   else
   {
      Run any action result set by the authorization filters
   }
}
catch(exception not handled by any action or result filter)
{
      Run each IExceptionFilter's OnException() method
      Run any action result set by the exception filters
}

This pseudocode gives you the big picture of what happens when, but is not precise enough to describe completely how exceptions bubble up through action and result filters, or how you can handle them before they reach the exception filters. You'll learn about that later.

First, let's get more familiar with each of the four basic filter types.

Creating Action Filters and Result Filters

As mentioned previously, general purpose action and result filters are .NET attributes, derived from FilterAttribute, that also implement IActionFilter, IResultFilter, or both. However, rather than creating one like that, it's easier and more common simply to derive a subclass of the built-in ActionFilterAttribute—it gives you a combination of an action filter and a result filter (it implements both interfaces for you), and then you only need to override the specific methods that interest you.

Between IActionFilter and IResultFilter, there are four methods you can implement, which correspond to four different places in the request handling pipeline where you can inject custom logic. These methods are shown in Tables 10-2 and 10-3.

Table 10-2. Methods on IActionFilter (Which You Can Also Override on ActionFilterAttribute)

Method

When Called

Special Things You Can Do During the Method

OnActionExecuting()

Before the action method runs

You can prevent execution of the action method by assigning an ActionResult to filterContext.Result.

You can inspect and edit filterContext.ActionParameters, the parameters that will be used when calling the action method.

OnActionExecuted()

After the action method runs

You can obtain details of any exception thrown by the action method from filterContext.Exception, and optionally mark it as "handled"[a] by setting filterContext.ExceptionHandled = true.

You can inspect or change the ActionResult using filterContext.Result.

[a] If you don't set filterContext.ExceptionHandled = true, it will bubble up to the next filter in the chain. You'll learn more about this mechanism shortly.

Table 10-3. Methods on IResultFilter (Which You Can Also Override on ActionFilterAttribute)

Method

When Called

Special Things You Can Do During the Method

OnResultExecuting()

Before the ActionResult is executed

You can inspect (but not change) the ActionResult using filterContext.Result.

You can prevent its execution by setting filterContext.Cancel = true.

OnResultExecuted()

After the ActionResult is executed

You can obtain details of any exception thrown by the ActionResult from filterContext.Exception, and optionally mark it as "handled" by setting filterContext.ExceptionHandled = true.

You can inspect (but not change) the ActionResult using filterContext.Result.

In all four cases, the framework supplies a "context" parameter called filterContext that lets you read and write a range of context objects. For example, it gives you access to Request and Response. Here's a fairly artificial example that demonstrates all four points of interception by writing directly to Response:

public class ShowMessageAttribute : ActionFilterAttribute
{
    public string Message { get; set; }

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        filterContext.HttpContext.Response.Write("[BeforeAction " + Message + "]");
    }
    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        filterContext.HttpContext.Response.Write("[AfterAction " + Message + "]");
    }
    public override void OnResultExecuting(ResultExecutingContext filterContext)
    {
        filterContext.HttpContext.Response.Write("[BeforeResult " + Message + "]");
    }
    public override void OnResultExecuted(ResultExecutedContext filterContext)
    {
        filterContext.HttpContext.Response.Write("[AfterResult " + Message + "]");
    }
}

If you attach this filter to an action method—for example:

public class FiltersDemoController : Controller
{
    [ShowMessage(Message = "Howdy")]
    public ActionResult SomeAction()
    {
        Response.Write("Action is running");
        return Content("Result is running");
    }
}

it will output the following (the line break is added for clarity):

[BeforeAction Howdy]Action is running[AfterAction Howdy]
[BeforeResult Howdy]Result is running[AfterResult Howdy]

Controlling the Order of Execution

You can associate multiple filters with a single action method:

[ShowMessage(Message = "A")]
[ShowMessage(Message = "B")]
public ActionResult SomeAction()
{
Response.Write("Action is running");
    return Content("Result is running");
}

Note

By default, the C# compiler won't let you put two instances of the same attribute type at a single location. Compilation will fail with the error "Duplicate 'ShowMessage' attribute." To get around this, declare your filter attribute to allow multiple instances by inserting the following immediately above the ShowMessageAttribute class: [AttributeUsage(AttributeTargets.Class|AttributeTargets.Method, AllowMultiple=true)].

This outputs the following (the line break is added for clarity):

[BeforeAction B][BeforeAction A]Action is running[AfterAction A][AfterAction B]
[BeforeResult B][BeforeResult A]Result is running[AfterResult A][AfterResult B]

As you can see, it's like a stack: the OnActionExecuting() calls build up, then the actual action method runs, and then the stack unwinds with OnActionExecuted() calls in the opposite order—likewise with OnResultExecuting() and OnResultExecuted().

It just so happens that when I ran this code, filter B was chosen to go first in the stack, but your results may vary—technically, the filter stack order is undefined unless you specify an explicit order. You can assign an explicit stack order by assigning an int value to each filter's Order property (it's defined on the FilterAttribute base class):

[ShowMessage(Message = "A", Order = 1)]
[ShowMessage(Message = "B", Order = 2)]
public ActionResult SomeAction()
{
    Response.Write("Action is running");
    return Content("Result is running");
}

Lower Order values go first, so this time A and B appear in the opposite order:

[BeforeAction A][BeforeAction B]Action is running[AfterAction B][AfterAction A]
[BeforeResult A][BeforeResult B]Result is running[AfterResult B][AfterResult A]

All action filters are sorted by Order. It doesn't matter what action filter type they are, or whether they are defined at the action level, at the controller level, or on the controller's base class—lower Order values always run first. Afterward, all the result filters are run in order of their Order values.

If you don't assign an Order value, that filter is "unordered," and by default takes the special Order value of −1. You can't explicitly assign an order lower than −1, so unordered filters are always among the first to run. As I hinted at earlier, groups of filters with the same Order value (e.g., unordered ones) run in an undefined order among themselves.[63]

Filters on Actions Can Override Filters on Controllers

What would you expect to happen if you attached the same type of filter both to a controller and to one of its action methods? The following code gives an example:

[ShowMessage(Message = "C")]
public class FiltersDemoController : Controller
{
    [ShowMessage(Message = "A")]
    public ActionResult SomeAction()
    {
        Response.Write("Action is running");
        return Content("Result is running");
    }
}

If the filter attribute is itself marked with an [AttributeUsage] attribute specifying AllowMultiple=true, then ASP.NET MVC will invoke both instances of the filter, so you'd get the following output (line break added):

[BeforeAction C][BeforeAction A]Action is running[AfterAction A][AfterAction C]
[BeforeResult C][BeforeResult A]Result is running[AfterResult A][AfterResult C]

But if the filter attribute is not marked with AllowMultiple=true—and by default it isn't—then the framework will consider instances associated with actions as overriding and replacing any instances of an identical type associated with controllers. So, you'd get the following output (line break added):

[BeforeAction A]Action is running[AfterAction A]
[BeforeResult A]Result is running[AfterResult A]

This behavior is useful if you want to establish a default behavior by applying a filter at the controller level, but also override and replace that behavior by using the same filter type on an individual action.

Using the Controller Itself As a Filter

There is another way to attach code as a filter without having to create any attribute. The Controller base class itself implements IActionFilter, IResultFilter, IAuthorizationFilter, and IExecutionFilter. That means it exposes the following overridable methods:

  • OnActionExecuting() and OnActionExecuted()

  • OnResultExecuting() and OnResultExecuted()

  • OnAuthorization()

  • OnException()

If you override any of these, your code will be run at the exact same point in the request processing pipeline as the equivalent filter attribute. These controller methods are treated as being higher in the filter stack, above any filter attributes of the equivalent type, regardless of your attributes' Order properties. These methods give you a very quick and easy way to add controller code that runs before or after all action methods on that particular controller, or whenever an unhandled exception occurs in that particular controller.

So, when should you create and attach a filter attribute, and when should you just override a filter method on the Controller base class? It's simple: if you want to reuse your behavior across multiple controllers, then it needs to be an attribute. If you're only going to use it on one specific controller, then it's easier just to override one of the preceding methods.

This also means that if you create a common base class for all your controllers, you can apply filter code globally across all controllers just by overriding a filter method on your base class. This is a flexible and powerful pattern known as layer supertype. The cost of that power, however, can be extra difficulty in long-term maintenance—it's all too easy to add more and more code to the base class over time, even code that's relevant only to a subset of your controllers, and then have every controller become a complex and slow-running beast. You have to weigh the power of this approach against the responsibility of prudent base-class management. In many cases, it's tidier not to use a layer supertype, but instead to compose functionality by combining the relevant filter attributes for each separate controller.

Creating and Using Authorization Filters

As mentioned earlier, authorization filters are special types of filters that run early in the request processing pipeline, before any subsequent action filters, action method, or action result. You can create a custom authorization filter by deriving from FilterAttribute and also implementing IAuthorizeFilter; but for reasons I'll explain in a moment, it's usually better either to use the built-in concrete authorization filter, AuthorizeAttribute, or to derive a subclass from it.

AuthorizeAttribute lets you specify values for any of the properties listed in Table 10-4.

Table 10-4. Properties of AuthorizeAttribute

Property Name

Type

Meaning

Order

int

Execution order of this filter among other authorization filters. Lower values go first. Inherited from FilterAttribute.

Users

string

Comma-separated list of usernames that are allowed to access the action method.

Roles

string

Comma-separated list of role names. To access the action method, users must be in at least one of these roles.

If you specify both Users and Roles, then a user needs to satisfy both criteria in order to access the action method. For example, if you use the attribute as follows:

public class MicrosoftController : Controller
{
    [Authorize(Users="billg, steveb, rayo", Roles="chairman, ceo")]
    public ActionResult BuySmallCompany(string companyName, double price)
    {
        // Cher-ching!
    }
}

then a user may only access the BuySmallCompany() action if the user meets all of the following criteria:

  1. They are authenticated (i.e., HttpContext.User.Identity.IsAuthenticated equals true).

  2. Their username (i.e., HttpContext.User.Identity.Name) equals billg, steveb, or rayo (case insensitively).

  3. They are in at least one of the roles chairman or ceo (as determined by HttpContext.User.IsInRole(roleName)).

If the user fails to meet any one of those criteria, then AuthorizeAttribute cancels execution of the action method (and all subsequent filters) and forces an HTTP status code of 401 (meaning "not authorized"). The 401 status code will cause your active authentication system (e.g., Forms Authentication) to kick in, which may prompt the user to log in, or may return an "access denied" screen.

If you don't specify any usernames, then criterion 2 is skipped. If you don't specify any role names, then criterion 3 is skipped.

Since the filter determines the current request's username and role data by looking at the IPrincipal object in HttpContext.User, it's automatically compatible with Forms Authentication, integrated Windows Authentication, and any custom authentication/authorization system that has already set a value for HttpContext.User.

Note

[Authorize] doesn't give you a way of combining criteria 2 and 3 with an "or" disjunction (e.g., a user can access an action if their login name is billg or they are in the role chairman, or both). To do that, you'll need to implement a custom authorization filter. You'll see an example shortly.

How Authorization Filters Interact with Output Caching

As you'll learn in more detail in a few pages, ASP.NET MVC also supports output caching through its built-in [OutputCache] filter. This works just like ASP.NET Web Forms' output caching, in that it caches the entire response so that it can be reused immediately next time the same URL is requested. Behind the scenes, [OutputCache] is actually implemented using the core ASP.NET platform's output-caching technology, which means that if there's a cache entry for a particular URL, it will be served without invoking any part of ASP.NET MVC (not even the authorization filters).

So, what happens if you combine an authorization filter with [OutputCache]? In the worst case, you run the risk of an authorized user first visiting your action, causing it to run and be cached, shortly followed by an unauthorized user, who gets the cached output even though they aren't authorized. Fortunately, the ASP.NET MVC team has anticipated this problem, and has added special logic to AuthorizeAttribute to make it play well with ASP.NET output caching. It uses a little-known output-caching API to register itself to run when the output-caching module is about to serve a response from the cache. This prevents unauthorized users from getting cached content.

You might be wondering why I've bothered explaining this obscure technicality. I've done so to warn you that if you implement your own authorization filter from scratch—by deriving from FilterAttribute and implementing IAuthorizationFilter—you won't inherit this special logic, so you'll risk allowing unauthorized users to obtain cached content. Therefore, don't implement IAuthorizationFilter directly, but instead derive a subclass of AuthorizeAttribute.

Creating a Custom Authorization Filter

As explained previously, the best way to create a custom authorization filter is to derive a subclass of AuthorizeAttribute. All you need to do is override its virtual AuthorizeCore() method and return a bool value to specify whether the user is authorized—for example:

public class EnhancedAuthorizeAttribute : AuthorizeAttribute
{
    public bool AlwaysAllowLocalRequests = false;

    protected override bool AuthorizeCore(System.Web.HttpContextBase httpContext)
    {
        if (AlwaysAllowLocalRequests && httpContext.Request.IsLocal)
            return true;

        // Fall back on normal [Authorize] behavior
        return base.AuthorizeCore(httpContext);
    }
}

You could use this custom authorization filter as follows:

[EnhancedAuthorize(Roles = "RemoteAdmin", AlwaysAllowLocalRequests = true)]

This would grant access to visitors if they were in the RemoteAdmin role or if they were directly logged into Windows on the server itself. This could be handy to allow server administrators to access certain configuration functions, but without necessarily letting them do so from across the Internet.

Since it's derived from FilterAttribute, it inherits an Order property, so you can specify its order among other authorization filters. The MVC Framework's default ControllerActionInvoker will run each one in turn. If any of the authorization filters denies access, then ControllerActionInvoker short-circuits the process by not bothering to run any subsequent authorization filters.

Also, since this class is derived from AuthorizeAttribute, it shares the behavior of being safe to use with output caching, and of applying an HttpUnauthorizedResult if access is denied.

Tip

As described previously, you can add custom authorization code to an individual controller class without creating an authorization filter attribute—just override the controller's OnAuthorization() method instead. To deny access, set filterContext.Result to any non-null value, such as an instance of HttpUnauthorizedResult. The OnAuthorization() method will run at the exact same point in the request handling pipeline as an authorization filter attribute, and can do exactly the same things. However, if you need to share the authorization logic across multiple controllers, or if you need authorization to work safely with output caching, then it's better to implement authorization as a subclass of AuthorizeAttribute, as shown in the previous example.

If you want to intercept authorization failures and add some custom logic at that point, you can override the virtual HandleUnauthorizedRequest() method on your custom authorization filter.

This is a common requirement in Ajax scenarios. If an Ajax request is denied authorization, then usually you don't want to return an HTTP redirection to the login page, because your client-side code is not expecting that and may do something unwanted such as injecting the entire login page into the middle of whatever page the user is on. Instead, you'll want to send back a more useful signal to the client-side code, perhaps in JSON format, to explain that the request was not authorized. You could implement this as follows:

public class EnhancedAuthorizeAttribute : AuthorizeAttribute
{
    protected override void HandleUnauthorizedRequest(AuthorizationContext context)
    {
        if (context.HttpContext.Request.IsAjaxRequest()) {
            UrlHelper urlHelper = new UrlHelper(context.RequestContext);
            context.Result = new JsonResult {
                Data = new {
                    Error = "NotAuthorized",
                    LogOnUrl = urlHelper.Action("LogOn", "Account")
                },
                JsonRequestBehavior = JsonRequestBehavior.AllowGet
            };
        }
        else
            base.HandleUnauthorizedRequest(context);
    }
}

To use this, you would also need to enhance your client-side code to detect this kind of response and notify the user appropriately. You'll learn more about working with Ajax and JSON in Chapter 14.

Creating and Using Exception Filters

As you saw in the pseudocode a few pages back, exception filters run only if there has been an unhandled exception while running authorization filters, action filters, the action method, result filters, or the action result. The two main use cases for exception filters are

  • To log the exception

  • To display a suitable error screen to the user

You can implement a custom exception filter, or in simple cases, you can just use the built-in HandleErrorAttribute as is.

Using HandleErrorAttribute

HandleErrorAttribute lets you detect specific types of exceptions, and when it detects one, it just renders a particular view template and sets the HTTP status code to 500 (meaning "internal server error"). The idea is that you can use it to render some kind of "Sorry, there was a problem" screen. It doesn't log the exception in any way—you need to create a custom exception filter to do that.

HandleErrorAttribute has four properties for which you can specify values, as listed in Table 10-5.

Table 10-5. Properties You Can Set on HandleErrorAttribute

Property Name

Type

Meaning

Order

int

The execution order of this filter among other exception filters. Lower values go first. Inherited from FilterAttribute.

ExceptionType

Type

The exception type handled by this filter. It will also handle exception types that inherit from the specified value, but will ignore all others. The default value is System.Exception, which means that by default it will handle all standard exceptions.

View

string

The name of the view template that this filter renders. If you don't specify a value, it takes a default value of Error, so by default it would render /Views/currentControllerName/Error.aspx or /Views/Shared/Error.aspx.

Master

string

The name of the master page used when rendering this filter's view template. If you don't specify a value, the view uses its default master page.

If you apply the filter as follows:

[HandleError(View = "Problem")]
public class ExampleController : Controller
{
    /* ... action methods here ... */
}

then, if there's an exception while running any action method (or associated filter) on that controller, HandleErrorAttribute will try to render a view from one of the following locations:

  • ~/Views/Example/Problem.aspx.

  • ~/Views/Example/Problem.ascx.

  • ~/Views/Shared/Problem.aspx.

  • ~/Views/Shared/Problem.ascx.

Warning

HandleErrorAttribute only takes effect when you've enabled custom errors in your Web.config file—for example, by adding <customErrors mode="On" /> inside the <system.web> node. The default custom errors mode is RemoteOnly, which means that during development, HandleErrorAttribute won't intercept exceptions at all, but when you deploy to a production server and make requests from another computer, HandleErrorAttribute will take effect. This can be confusing! To see what end users are going to see, make sure you've set the custom errors mode to On.

When rendering the view, HandleErrorAttribute will supply a Model object of type HandleErrorInfo. So, if you make your error handling view template strongly typed (specifying HandleErrorInfo as the model type), you'll be able to access and render information about the exception. For example, by adding the following to /Views/Shared/Problem.aspx:

<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<HandleErrorInfo>" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
          "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Sorry, there was a problem!</title>
    </head>
    <body>
        <p>
             There was a <b><%: Model.Exception.GetType().Name %></b>
             while rendering <b><%: Model.ControllerName %></b>'s
             <b><%: Model.ActionName %></b> action.
        </p>
        <p>
             The exception message is: <b><%: Model.Exception.Message %></b>
        </p>
        <p>Stack trace:</p>
        <pre><%: Model.Exception.StackTrace %></pre>
    </body>
</html>

you can render a screen like that shown in Figure 10-2. Of course, for a publicly deployed web site, you won't usually want to expose this kind of information (especially not the stack trace), but it might be helpful during development.

Rendering a view from HandleErrorAttribute

Figure 10-2. Rendering a view from HandleErrorAttribute

When HandleErrorAttribute handles an exception and renders a view, it marks the exception as "handled" by setting a property called ExceptionHandled to true. You'll learn about the meaning and significance of this during the next example.

Creating a Custom Exception Filter

Not surprisingly, you can create a custom exception filter by creating a class derived from FilterAttribute and implementing IExceptionFilter. You might just silently log the exception to your database or to the Windows Application event log, and leave it to some other filter to produce visible output for the user. Or, you can produce visible output (e.g., render a view or perform a redirection) by assigning an ActionResult object to the filterContext.Result property.

Here's a custom exception filter that performs a redirection:

public class RedirectOnErrorAttribute : FilterAttribute, IExceptionFilter
{
    public void OnException(ExceptionContext filterContext)
    {
        // Don't interfere if the exception is already handled
        if(filterContext.ExceptionHandled)
            return;

        // Let the next request know what went wrong
        filterContext.Controller.TempData["exception"] = filterContext.Exception;

        // Set up a redirection to my global error handler
        filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(
                new { controller = "Exception", action = "HandleError" }
            ));

        // Advise subsequent exception filters not to interfere
        // and stop ASP.NET from producing a "yellow screen of death"
filterContext.ExceptionHandled = true;

        // Erase any output already generated
        filterContext.HttpContext.Response.Clear();
    }
}

This example demonstrates the use of filterContext.ExceptionHandled. It's a bool property that starts off false, but as each exception filter is run in turn, one of them might choose to switch it to true. This does not cause ControllerActionInvoker to stop running subsequent exception filters, however. It will still run all the remaining exception filters, which is helpful if a subsequent filter is supposed to log the exception.[64]

The filterContext.ExceptionHandled flag tells subsequent exception filters that you've taken care of things, and they can ignore the exception. But that doesn't force them to ignore the exception—they might still wish to log it, and they could even overwrite your filterContext.Result. The built-in HandleErrorAttribute is well behaved—if filterContext.ExceptionHandled is already set to true, then it will ignore the exception entirely.

After all the exception filters have been run, the default ControllerActionInvoker looks at filterContext.ExceptionHandled to see whether the exception is considered to be handled. If it's still false, then it will rethrow the exception into ASP.NET itself, which will produce a familiar "yellow screen of death" (unless you've set up an ASP.NET global exception handler).

Tip

As described previously, you can add custom exception handling code to an individual controller class without creating an exception filter attribute—just override the controller's OnException() method instead. That code will run at the exact same point in the request handling pipeline as an exception filter attribute, and can do exactly the same things. This is easier as long as you don't intend to share that exception handling code with any other controller.

Bubbling Exceptions Through Action and Result Filters

As it happens, exception filters aren't the only way to catch and deal with exceptions:

  • If an action method throws an unhandled exception, then all the action filters' OnActionExecuted() methods will still fire, and any one of them can choose to mark the exception as "handled" by setting filterContext.ExceptionHandled to true.

  • If an action result throws an unhandled exception, then all the result filters' OnResultExecuted() methods will still fire, and any one of them can choose to mark the exception as "handled" by setting filterContext.ExceptionHandled to true.

To clarify how this process works, and also to understand why OnActionExecuted() methods run in the opposite order to OnActionExecuting(), consider Figure 10-3. It shows that each filter in the chain creates an extra level of recursion.

How action filters are called recursively around the action method

Figure 10-3. How action filters are called recursively around the action method

If an exception occurs at any level, it's caught at the level above, and that level's OnActionExecuted() method gets invoked. If OnActionExecuted() sets filterContext.ExceptionHandled to true, then the exception is swallowed, and no other filters ever hear about it (including exception filters). Otherwise, it's rethrown, and recaught at the next level above. Ultimately, if the top action filter (meaning the first one) doesn't mark the exception as handled, then the exception filters will be invoked.

The same sequence of events occurs when processing result filters and the action result. Exceptions bubble up through calls to OnResultExecuted() in just the same way, being swallowed or rethrown. If the top (i.e., first) result filter doesn't mark the exception as handled, then the exception filters will be invoked.

As mentioned previously, if the exception reaches the exception filters, then all the exception filters will run. If at the end none of them has marked it as handled, then it's rethrown into ASP.NET itself, which may produce a yellow screen of death or a custom error page.

Note

If you've ever delved into the internals of previous versions of ASP.NET, you might be aware that when you issue a redirection using Response.Redirect(), it can stop execution by throwing a ThreadAbortException. If you were to call Response.Redirect() (instead of returning a proper ASP.NET MVC RedirectToRouteResult), you might think this would unhelpfully cause your exception filters to kick in. Fortunately, the MVC team anticipated this potential problem and treated ThreadAbortException as a special case—this exception type is hidden from all filters so that redirections don't get treated as errors.

The [OutputCache] Action Filter

As you can guess, OutputCacheAttribute tells ASP.NET to cache the action method's output so that the same output will be reused next time the action method is requested. This can increase your server's throughput by orders of magnitude, as for subsequent requests it eliminates almost all the expensive parts of request processing (such as database queries). Of course, the cost of this is that you're limited to producing the exact same output in response to each such request.

Just like core ASP.NET's output-caching feature, ASP.NET MVC's OutputCacheAttribute lets you specify a set of parameters that describe when to vary the action's output. This is a trade-off between flexibility (varying your output) and performance (reusing precached output). Also, as with the core ASP.NET output-caching feature, you can use it to control client-side caching, too—affecting the values sent in Cache-Control headers.

Table 10-6 describes the parameters you can specify.

Table 10-6. Parameters You Can Specify for OutputCacheAttribute

Parameter Name

Type

Meaning

Duration(required)

int

Specifies how long (in seconds) the output remains cached.

VaryByParam(required)

string (semicolon-separated list)

Tells ASP.NET to use a different cache entry for each combination of Request.QueryString and Request.Form values matching these names. You can also use the special value none, meaning "Don't vary by query string or form values," or *, meaning "Vary by all query string and form values." If unspecified, it takes the default value none.

VaryByHeader

string (semicolon-separated list)

Tells ASP.NET to use a different cache entry for each combination of values sent in these HTTP header names.

VaryByCustom

string

If specified, ASP.NET calls your Global.asax.cs file's GetVaryByCustomString() method passing this arbitrary string value as a parameter, so you can generate your own cache key. The special value browser is used to vary the cache by the browser's name and major version data.

VaryByContentEncoding

string (semicolon-separated list)

Allows ASP.NET to create a separate cache entry for each content encoding (e.g., gzip, deflate) that may be requested by a browser. You'll learn more about content encoding in Chapter 17.

Location

OutputCacheLocation

Specifies where the output is to be cached. This parameter takes one of the following enumeration values: Server (in the server's memory only), Client (by the visitor's browser only), Downstream (by the visitor's browser, or by any intermediate HTTP-caching device, such as a proxy server), ServerAndClient (combination of Server and Client), Any (combination of Server and Downstream), or None (no caching). If not specified, it takes the default value Any.

NoStore

bool

If true, tells ASP.NET to send a Cache-Control: no-store header to the browser, instructing the browser not to store (i.e., cache) the page for any longer than necessary to display it. If the visitor later returns to the page by clicking the back button, this means that the browser needs to resend the request, so there is a performance cost. This is only used to protect very private data.

CacheProfile

string

If specified, instructs ASP.NET to take cache settings from a particular named <outputCacheSettings> node in Web.config.

SqlDependency

string

If you specify a database and table name pair, this causes the cached data to expire automatically when the underlying database data changes. Before this will work, you must also configure the core ASP.NET SQL Cache Dependency feature, which can be quite complicated and is well beyond the scope of this section. See http://msdn.microsoft.com/en-us/library/ms178604.aspx for further documentation.

Order

int

Irrelevant, because OutputCacheAttribute has the same effect regardless of when it runs. Inherited from FilterAttribute.

If you've used ASP.NET's output-caching facility before, you'll recognize these options. In fact, OutputCacheAttribute is really just a wrapper around the core ASP.NET output-caching facility. For that reason, it always varies the cache entry according to URL path. If you have parameters in your URL pattern, then each combination of parameter values forces a different cache entry.

Warning

In the earlier section "How Authorization Filters Interact with Output Caching," I explained that [Authorize] has special behavior to ensure that unauthorized visitors can't obtain sensitive information just because it's already cached. However, unless you specifically prevent it, it's still possible that cached output could be delivered to a different authorized user than the one for whom it was originally generated. One way to prevent that would be to implement your access control for a particular content item as an authorization filter (derived from AuthorizeAttribute) instead of simply enforcing authorization logic inline in an action method, because AuthorizeAttribute knows how to avoid being bypassed by output caching. Test carefully to ensure that authorization and output caching are interacting in the way you expect.

Warning

Because it is based on the underlying ASP.NET platform's output-caching feature, the [OutputCache] filter is only able to cache the entire HTML response sent back to the browser. It doesn't understand the concept of child actions, so if you attach [OutputCache] to some action that you invoke using Html.Action() or Html.RenderAction(), you might expect it to cache the output of the child action, but it can't—it does nothing during child actions. If you need a mechanism to cache widgets that you render using Html.RenderAction(), you can obtain an alternative output-caching filter from my blog, at http://tinyurl.com/mvcOutputCache.

The [RequireHttps] Filter

If you want your users to switch into HTTPS mode when they request certain actions, you can enforce this using [RequireHttps]. It's an authorization filter that simply checks whether the incoming request uses the HTTPS protocol (i.e., Request.IsSecureConnection), and if not, returns a 302 redirection to the same URL, replacing http:// with https://.

Note

[RequireHttps] applies only to GET requests. That's because POST requests can contain form post data that would be lost if you attempted to redirect the user to a different URL.

Other Built-In Filter Types

The ASP.NET MVC package also includes a few more ready-to-use filters:

  • ValidateInput and ValidationAntiForgeryToken are both authorization filters related to security, so you'll learn more about them in Chapter 15.

  • AsyncTimeout and NoAsyncTimeout are both action filters related to asynchronous requests, and are covered at the end of this chapter.

  • ChildActionOnlyAttribute is an authorization filter related to the Html.Action() and Html.RenderAction() helpers, and is described in Chapter 13.

Controllers As Part of the Request Processing Pipeline

Take a look at Figure 10-4. It's a section of the MVC Framework's request handling pipeline, showing that requests are first mapped by the routing system to a particular controller, and then the chosen controller selects and invokes one of its own action methods. By now, this sequence should be quite familiar to you.

The process of invoking an action method

Figure 10-4. The process of invoking an action method

As you know, ASP.NET MVC by default uses conventions to select controllers and actions:

  • If RouteData.Values["controller"] equals Products, then the default controller factory, DefaultControllerFactory, will expect to find a controller class named ProductsController.

  • The default controller base class uses a component called ControllerActionInvoker to select and invoke an action method. If RouteData.Values["action"] equals List, then ControllerActionInvoker will expect to find an action method named List().

In many applications, this does the job perfectly well enough. But not surprisingly, the MVC Framework gives you the power to customize or replace these mechanisms if you want.

In this section, we'll investigate how you, as an advanced user, can implement a custom controller factory or inject custom action-selection logic. The most likely reason to do this is to hook up an dependency injection (DI) container or perhaps to block certain types of requests from reaching certain action methods.

Working with DefaultControllerFactory

Unless you specifically set up a custom controller factory, you'll by default be using an instance of DefaultControllerFactory. Internally, it holds a cache of all the types in all your ASP.NET MVC project's referenced assemblies (not just in your ASP.NET MVC project itself) that qualify to be controller classes, according to the following criteria:

  • The class must be marked public.

  • The class must be concrete (i.e., not marked abstract).

  • This class must not take generic parameters.

  • The class's name must end with the string Controller.

  • The class must implement IController.

For each type satisfying these criteria, it adds a reference to its cache, keyed by the type's routing name (i.e., the type name with the Controller suffix removed). Then, when it's asked to instantiate the controller corresponding to a particular routing name (since that's what's provided in RouteData.Values["controller"]), it can find that type by key very quickly. Finally, having chosen a controller type, it obtains an instance of that type simply by calling Activator.CreateInstance(theControllerType) (which is why DefaultControllerFactory can't handle controllers that require constructor parameters), and returns the result.

Complications arise if you choose to give multiple controller classes the same name, even if they are in different namespaces. DefaultControllerFactory won't know which one to instantiate, so it will simply throw an InvalidOperationException, saying "Multiple types were found that match the controller name." To deal with this, you must either avoid having multiple controller classes with the same name, or you must give DefaultControllerFactory some way of prioritizing one above the others. There are two mechanisms for defining a priority order.

Prioritizing Namespaces Globally Using DefaultNamespaces

To make DefaultControllerFactory give priority to controller classes defined in a certain collection of namespaces, you can add values to a static collection called ControllerBuilder.Current.DefaultNamespaces—for example, in your Global.asax.cs file:

protected void Application_Start()
{
    RegisterRoutes(RouteTable.Routes);
    ControllerBuilder.Current.DefaultNamespaces.Add("MyApp.Controllers.*");
    ControllerBuilder.Current.DefaultNamespaces.Add("OtherAssembly.MyNamespace.*");
}

Now, if a desired controller name is unique to a single controller type within or below those namespaces, DefaultControllerFactory will select and use that controller type rather than throwing an exception. However, if there are still multiple matching controller types within or below those namespaces, it will again throw an InvalidOperationException. (Don't be mistaken into thinking it gives priority to the namespaces in DefaultNamespaces according in the order that you've added them—it doesn't care about how they are ordered.)

Note

You need to put a trailing .* on a namespace if you want to include its child namespaces too. Without this, the framework will only prioritize controllers in that exact namespace, and not ones in any namespace below it.

If DefaultControllerFactory can't find any suitable controller type in those nominated namespaces, it reverts to its usual behavior of picking a controller type from anywhere, regardless of namespace.

Prioritizing Namespaces on Individual Route Entries

You can also prioritize a set of namespaces to use when handling a particular RouteTable.Routes entry. For example, you might decide that the URL pattern admin/{controller}/{action} should prefer to pick a controller class from the MyApp.Admin.Controllers namespace and ignore any clashing controllers that are in other namespaces.

To do this, add to your route entry a DataTokens value called Namespaces. The value you assign must implement IEnumerable<string>—for example:

routes.Add(new Route("admin/{controller}/{action}", new MvcRouteHandler())
{
    Defaults = new RouteValueDictionary(new {
        controller = "Home", action = "Index"
    }),
DataTokens = new RouteValueDictionary(new {
        Namespaces = new[] { "MyApp.Admin.Controllers.*",
                             "AnotherAssembly.Controllers.*" }
    })
});

Or equivalently, you can call MapRoute() and pass a namespaces parameter:

routes.MapRoute(null, "admin/{controller}/{action}",
    new { controller = "Home", action = "Index" },
    new[] { "MyApp.Admin.Controllers.*", "AnotherAssembly.Controllers.*"}
);

These namespaces will be prioritized only during requests that match this route entry. These prioritizations themselves take priority over ControllerBuilder.Current.DefaultNamespaces.

If you're using custom RouteBase subclasses rather than Route objects, you can support controller namespace prioritization there, too. During the GetRouteData() method, put an IEnumerable<string> value into the returned RouteData object's DataTokens collection—for example:

public class CustomRoute : RouteBase
{
    public override RouteData GetRouteData(HttpContextBase httpContext)
    {
        if (choosing to match this request)
        {
            RouteData rd = new RouteData(this, new MvcRouteHandler());
            rd.Values["controller"] = chosen controller
            rd.Values["action"] = chosen action method name
            rd.DataTokens["namespaces"] = new[] { "MyApp.Admin.Controllers.*" };
            return rd;
        }
        else
            return null;
    }
    public override VirtualPathData GetVirtualPath(...) { /* etc */ }
}

Limiting a Route Entry to Match Controllers in a Specific Set of Namespaces

If you want to ensure that your route entry only ever matches controllers in the namespaces you've specified (and doesn't merely prioritize them over others, as described previously), then you can add a further DataTokens entry called UseNamespaceFallback and set it to false.

routes.Add(new Route("admin/{controller}/{action}", new MvcRouteHandler())
{
    Defaults = new RouteValueDictionary(new {
        controller = "Home", action = "Index"
    }),
    DataTokens = new RouteValueDictionary(new {
        Namespaces = new[] { "MyApp.Admin.Controllers.*",
                             "AnotherAssembly.Controllers.*" },
        UseNamespaceFallback = false
    })
});

Now, this route entry will completely ignore all controllers except those in the nominated namespaces. It won't even pay attention to ControllerBuilder.Current.DefaultNamespaces.

Note

When you configure a route within an area (described in Chapter 8) using the AreaRegistrationContext's MapRoute() method, it automatically sets the UseNamespaceFallback flag to false so that route entry can't accidentally match controllers outside the area's namespace.

Creating a Custom Controller Factory

If a plain vanilla DefaultControllerFactory doesn't do everything you want, then you can replace it. The most obvious reason to do this is if you want to instantiate controller objects through a DI container. That would allow you to supply constructor parameters to your controllers based on your DI configuration. For a primer on DI, see Chapter 3.

You can create a custom controller factory either by writing a class that implements IControllerFactory or by deriving a subclass of DefaultControllerFactory. The latter option is usually much more productive, because you can inherit most of the default functionality (such as caching and quickly locating any type referenced by your project) and just override the behavior you want to change.

If you subclass DefaultControllerFactory, see Table 10-7 for details of the methods you can override.

Table 10-7. Overridable Methods on DefaultControllerFactory

Method

Purpose

Default Behavior

CreateController(requestContext, controllerName)

Returns a controller instance corresponding to the supplied parameters

Calls GetControllerType() and then feeds the return value into GetControllerInstance()

GetControllerType(requestContext, controllerName)

Selects which .NET type is the controller class to be instantiated

Looks for a controller type whose routing name (i.e., the name without the Controller suffix) equals controllerName; respects prioritization rules described earlier

GetControllerInstance(requestContext, controllerType)

Returns a live instance of the specified type

Calls Activator.CreateInstance(controllerType)

ReleaseController(controller)

Performs any disposal or cleanup needed

If the controller implements IDisposable, calls its Dispose() method

To integrate with most DI containers, all you need to override is GetControllerInstance(). You can retain the default type selection and disposal logic, so there's very little work for you to do. For a simple example, see NinjectControllerFactory in Chapter 4—it instantiates controllers through the Ninject container.

Registering a Custom Controller Factory

To start using your custom controller factory, register an instance of it on a static object called ControllerBuilder.Current. Do this only once, early in the application's lifetime. For example, add the following to Global.asax.cs:

protected void Application_Start()
{
    RegisterRoutes(RouteTable.Routes);
    ControllerBuilder.Current.SetControllerFactory(new MyControllerFactory());
}

That's all there is to it!

Customizing How Action Methods Are Selected and Invoked

You've just learned how the MVC Framework chooses which controller class should handle an incoming request, and how you can customize that logic by implementing your own controller factory. This takes care of the first half of Figure 10-4.

Now we'll move on to the second half of Figure 10-4. How does the controller base class, System.Web.Mvc.Controller, choose which action method to invoke, and how can you inject custom logic into that process? To proceed with this discussion, I need to reveal the shocking true story about how an action is not really the same as an action method.

The Real Definition of an Action

So far throughout this book, all of our actions have beenC# methods, and the name of each action has always matched the name of the C# method. Most of the time, that's exactly how things work, but the full story is slightly subtler.

Strictly speaking, an action is a named piece of functionalityon a controller. That functionality might be implemented as a method on the controller (and it usually is), or it might be implemented in some other way. The name of the action might correspond to the name of a method that implements it (and it usually does), or it might differ.

How does a controller method get counted as an action in the first place? Well, if you create a controller derived from the default controller base class, then each of its methods is considered to be an action, as long as it meets the following criteria:

  • It must be marked public and not marked static.

  • It must not be defined on System.Web.Mvc.Controller or any of its base classes (so this excludes ToString(), GetHashCode(), etc.).

  • It must not have a "special" name (as defined by System.Reflection.MethodBase's IsSpecialName flag). This excludes, for example, constructors, property accessors, and event accessors.

Note

Methods that take generic parameters (e.g., MyMethod<T>()) are considered to be actions, but the framework will simply throw an exception if you try to invoke one of them.

Using [ActionName] to Specify a Custom Action Name

As mentioned, an action is a named piece of functionality on a controller. The MVC Framework's usual convention is that the name of the action is taken from the name of the method that defines and implements that functionality. You can override this convention using ActionNameAttribute—for example:

[ActionName("products-list")]
public ActionResult DisplayProductsList()
{
    // ...
}

Under the default routing configuration, you would not find this action on the usual URL, /controllername/DisplayProductsList. Instead, its URL would be /controllername/products-list.

This is useful for two main reasons:

  • It creates the possibility of using action names that aren't legal as C# method names, such as in the preceding example. You can use any string as long as it's legal as a URL segment.

  • It allows you to have multiple C# methods that correspond to the same action name, and then use a method selector attribute (e.g., [HttpPost], described later in this chapter) to choose which one a given request should map to. This is a workaround for C#'s limitation of only allowing multiple methods to have the same name if they take a different set of parameters. You'll see an example of this shortly.

Note

Now you can appreciate why the MVC Futures generic URL-generating helpers (such as Html.ActionLink<T>()), which generate URLs based purely on .NET method names, don't entirely make sense and don't always work. This is why they are not included in the core MVC Framework.

Method Selection: Controlling Whether a C# Method Should Agree to Handle a Request

It's entirely possible for there to be multiple C# methods that are candidates to handle a single action name. Perhaps you have multiple methods with the same name (taking different parameters), or perhaps you are using [ActionName] so that multiple methods are mapped to the same action name. In this scenario, the MVC Framework needs a mechanism to choose between them.

This mechanism is called actionmethod selection, and is implemented using an attribute class called ActionMethodSelectorAttribute. You've already used one of the subclasses of that attribute, HttpPostAttribute, which prevents action methods from handling requests other than POST requests—for example:

[HttpPost]
public ActionResult DoSomething() { ... }

HttpPostAttribute, along with its friends HttpDeleteAttribute, HttpGetAttribute, and HttpPutAttribute, all work internally by calling an underlying selector attribute, AcceptVerbsAttribute. If you prefer, you can use [AcceptVerbs] directly—for example:

[AcceptVerbs(HttpVerbs.Get)]
public ActionResult DoSomething() { ... }

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult DoSomething(int someParam) { ... }

Here, there is just one logical action named DoSomething. There are two different C# methods that can implement that action, and the choice between them is made on a per-request basis according to the incoming HTTP method. Like all other action method selection attributes, AcceptVerbsAttribute and HttpPost are derived from ActionMethodSelectorAttribute.

Note

Method selector attributes may look like filter attributes (because they're both examples of attributes), but in fact they're totally unrelated to filters. Consider the request processing pipeline: method selection has to happen first, because the set of applicable filters isn't known until the action method has been selected.

Creating a Custom Action Method Selector Attribute

It's easy to create a custom action method selector attribute. Just derive a class from ActionMethodSelectorAttribute, and then override its only method, IsValidForRequest(), returning true or false depending on whether you want the action method to accept the request. Here's an example that handles or ignores requests based on whether the request appears to be coming from an iPhone:

public class iPhoneAttribute : ActionMethodSelectorAttribute
{
    public override bool IsValidForRequest(ControllerContext controllerContext,
                                           MethodInfo methodInfo)
    {
        var userAgent = controllerContext.HttpContext.Request.UserAgent;
        return userAgent != null && userAgent.Contains("iPhone");
    }
}

This means you can have two actions with the same name, and route requests to the appropriate one based on device type—for example:

[iPhone]
[ActionName("Index")]
public ActionResult Index_iPhone() { /* Logic for iPhones goes here */ }

[ActionName("Index")]
public ActionResult Index_PC() { /* Logic for other devices goes here */ }

Tip

As all C# programmers know, all methods on a class must have different names or must at least take a different set of parameters. This is an unfortunate restriction for ASP.NET MVC, because in the preceding example it would have made more sense if the two action methods had the same name (Index) and were distinguished only by one of them having an [iPhone] attribute. This is one of several places where ASP.NET MVC's heavy reliance on reflection and metaprogramming goes beyond what the .NET Framework designers originally planned for. In this example, you can work around it using [ActionName].

The idea with method selection is to select between multiple methods that can handle a single logical action. Do not confuse this with authorization. If your goal is to grant or deny access to a single action, then use an authorization filter instead. Technically, you could use an action method selector attribute to implement authorization logic, but that would be a poor way of expressing your intentions. Not only would it be confusing to other developers, but it would also lead to strange behavior when authorization was denied (i.e., causing a 404 Not Found error instead of a redirection to a login screen), and it wouldn't be compatible with output caching, as discussed earlier in this chapter.

Using the [NonAction] Attribute

Besides AcceptVerbsAttribute and its shorthand relatives (e.g., HttpPostAttribute), the MVC Framework ships with one other ready-made method selector attribute, NonActionAttribute. It is extremely simple—its IsValidForRequest() method just returns false every time. In the following example, this prevents MyMethod() from ever being run as an action method:

[NonAction]
public void MyMethod()
{
    ...
}

So, why would you do this? Remember that public instance methods on controllers can be invoked directly from the Web by anybody. If you want to add a public method to your controller but don't want to expose it to the Web, then as a matter of security, remember to mark it with [NonAction].

You should rarely need to do this, because architecturally it doesn't usually make sense for controllers to expose public facilities to other parts of your application. Each controller should normally be self contained, with shared facilities provided by your domain model or some kind of utility class library.

How the Whole Method Selection Process Fits Together

You've now seen that ControllerActionInvoker's choice of action method depends on a range of criteria, including the incoming RouteData.Values["action"] value, the names of methods on the controller, those methods' [ActionName] attributes, and their method selection attributes.

To understand how this all works together, examine the flowchart shown in Figure 10-5.

How ControllerActionInvoker chooses which method to invoke

Figure 10-5. How ControllerActionInvoker chooses which method to invoke

Notice that if a method has multiple action method selection attributes, then they must all agree to match the request; otherwise, the method will be ejected from the candidate list.

The figure also shows that the framework gives priority to methods with selector attributes (such as [HttpPost]). Such methods are considered to be a stronger match than regular methods with no selector attribute. What's the point of this convention? It means that the following code won't throw an ambiguous match exception:

public ActionResult MyAction() { ... }

[HttpPost]
public ActionResult MyAction(MyModel model) { ... }

Even though both methods would be willing to handle POST requests, only the second one has a method selector attribute. Therefore, the second one would be given priority to handle POST requests and the first one would be left to handle any other type of request.

Note

Ignore this note unless you really care about the details of method selection! When building the method candidate list, the framework actually considers a method to be aliased if it has any attribute derived from ActionNameSelectorAttribute (not to be confused with ActionMethodSelectorAttribute). Note that [ActionName] is derived from ActionNameSelectorAttribute. In theory, you could make a custom ActionNameSelectorAttribute and then use it to make an action method's name change dynamically at runtime. I don't think that most developers will want to do that, so I simplified the preceding discussion slightly by pretending that [ActionName] is the only possible type of ActionNameSelectorAttribute (for most people, the simplification is true, because it is the only built-in type of ActionNameSelectorAttribute).

Handling Unknown Actions

As shown in Figure 10-5, if there are no methods to match a given action name, then the default controller base class will try to run its unknown action handler. This is a virtual method called HandleUnknownAction(). By default, it returns a 404 Not Found response, but you can override it to do something different—for example:

public class HomeController : Controller
{
    protected override void HandleUnknownAction(string actionName)
    {
        Response.Write("You are trying to run an action called "
                       + Server.HtmlEncode(actionName));
    }
}

Now, if you request the URL /Home/Anything, you'll receive the following output instead of a 404 Not Found error:

You are trying to run an action called Anything

This is one of many places where ASP.NET MVC provides extensibility so that you have the power to do anything you want. However, in this case it isn't something you'll need to use often, for the following reasons:

  • HandleUnknownAction() is not a good way to receive an arbitrary parameter from a URL (as in the preceding example). That's what the routing system is for! Curly brace routing parameters are much more descriptive and powerful.

  • If you were planning to override HandleUnknownAction() in order to generate a custom 404 Not Found error page, then hold on—there's a better way! By default, the controller base class's HandleUnknownAction() method will invoke the core ASP.NET custom error facility anyway. For more details about how to configure custom errors, see the MSDN documentation at http://tinyurl.com/aspnet404.

Overriding HTTP Methods to Support REST Web Services

In recent years, many developers have chosen to implement their web services in the simple Representation State Transfer (REST) style, rather than following the older and more complex Simple Object Access Protocol (SOAP). REST attempts to give meanings to URLs, and uses the full range of HTTP methods, such as GET, POST, and DELETE, to specify operations on the business entities described by those URLs.

For example, you could do this by creating the following controller class:

public class PeopleController : Controller
{
    public ActionResult Index()
    {
        // Omitted: Return a list of all the Person records
    }

    // Handles GET requests to, e.g., http://hostname/people/4837
    [HttpGet] public ActionResult People(int personId)
    {
        // Omitted: Return data describing the corresponding Person record
    }

    // Handles POST requests to, e.g., http://hostname/people/4837
    [HttpPost] public ActionResult People(int personId, Person person)
    {
        // Omitted: Create or overwrite the corresponding Person record
    }

    // Handles DELETE requests to, e.g., http://hostname/people/4837
    [HttpDelete] [ActionName("People")]
    public ActionResult People_Delete(int personId) // To avoid name clash
    {
        // Omitted: Delete the corresponding Person record
    }
}

Now, if you add a routing entry as follows:

routes.MapRoute(null, "people/{personId}",
                new {controller = "People", action = "People"},
                new { personId = @"d+" /* Require ID to be numeric */ });

then each Person entity in your system has a unique address of the form /people/123, and clients can GET, POST, or DELETE entities at those addresses. This is a REST-style API.

This all works marvelously as long as all the clients who interact with your service are capable of using the full range of HTTP methods. Anyone making calls directly from server-side code written in .NET, Java, Ruby, or similar, or making calls from an Ajax application written in JavaScript running in a recent version of Firefox, Chrome, or Internet Explorer will have no problem with this.

But unfortunately, some mainstream client technologies, including plain old HTML forms and even Adobe Flash/Flex (based on the current version at the time of writing), are not capable of using arbitrary HTTP methods, and are limited to sending GET and POST requests.

ASP.NET MVC has a built-in workaround for these client limitations. If a client wishes to send, say, a DELETE request, it can do so by actually sending a POST request and adding an extra parameter called X-HTTP-Method-Override with the value set to DELETE. If ASP.NET MVC finds such a key/value pair in the query string, the form post collection, or the HTTP headers, then it will treat that value as overriding the actual HTTP method.

Submitting a Plain HTML Form with an Overridden HTTP Method

If you are writing the client for an ASP.NET MVC-powered REST web service, then you can use the Html.HttpMethodOverride() helper method to add the appropriate key/value pair to an HTML form. Continuing the previous example, you could write

<% using(Html.BeginForm("People", "People", new { personId = 123 })) { %>
    <%= Html.HttpMethodOverride(HttpVerbs.Delete) %>
    <input type="submit" value="Delete this person" />
<% } %>

This view code will render the following HTML:

<form action="/people/123" method="post">
    <input name="X-HTTP-Method-Override" type="hidden" value="DELETE" />
    <input type="submit" value="Delete this person" />
</form>

When this form is submitted, it will invoke the People_Delete() action method.

Note

HTTP method overriding only takes effect during POST requests. You can't simply set up a link to the URL /someUrl?X-HTTP-Method-Override=PUT and expect the GET request to be treated as a PUT request. The MVC Framework deliberately limits HTTP method overriding to POST requests because otherwise it would conflict with HTTP standards. As you learned in Chapter 8, GET requests should only perform read operations, so you shouldn't be encouraged to use an actual GET request if logically you're performing a delete or update operation.

How HTTP Method Overriding Works

The built-in method selectors ([HttpPut], [HttpPost], etc.) respect HTTP method overriding because when they need to know the incoming request's HTTP method, they don't look at Request.HttpMethod but instead call Request.GetHttpMethodOverride().

Request.GetHttpMethodOverride() uses the following algorithm:

  1. If the true HTTP method (i.e., Request.HttpMethod) is anything other than POST, it simply returns that HTTP method. The reason for this was explained in the note in the preceding section.

  2. Otherwise, it looks for a key/value pair called X-HTTP-Method-Override in the following dictionaries, in this priority order:

    • Request.Headers

    • Request.Form

    • Request.QueryString

  3. If it finds any X-HTTP-Method-Override value, and the value is something other than GET or POST (those values should never require HTTP method overriding), then it will return that value. Otherwise, it will return the true HTTP method.

Tip

If you want to respect HTTP method overriding in your own code, be sure to read the HTTP method using Request.GetHttpMethodOverride() and ignore the true HTTP method specified by Request.HttpMethod.

Boosting Server Capacity with Asynchronous Controllers

The core ASP.NET platform holds a pool of .NET threads called the worker thread pool, which it uses to handle incoming requests. For each incoming request, an available thread is taken from the pool and instructed to handle the request, and when it finishes, the thread is returned back to the pool.

Hopefully, your application can respond to most HTTP requests within a tiny fraction of a second. If that is the case, then even if you have a large number of concurrent users, the worker threads can complete their tasks very quickly, so only a small number of them will need to be busy at any given moment. This means that your server will be able to handle the load comfortably.

However, if the requests take a long time to process, then ASP.NET will need to use more worker threads simultaneously to handle the load. If you have a lot of worker threads working simultaneously (say, more than 40 per CPU in your server), then performance will suffer and the site will feel sluggish to end users. Ultimately, there is only a finite number of threads in the pool (it grows on demand, but is limited to 100 per CPU by default[65]), and if you hit this limit and continue queuing incoming requests, then the server will start returning "Server too busy" errors, and your potential users will return to Google to look for a competitor's web site. Clearly, you want to avoid this situation.

But why would your requests take a long time to process anyway? The most common reason is that you are performing long-running input/output (I/O) operations such as slow database queries or HTTP requests to external web services. The real frustration here is that your precious worker threads aren't really doing anything most of the time—they're just waiting for the I/O operations to complete—but that still blocks them from doing any other useful work.

Warning

If your server runs ASP.NET 3.5, it may not be possible for you to get any significant benefits from asynchronous controllers without making a crucial change to your MaxConcurrentRequestsPerCPU setting. This is explained in more detail toward the end of this section.

Introducing Asynchronous Requests

Since ASP.NET 2.0, the core platform has supported a notion of asynchronous requests. These requests begin as normal using a worker thread from the pool, but after they start up one or more I/O operations asynchronously, they immediately return the worker thread to the pool without waiting for the I/O to complete. Later, when all the I/O operations associated with that request have finished, ASP.NET grabs another worker thread from the pool, reattaches it to the original request's HttpContext, and lets it finish off processing the request.

The benefit of asynchronous requests is that no ASP.NET worker thread is being held up while the I/O is in progress. By comparison, a synchronous request is like an inefficient colleague who, after sending an e-mail message, can only stare blankly at the screen until he receives a reply rather than getting on with any other task.

Note

Asynchronous requests don't cause any individual request to complete faster. However long a request's external I/O takes to complete, the request can't finish any faster than that. The purpose of asynchronous requests is to allow your server to handle a greater number of such requests simultaneously without hitting thread pool size limits.

I should also point out that asynchronous requests aren't supposed to be used if your long-running operation is CPU bound (e.g., it's performing a complex calculation), because that operation will usually still consume a .NET worker thread, so the pressure on the pool is the same. Since there's a slight overhead in running an ASP.NET request asynchronously, you'd actually experience a net performance loss. Asynchronous requests are only beneficial when the background operation is I/O bound and can signal its completion without blocking a worker thread in the meantime.

Using Asynchronous Controllers

ASP.NET MVC supports asynchronous requests in the following three ways, though you're unlikely to use the first two:

  • Your routing configuration can include an entry whose RouteHandler property's GetHttpHandler() method returns an object that implements IHttpAsyncHandler. This lets you work with the underlying ASP.NET core platform's asynchronous request API. This builds directly on the routing system and bypasses ASP.NET MVC entirely.

  • You can create a custom controller type that implements IAsyncController. This is the asynchronous equivalent of IController.

  • Your controller can inherit from AsyncController rather than Controller. Note that AsyncController itself inherits from Controller and also implements IAsyncController.

The last option is by far the simplest. And just as a Controller adds many useful features on top of the bare-metal IController interface, AsyncController adds a flexible and convenient API for working with asynchronous requests on top of the bare-metal IAsyncController interface. So now, we'll focus exclusively on using AsyncController.

Turning a Synchronous Action into an Asynchronous Action

You may have a regular synchronous action method that performs long-running I/O, such as the following example. It calls a REST web service on Flickr, the popular photo sharing site, to obtain the URL of an image related to a user-supplied tag parameter.

const string FlickrSearchApi = "http://api.flickr.com/services/rest/?"
    + "method=flickr.photos.search"
    + "&text={0}"
    + "&sort=relevance"
    + "&api_key=" + /* Omitted - get your own API key from Flickr */;

public ContentResult GetPhotoByTag(string tag)
{
    // Make a request to Flickr
    string url = string.Format(FlickrSearchApi, HttpUtility.UrlEncode(tag));
    using (var response = WebRequest.Create(url).GetResponse())
    {
        // Parse the response as XML
        var xmlDoc = XDocument.Load(XmlReader.Create(response.GetResponseStream()));

        // Use LINQ to convert each <photo /> node to a URL string
        var photoUrls = from photoNode in xmlDoc.Descendants("photo")
                        select string.Format(
                            "http://farm{0}.static.flickr.com/{1}/{2}_{3}.jpg",
                            photoNode.Attribute("farm").Value,
                            photoNode.Attribute("server").Value,
                            photoNode.Attribute("id").Value,
                            photoNode.Attribute("secret").Value);

        // Return an <img> tag referencing the first photo
        return Content(string.Format("<img src='{0}'/>", photoUrls.First()));
    }
}

Of course, in a real application you'd probably pass the image URL to be rendered as part of a view, but to keep this example focused, let's just return an <img> tag directly from the action.

Now, if a user requests /controller/GetPhotoByTag?tag=stadium, they will be shown a relevant image such as that shown in Figure 10-6.

Output from the GetPhotoByTag() action method

Figure 10-6. Output from the GetPhotoByTag() action method

This is good, but you can't predict how long the REST call to Flickr will last. It might take several seconds or longer, so if you have a large number of users hitting this action at roughly the same time, it could block a large number of worker threads for a long time, possibly having a serious impact on your server's responsiveness or even making it totally unresponsive.

To convert this into an asynchronous action, you must first change your controller to inherit from AsyncController rather than Controller:

public class ImageController : AsyncController
{
    // Rest of controller as before
}

Note

AsyncController implements IAsyncController. This interface acts as a switch that tells the MVC Framework's request handler to enable asynchronous mode. Without this, ASP.NET MVC's default is to tell the underlying ASP.NET platform that the request will definitely complete synchronously, which has slightly less overhead but doesn't allow the worker thread to be released mid-request.

So far this won't have any noticeable effect on your application. The action will still work synchronously. But now that your controller inherits from AsyncController, you can split any of its actions into two parts:

  • One method named ActionNameAsync. This method should begin one or more asynchronous operations, using methods on AsyncManager.OutstandingOperations to say how many asynchronous operations have been started, and must then return void. As each I/O operation completes, tell the MVC Framework that the operation is finished by calling AsyncManager.OutstandingOperations.Decrement().

  • Another method named ActionNameCompleted. The framework will invoke this method when all of the I/O operations are finished (i.e., when AsyncManager.OutstandingOperations.Count reaches zero). This method can then return any ActionResult to send a response back to the browser.

Here's how this might work in the Flickr example:

public void GetPhotoByTagAsync(string tag)
{
    AsyncManager.OutstandingOperations.Increment();

    // Begin an asynchronous request to Flickr
    string url = string.Format(FlickrSearchApi, HttpUtility.UrlEncode(tag));
    WebRequest request = WebRequest.Create(url);
    request.BeginGetResponse(asyncResult =>
    {
        // This lambda method will be executed when we've got a response from Flickr

        using (WebResponse response = request.EndGetResponse(asyncResult))
        {
           // Parse response as XML, then convert to each <photo> node to a URL
           var xml = XDocument.Load(XmlReader.Create(response.GetResponseStream()));
           var photoUrls = from photoNode in xml.Descendants("photo")
                           select string.Format(
                               "http://farm{0}.static.flickr.com/{1}/{2}_{3}.jpg",
                               photoNode.Attribute("farm").Value,
                               photoNode.Attribute("server").Value,
                               photoNode.Attribute("id").Value,
                               photoNode.Attribute("secret").Value);
           AsyncManager.Parameters["photoUrls"] = photoUrls;

           // Now allow the Completed method to run
           AsyncManager.OutstandingOperations.Decrement();
}
    }, null);
}

public ContentResult GetPhotoByTagCompleted(IEnumerable<string> photoUrls)
{
    return Content(string.Format("<img src='{0}'/>", photoUrls.First()));
}

Note

Even though there are now two C# methods, GetPhotoByTagAsync() and GetPhotoByTagCompleted(), these are still treated as a single action called GetPhotoByTag. So, requests for this action should still go to /controller/GetPhotoByTag, and redirections to it should be generated by calling RedirectToAction("GetPhotoByTag"). The Async and Completed suffixes are only seen by the asynchronous request processor. Of course, you shouldn't also try to have a synchronous action with the same name (i.e., GetPhotoByTag()), as this will lead to an ambiguous match error.

If you want to add filters to this action, put them on the GetPhotoByTagAsync() method. Any filter attributes attached to the "completed" method will be ignored.

Instead of calling WebRequest's GetResponse() method, we're now calling its asynchronous alternative, BeginGetResponse() (if you're unfamiliar with this API, see the following sidebar).[66] The GetPhotoByTagAsync() method returns without waiting for any response from Flickr, so it frees the worker thread to get on with other tasks.

BeginGetResponse() allows you to supply a callback method that it should invoke once the WebRequest is completed. Inside this callback, we get the finished WebResponse object and use the XML data returned by Flickr to construct a set of image URLs and then store them in a temporary area called AsyncManager.Parameters. Finally, we inform ASP.NET MVC that the operation is complete by decrementing the count of outstanding operations, so it will invoke GetPhotoByTagCompleted(), passing the AsyncManager.Parameters values as method parameters.

In this example, we're only running one asynchronous operation. But of course you can run multiple asynchronous operations concurrently if you wish—just call AsyncManager.OutstandingOperations's Increment() method before each one starts, and Decrement() when each one finishes—and ASP.NET MVC will wait until the last one is done (i.e., as soon as AsyncManager.OutstandingOperations.Count hits zero) before invoking your "completed" method.

Passing Parameters to the Completion Method

As illustrated in the previous example, you can use the AsyncManager.Parameters dictionary to store the results of your asynchronous I/O operations. When the framework invokes your "completed" method, it will try to obtain a value for each parameter by looking for an entry in the dictionary with a matching name.

This mechanism doesn't use the value provider or model binding systems, so it won't automatically use Request.QueryString, Request.Form, or other incoming values to populate the parameters on your "completed" method. It will only pass values from AsyncManager.Parameters. If you do need to access a query string or form parameter in your "completed" method, you should add a line to your "async" method to transfer this value across—for example:

public void GetPhotoByTagAsync(string tag, string someOtherParam)
{
    AsyncManager.Parameters["someOtherParam"] = someOtherParam;

    // ... all else as before ...
}

public ContentResult GetPhotoByTagCompleted(IEnumerable<string> photoUrls,
                                             string someOtherParam)
{
    // ...
}

If the framework can't find a matching value for any "completed" method parameter, or if the value isn't of a compatible type, it will simply supply the default value for that type. For reference types this means null; for value types this means zero, false, or similar.

Controlling and Handling Timeouts

By default, ASP.NET MVC will not call your "completed" method until the AsyncManager associated with the request says there no outstanding asynchronous operations. It could take a long time, and it's possible that one or more asynchronous operations might never complete.

Warning

If the callback for one of your asynchronous I/O operations throws an exception before it calls AsyncManager.OutstandingOperations.Decrement(), then in effect it will never complete, and the request will keep waiting until it times out. You might want to put the Decrement() call inside a finally block.

AsyncManager has a built-in default timeout set to 45 seconds, so if the count of outstanding operations doesn't reach zero after this long, the framework will throw a System.TimeoutException to abort the request. You can alter this timeout duration using the [AsyncTimeout] filter—for example:

[AsyncTimeout(10000)]  // 10000 milliseconds equals 10 seconds
public void GetPhotoByTagAsync(string tag) { ... }

If you want to eliminate the timeout entirely, so that the I/O operations are allowed to run for an unlimited period, then use the [NoAsyncTimeout] filter instead. It's exactly equivalent to [AsyncTimeout(Timeout.Infinite)]. Also, in case you want to use custom logic to select a timeout duration, you can directly assign a timeout value (in milliseconds) to your asynchronous controller's AsyncManager.Timeout property.

Most applications will have an ASP.NET global exception handler that will deal with timeout exceptions in the same way as other unhandled exceptions. But if you want to treat timeouts as a special case and provide different feedback to the user, you can create your own exception filter that catches them, or you can override the controller's OnException() method. For example, you could redirect users to a special "Try again later" page:

protected override void OnException(ExceptionContext filterContext)
{
    if (filterContext.Exception is TimeoutException) {
        filterContext.Result = RedirectToAction("TryAgainLater");
        filterContext.ExceptionHandled = true;
    }
}

Using Finish() to Abort All Remaining Asynchronous Operations

You can short-circuit the entire collection of asynchronous operations associated with a request by calling AsyncManager.Finish() from one of your callbacks. This tells the framework to call your "completed" method immediately, without waiting for any outstanding operations to finish. It doesn't stop the current callback method or any other outstanding operation from running—it has no way of doing that—but the framework won't wait for them to signal completion.

Your "completed" method will usually expect to receive some parameters taken from AsyncManager.Parameters. If any of the expected parameters aren't already populated by the time the "completed" method gets called, then it will receive default values (null, zero, false, etc.) for those parameters.

Using Sync() to Transition Back to the Original HTTP Context

When you begin an asynchronous operation such as BeginGetResponse() and supply a callback parameter, you can't control which thread your callback will be invoked on. In general, it won't be an ASP.NET worker thread, and it won't be associated with your original request's HttpContext. This can lead to two possible problems:

  • If you call any code that depends on System.Web.HttpContext.Current (which isn't common in ASP.NET MVC controllers, but it can be done), you may get unexpected behavior because System.Web.HttpContext.Current could be null.

  • If you call any non-thread-safe properties or methods on objects associated with the original request, you could get race conditions, errors, or other unpredictable results. Note that the methods on AsyncManager.OutstandingOperations are thread safe, but AsyncManager.Parameters is internally just an object of type Dictionary<string, object>, which is not guaranteed to be thread safe.

To solve the first problem, AsyncManager provides a method called Sync(), which takes a delegate, runs it on an ASP.NET thread associated with the original HttpContext, and uses locking to ensure that only one such delegate runs at any time. You can call this from inside a callback as follows:

BeginAsyncOperation(asyncResult => {
    var result = EndAsyncOperation(asyncResult);

    // Can't always access System.Web.HttpContext.Current from here...

    Action doSomethingWithHttpContext = () => {
        // ... but can always access it from this delegate
    };
    if (asyncResult.CompletedSynchronously) // Already on an ASP.NET thread
        doSomethingWithHttpContext();
    else
                                            // Must switch to an ASP.NET thread
        AsyncManager.Sync(doSomethingWithHttpContext);

    AsyncManager.OutstandingOperations.Decrement();
}, null);

As an awkward quirk, you're not supposed to call Sync() from any thread that is already associated with ASP.NET, which is why the preceding code checks whether to invoke the doSomethingWithHttpContext delegate via Sync() or just to invoke it directly.

You could also use Sync() as a way of solving the second problem, because it only executes one delegate at a time. However, that's a heavyweight solution involving thread-switching and several extra lines of code. A simpler option is just to take a suitable lock before interacting with a non-thread-safe object—for example:

BeginAsyncOperation(asyncResult => {
    var result = EndAsyncOperation(asyncResult);

    lock(AsyncManager.Parameters) {
AsyncManager.Parameters["result"] = result;
    }

    AsyncManager.OutstandingOperations.Decrement();
}, null);

Normally, you will only need to worry about this if your request sets up multiple asynchronous operations that might complete simultaneously.

Adding Asynchronous Methods to Domain Classes

The Flickr example so far has been too simplistic, because I've assumed that you're willing to put all your logic directly into your controller. In fact, you are probably building on a multilayer or multicomponent architecture, so you would want to encapsulate access to the external REST service inside a separate class.

Fortunately, it's quite easy to create domain or service classes with BeginXyz/EndXyz methods that wrap some underlying asynchronous I/O. For example, you might adapt the previous example's code into a PhotoService class as follows:

public class PhotoService
{
    const string FlickrSearchApi = /* As before */;

    public IAsyncResult BeginGetPhotoUrls(string tag, AsyncCallback callback)
    {
        var url = string.Format(FlickrSearchApi, HttpUtility.UrlEncode(tag));
        var request = WebRequest.Create(url);
        return request.BeginGetResponse(callback, request);
    }

    public IEnumerable<string> EndGetPhotoUrls(IAsyncResult asyncResult)
    {
        WebRequest request = (WebRequest) asyncResult.AsyncState;
        using (WebResponse response = request.EndGetResponse(asyncResult))
        {
           var xml = XDocument.Load(XmlReader.Create(response.GetResponseStream()));
           return from photoNode in xml.Descendants("photo")
                  select string.Format(
                      "http://farm{0}.static.flickr.com/{1}/{2}_{3}.jpg",
                      photoNode.Attribute("farm").Value,
                      photoNode.Attribute("server").Value,
                      photoNode.Attribute("id").Value,
                      photoNode.Attribute("secret").Value);
        }
    }
}

Now you could call this from any number of asynchronous actions without those actions needing to understand anything about Flickr's API.

public void GetPhotoByTagAsync(string tag)
{
    AsyncManager.OutstandingOperations.Increment();
var photoService = new PhotoService();
photoService.BeginGetPhotoUrls(tag, asyncResult =>
    {
        var photoUrls = photoService.EndGetPhotoUrls(asyncResult);
        AsyncManager.Parameters["photoUrls"] = photoUrls;
        AsyncManager.OutstandingOperations.Decrement();
    });
}

You could use the same technique to wrap asynchronous access to long-running SQL database calls.

Choosing When to Use Asynchronous Controllers

Asynchronous actions are significantly more complex than normal synchronous ones. They involve writing a fair amount of extra code, are harder to read and maintain later, and create extra opportunities for subtle bugs. Plus, they make the framework call a lot more code at runtime. Asynchronous controllers are a good solution if your scenario meets the following conditions:

  • Your action must wait for I/O that supports asynchronous invocation: Don't use asynchronous controllers if you just want to run a set of CPU-bound tasks in parallel—you can simply use ThreadPool.QueueUserWorkItem() or .NET 4's Parallel.Invoke() for that.

  • You're actually experiencing problems due to excessive worker thread use (or load testing proves that you will): Most ASP.NET applications never use asynchronous requests and they still get along just fine.

  • You're willing to accept the added complexity: It will make your code harder to maintain. You will want to factor out as much logic as possible from your action so that you don't feel required to unit test it—asynchronous actions are hard to unit test.

  • You absolutely need to run the I/O on every request to your action: If you can avoid this by caching the I/O results, you can get far better performance both in terms of server capacity and response times for users. It depends on whether it's acceptable for you to return data that's possibly slightly out of date.

Measuring the Effects of Asynchronous Controllers

With all those caveats in mind, it's extremely valuable to run a real load test to see how much difference (if any) an asynchronous controller will make in your situation. Unless you can practically observe the difference, you won't truly know whether your server is configured to gain any benefit from it, and you won't know where the remaining performance limits are.

To illustrate the real effects and limitations of asynchronous controllers, I created a SQL stored procedure that simulates a long-running process by simply pausing for 2 seconds (using the T-SQL command WAITFOR DELAY '00:00:02') and then returning a fixed value. I set up two ASP.NET MVC controllers that call this stored procedure—one synchronously and the other asynchronously. Finally, I created a small C# console application that simulates an increasing workload by repeatedly making HTTP requests to a given URL; initially on just one thread, but gradually increasing the number of threads to 150 over a 30-minute period. It records a rolling average of the response times, from which I produced the graph shown in Figure 10-7.

Synchronous performance vs. asynchronous performance. Lower response times are better.

Figure 10-7. Synchronous performance vs. asynchronous performance. Lower response times are better.

Note

If you want to try running my simple load testing console application against your own web site, you can download it from my blog at http://tinyurl.com/mvcAsyncPerf.

To make the results clearer, I set my ASP.NET MVC application's maximum thread pool size to the artificially low limit of 50 (by putting ThreadPool.SetMaxThreads(50, 50); into Global.asax.cs). My dual-core server has a more sensible default thread pool limit of 200, but this doesn't change the principles. So, what can we observe from this graph?

  • Synchronous and asynchronous requests took exactly the same time to complete, as long as there were enough worker threads to handle all the concurrent requests.

  • With more than 50 clients, the synchronous requests had to wait in line for an available worker thread. The queuing time grew linearly with the number of clients, which is exactly like a queue at a supermarket. If the queue were twice as long, then on average you'd expect to wait in it for twice as long.

  • It might appear that for, say, 70 clients, synchronous requests performed only slightly worse than asynchronous ones. But that misses a crucial point: every single ASP.NET request becomes subject to this extra queuing time—not just the ones with the expensive database call! This means that a single slow action can make your entire site feel extremely sluggish. The asynchronous controller avoided this problem. Because its asynchronous action didn't block any worker threads, all other requests could be processed immediately, and the site remained perfectly responsive.

  • If you're wondering why the asynchronous requests had to start queuing with more than 100 clients, it's because SQL Server by default allows a maximum of 100 concurrent connections. This illustrates that no matter how well you set up your ASP.NET MVC asynchronous controllers, your capacity for concurrent requests will still always be limited by the capacity of whatever external resources they use.

Bear in mind that I was simulating a gradual increase in traffic over a 30-minute period. When instead I chose to simulate a more sudden spike in traffic, I found that asynchronous requests performed just the same, whereas synchronous ones performed very badly. My ASP.NET MVC 2 test application running on IIS 7 and .NET 4 took up to 10 minutes to notice the traffic and create enough worker threads to handle it synchronously, during which time the server was extremely unresponsive and most of the requests timed out. Of course, your results may vary depending on your system configuration.

Ensuring Your Server Is Configured to Benefit from Asynchronous Requests

If you plan to use ASP.NET 3.5 on your server, you should be aware that its default MaxConcurrentRequestsPerCPU setting will limit the maximum number of concurrent requests to 12 per CPU, no matter whether those requests are asynchronous or not. This is an incredibly unhelpful default value: it means that you're unlikely to get anywhere near the theoretical worker thread pool limit of 100 threads per CPU, so you won't get any significant benefit from using asynchronous requests. (But if you'll be using ASP.NET 4.0, you can stop worrying because your MaxConcurrentRequestsPerCPU setting is 5000 by default).

To change this setting on ASP.NET 3.5, you can do either of the following:

  • Use regedit to create a DWORD value called MaxConcurrentRequestsPerCPU at HKEY_LOCAL_MACHINESOFTWAREMicrosoftASP.NET2.0.50727.0, containing a large value such as 5000, or even 0 to mean "unlimited."

  • Edit your server's windowsMicrosoft.NETFrameworkv2.0.50727aspnet.config file to include the following:

    <system.web>
           <applicationPool maxConcurrentRequestsPerCPU="5000"
                               maxConcurrentThreadsPerCPU="0"
                               requestQueueLimit="5000"/>
    </system.web>

After changing either of these settings, reset IIS using by calling iisreset from the command line.

When I first performed the preceding investigation, I couldn't observe any performance benefit from using asynchronous requests. First it was because I was using Windows 7, and then it was because I was using Windows Server 2008 with ASP.NET 3.5 and hadn't yet changed the MaxConcurrentRequestsPerCPU setting. If I hadn't been trying to observe the benefit in a practical experiment, I'd never have known that it was completely ineffective. Be sure to verify practically that your implementation works as you expect.

Warning

Don't even bother trying to measure the affects of asynchronous requests using IIS on Windows XP, Vista, or 7. On these client operating systems, IIS won't process more than ten concurrent requests anyway—asynchronous or not. For performance testing, you must deploy your application to the intended server OS, which must be configured just as you intend to configure it when live.

Summary

In this chapter, you saw how to create reusable behaviors that you can tag on as filter attributes, how to implement a custom controller factory or customize action selection logic, and how to boost server capacity by minimizing your application's use of worker process threads with asynchronous actions. Altogether, this represents a wide range of extensibility options, so you should now be able to fit controllers and actions into almost any wider architecture or set of conventions according to your project requirements.

In the next chapter, you'll study the MVC Framework's built-in view engine, and your many options for transforming a Model object or a ViewData structure into a finished page of HTML.



[63] In practice, filters assigned to controllers run before filters assigned to action methods. Beyond that, the ordering is determined by the output of the .NET reflection method GetCustomAttributes(), which the framework uses internally to discover your filter attributes. That method can return attributes in a different order than they appear in your source code.

[64] As you'll learn in the next section, the behavior is different if an action filter or result filter marks an exception as handled: it prevents subsequent filters from even hearing about the exception.

[65] There's a lot of inconsistent information on the web about this default value. I obtained this figure by calling ThreadPool.GetMaxThreads() before and after changing the number of CPUs in my virtual machine. You can change the thread pool size limit using ThreadPool.SetMaxThreads().

[66] The following is an obscure detail, but it might help you to understand an odd behavior. The WebRequest class performs asynchronous GET requests very nicely, but its implementation of POST has a quirk. First, to perform an asynchronous POST request you must call BeginGetRequestStream() (not GetRequestStream()) to send the POST data asynchronously. Second, you should beware that BeginGetRequestStream() actually blocks the calling thread while performing a DNS lookup for the target server. This means it isn't truly asynchronous after all, and if your DNS server is slow or inaccessible, the call may fail.

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

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