Chapter 12. Controllers and Routing

Knowing how home plumbing works is unnecessary much of the time—but when you need to know it, you really need to know it.

While ASP.NET Web API provides a litany of helpful high-level features ranging from serialization and model binding to support for OData-style queries, the core job of all Web APIs, and ASP.NET Web API as a result, is to process HTTP requests and provide appropriate responses. Therefore, it is critical to understand the core mechanics of how an HTTP request flows from a client through the various elements of the ASP.NET Web API infrastructure and programming model, ultimately resulting in an HTTP response that can be sent back to the client.

This chapter focuses on that message flow, exploring the fundamental mechanics and supporting the programming model behind request handling and response generation. In addition, this chapter will look at the key types and insertion points that enable the framework to be extended to support custom message flow and processing schemes.

HTTP Message Flow Overview

The precise message flow through ASP.NET Web API will vary somewhat depending on the choice of host, and hosting is discussed in much greater detail in Chapter 10. However, at a high level, the framework components that participate in the HTTP message flow fall into two categories (as illustrated in Figure 12-1):

  • Components that rely only on the HTTP message for context
  • Components that rely on the higher-level programming model for context

The components that rely only on the core HTTP message context form the lower-level “message handler” pipeline. These components receive an HttpRequestMessage object from the hosting abstraction and are ultimately responsible for returning an HttpResponseMessage object.

By contrast, components that rely on the higher-level programming model have visibility into and are able to take advantage of programming framework abstractions, such as the controller and action methods as well as the parameters that map to the various elements of the HTTP request.

The message handler and controller pipelines
Figure 12-1. The message handler and controller pipelines

As mentioned, the low-level mechanics for activities such as selecting URL routes varies depending on the host. For example, when a Web API is hosted as part of an MVC application hosted on IIS, an HTTP message flows through the core routing infrastructure provided by ASP.NET. Conversely, when a Web API is self-hosted, the message flows through a WCF channel stack built around an HttpListener object. Regardless of the selected hosting option, a request will ultimately be converted to an instance of HttpRequestMessage and will be passed to an instance of HttpServer.

The Message Handler Pipeline

HttpServer is the entry point into the message handler pipeline for host-specific components. It initializes the pipeline from the handlers supplied by both global and route configuration data using the HttpClientFactory’s CreatePipeline method, as shown in Example 12-1.

Example 12-1. Initializing the MessageHandler pipeline
protected virtual void Initialize()
{
    // Do final initialization of the configuration.
    // It is considered immutable from this point forward.
    _configuration.Initializer(_configuration);

    // Create pipeline
    InnerHandler = HttpClientFactory.CreatePipeline(_dispatcher,
        _configuration.MessageHandlers);
}

Finally, because HttpServer itself derives from the DelegatingHandler class, it acts as the first handler in a message handler pipeline. The complete pipeline consists of HttpServer followed by any number of custom DelegatingHandler objects that you register with HttpConfiguration; followed by another special handler called HttpRoutingDispatcher; and finally, either a custom route-specific message handler (or another message handler pipeline built up with HttpClientFactory.CreatePipeline) supplied during route registration, or the default HttpControllerDispatcher message handler. HttpControllerDispatcher selects, creates, and dispatches the message to a controller instance. The pipeline is illustrated in Figure 12-2.

The MessageHandler Pipeline
Figure 12-2. The MessageHandler Pipeline

HttpServer establishes itself as the first node in the pipeline by setting the value returned from HttpClientFactory.CreatePipeline to its own InnerHandler property. This enables HttpServer to pass control to the next handler in the pipeline by calling the SendAsync method on its base class. This approach is consistent with all message handlers in the pipeline:

return base.SendAsync(request, cancellationToken)

The DelegatingHandler base class simply calls SendAsync on the object’s InnerHandler value. The inner handler processes the message in its SendAsync method and then repeats the process by calling SendAsync on its own InnerHandler. This process of calling the inner handler’s SendAsync method continues until the innermost handler is reached—which, in the case of a typical ASP.NET Web API, is the handler that dispatches the request to a controller instance. This style of pipeline, depicted in Figure 12-3, is sometimes referred to as a “Russian doll” because handlers are layered within one another, and request data flows from the outermost handler to the innermost handler (and then vice versa for response data) as a result of the outer handler directly calling its inner handler.[7]

The MessageHandler “Russian doll” model
Figure 12-3. The MessageHandler “Russian doll” model

Keep in mind that this entire data flow is asynchronous and therefore the value returned from SendAsync is a Task. In fact, its complete signature is as follows:

Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
    CancellationToken cancellationToken)

Participating in a task-based async pipeline can take some getting used to, and that topic is discussed in much more depth in Chapter 10. However, some basic guidelines are as follows for creating a task-based message handler.

  • To pass control on to the next, or inner, handler in the pipeline, simply return the value of calling SendAsync on the base class.
  • To stop all further processing of the message and return a response (also known as “short-circuiting” the request processing), return a new Task<HttpResponseMessage>.
  • To process the HTTP response as it flows back from the innermost handler to the outermost handler, append a continuation (implemented with the ContinueWith method) to the returned task. The continuation should take a single parameter to hold the task that is being continued and should return an HttpResponseMessage object. With version 4.5 and later of the .NET Framework, you can simplify working with asynchronous code using the async and await keywords.

As an example, consider the message handler shown in Example 12-2, which examines an HTTP GET request and determines whether the request is a conditional GET request (that is, a request that contains an if-none-match header). If the request is a conditional request, and the entity tag (ETag) contained in the request cannot be found in a local cache, this indicates to the handler that the value of the underlying resource state has changed. Therefore, the handler lets the request continue to flow through the pipeline and to the appropriate controller by calling and returning the value of base.SendAsync. This will ensure that the response to this GET request will contain the most up-to-date representation of the resource.

Example 12-2. MessageHandler for processing conditional GET requests with ETags
protected override Task<HttpResponseMessage> SendAsync(
    HttpRequestMessage request,
    CancellationToken cancellationToken)
{
    if (request.Method == HttpMethod.Get &&
        request.Headers.IfNoneMatch.Count > 0 &&
        (!IfNoneMatchContainsStoredEtagValue(request)))

        return base.SendAsync(request, cancellationToken).ContinueWith(task => {
            var resp = task.Result;
            resp.Headers.ETag = new EntityTagHeaderValue(
                _eTagStore.Fetch(request.RequestUri));
            return resp;
        });
    }

    ...

    //by default, let the request keep moving through the message handler pipeline
    return base.SendAsync(request, cancellationToken);
}

The handler also adds a continuation to the returned task so that it can create and apply a new ETag value to the response message. This new ETag value can then be passed and validated for future requests to the resource.

Dispatcher

The final stage of the message handler pipeline is the dispatching stage. In earlier versions of ASP.NET Web API, this stage was predefined to select a controller from the information supplied by the route data, get an instance of the controller, and then pass the HTTP message and context to the controller for processing by the controller’s execution logic. You could still circumvent the controller programming model by simply adding a custom message handler that returned a new Task object. However, that message handler needed to be added in the global HttpConfiguration object, meaning that it needed to process every HTTP request sent to the Web API.

To enable message handlers to be configured on a per-route basis, as well as to enable different Web API frameworks that may use a different higher-level abstraction than IHttpController, the team added a level of indirection to the dispatching process. HttpServer composes an instance of HttpRoutingDispatcher as the last node in the message handler pipeline. As the following excerpt from the product source code illustrates, HttpRoutingDispatcher is responsible for invoking either a custom message handler supplied by the route or, alternately, the default HttpControllerDispatcher. Because the dispatcher derives from HttpMessageHandler, which cannot be directly invoked, the HttpRoutingDispatcher code wraps the instance in an HttpMessageInvoker object so that it can be executed:

var invoker = (routeData.Route == null || routeData.Route.Handler == null) ?
    _defaultInvoker : new HttpMessageInvoker(routeData.Route.Handler,
        disposeHandler: false);
return invoker.SendAsync(request, cancellationToken);

Route-specific message handlers are declared as a part of the route configuration itself. For example, consider the following route registration code:

public static void Register(HttpConfiguration config)
{
    config.Routes.MapHttpRoute("customHandler", "custom/{controller}/{id}",
        defaults: new {id = RouteParameter.Optional},
        constraints: null,
        handler: HttpClientFactory.CreatePipeline(
                    new HttpControllerDispatcher(config),
            new[] {new MyHandler()})
    );

    ...
}

In addition to the standard route configuration and registration code, the customHandler route provides a custom message handler as the last parameter to MapHttpRoute. However, the code actually does more than simply register an instance of the custom MyHandler message handler. It uses the HttpClientFactory.CreatePipeline helper method to compose MyHandler with the default HttpControllerDispatcher message handler. This is an important point to keep in mind when you’re inserting route-specific message handlers. If a custom message handler is supplied to HttpRoutingDispatcher, that message handler becomes responsible for any and all further processing of the HTTP message. The CreatePipeline method accepts the desired “final destination” message handler as its first argument followed by a list of all the additional message handlers to be composed into the pipeline. The method then wires the message handlers together by setting the InnerHandler property of the individual message handlers, and returns the first message handler in the chain. In this example, the chain consists of MyHandler followed by HttpControllerDispatcher. Keep in mind that for a message handler pipeline to be created like this, all message handlers except for the innermost handler must derive from DelegatingHandler rather than directly from HttpMessageHandler, since the DelegatingHandler supports composition via its InnerHandler property.

HttpControllerDispatcher

By default, the final stop in the message handler pipeline will be the HttpControllerDispatcher. This handler is the glue that binds together the message handler pipeline with the higher-level programming model elements of controllers and actions (we will call this the controller pipeline). The HttpControllerDispatcher has three responsibilities:

  • Select a controller using an object that implements IHttpControllerSelector.
  • Get an instance of a controller using an object that implements IHttpControllerActivator.
  • Execute the controller instance, passing it a controller context object that is composed of the current configuration, route, and request context.

To fulfill these responsibilities, HttpControllerDispatcher relies on two noteworthy types. These are types that implement the IHttpControllerSelector interface and types that implement the IHttpControllerActivator interface.

Controller Selection

As its title suggests, the responsibility of the IHttpControllerSelector is to select the proper controller based on the HTTP request. ASP.NET Web API supplies a default implementation with the DefaultHttpControllerSelector class. This class uses the following algorithm for choosing the controller:

  • Determine whether the controller can be identified directly from the route data. This condition is true when attribute-based routing is used.
  • Check whether the controller name is valid. If it is either null or an empty string, throw a 404 response exception.
  • Using the controller name, look for a matching HttpControllerDescriptor in its controller info cache and return it.

The controller info cache is a dictionary of controller names and HttpControllerDescriptor objects that is initialized when the cache is first accessed. During initialization, the controller info cache uses an instance of the HttpControllerTypeCache, which in turn uses an object that implements the IHttpControllerTypeResolver for iterating assemblies and types and building up a list of all valid controller types. By default, Web API uses DefaultHttpControllerTypeResolver, which selects as valid controllers any types that meet the following conditions:

  • The type is a class.
  • The class is public.
  • The class is not abstract.
  • The class implements or derives from a class that implements the IHttpController interface.
  • The class name ends with the string "Controller".

Because controllers are discovered when the DefaultHttpControllerSelector info cache is first accessed, any failure to find exactly one match for the requested controller name indicates an error in the default controller selection logic. For example, if no entries are found for the requested controller name, the framework returns an HTTP 404 Not Found response to the client. If, on the other hand, more than one entry is found for the requested controller name, then the framework throws an InvalidOperationException for the ambiguous match.

Assuming that the requested controller name matches a single entry in the default controller selector’s info cache, the controller selector returns the corresponding HttpControllerDescriptor value to the calling HttpControllerDispatcher.

One additional thing to note here is that the lifetime of the controller descriptor is the duration of the HttpConfiguration object, which practically speaking means that the controller descriptor lifetime is the lifetime of the application.

Supporting attribute-based routes

Web API 2 added the ability to specify routes as attributes. These attributes can be applied to both controller classes and action methods, and this declarative approach to routing adds to the purely convention-based approach of matching controllers and actions by route parameters and naming conventions.

Mechanically, using attribute-based routing is a two-step process. The first step is decorating controllers and/or actions with RouteAttribute and supplying the appropriate route template values. The second step is to have Web API map those attribute values to actual route data, which the framework can then use when processing requests.

For example, consider the following basic greeting Web API:

public class GreetingController : ApiController
{
    // mapped to GET /api/greeting by default
    public string GetGreeting()
    {
        return "Hello!";
    }
}

Using attribute-based routing, we could map this controller and action to a completely different URL without requiring a change to the global route configuration rules:

public class GreetingController : ApiController
{
    // mapped to GET /services/hello
    [Route("services/hello")]
    public string GetGreeting()
    {
        return "Hello!";
    }
}

In order to ensure that attribute-based routes are correctly added to Web API’s route configuration, we must call the MapHttpAttributeRoutes method on HttpConfiguration as follows:

config.MapHttpAttributeRoutes();

The approach of integrating attribute routes at configuration time enables fewer modifications to the other framework components. For example, because all of the complexity for parsing and managing attribute-based route values is managed at the RouteData level, the impact to DefaultHttpControllerSelector is limited to the following:

controllerDescriptor = routeData.GetDirectRouteController();
if (controllerDescriptor != null)
{
    return controllerDescriptor;
}

As the code indicates, if a controller and/or action is explicitly known from the route as a result of matching an attribute-based route, the HttpControllerDescriptor is immediately selected and returned. Otherwise, the convention-based controller selection process attempts to find and select a controller based on the type name.

Plugging in a custom controller selector

While the default logic for selecting a controller will be sufficient for the majority of Web API development scenarios, there may be cases where it is beneficial to supply a custom selection strategy.

Overriding the default controller selection strategy requires creating a new controller selector service and then configuring it to be used by the framework. Creating a new controller selector is simply a matter of authoring a class that implements the IHttpControllerSelector interface, which is defined as follows:

public interface IHttpControllerSelector
{
        HttpControllerDescriptor SelectController(HttpRequestMessage request);
        IDictionary<string, HttpControllerDescriptor> GetControllerMapping();
}

As the name indicates, the SelectController method has the primary responsibility of choosing a controller type for a supplied HttpRequestMessage and returning an HttpControllerDescriptor object. The GetControllerMapping method adds a secondary responsibility to the controller selector to return the entire set of controller names and their corresponding HttpControllerDescriptor objects as a dictionary. To date, however, this responsibility is exercised only by ASP.NET Web API’s API explorer feature.

We configure a custom controller selector to be used by the framework through the HttpConfiguration object’s Services collection. For example, the following code snippet illustrates how to replace the default controller selector logic, which looks for the suffix Controller in the class name, with a strategy that allows the developer to specify a custom suffix:[8]

const string controllerSuffix = "service";

config.Services.Replace(
    typeof(IHttpControllerSelector),
    new CustomSuffixControllerSelector(config, controllerSuffix));

Controller Activation

Once the controller selector finds and returns an HttpControllerDescriptor object to the dispatcher, the dispatcher gets an instance of the controller by calling the CreateController method on HttpControllerDescriptor. That method, in turn, delegates the responsibility of creating or getting a controller instance to an object that implements the IHttpControllerActivator interface.

The IHttpControllerActivator has the single responsibility of creating controller instances, and this is reflected in its definition:

public interface IHttpControllerActivator
{
    IHttpController Create(HttpRequestMessage request,
        HttpControllerDescriptor controllerDescriptor,
        Type controllerType);
}

Similarly to controller selection, the default logic for controller activation is implemented in the class DefaultHttpControllerActivator and registered with the framework in the DefaultServices constructor.

The default controller activator creates controller objects via one of two methods. It first attempts to create an instance using the ASP.NET Web API dependency resolver. The dependency resolver is an implementation of the IDependencyResolver interface, and provides a general mechanism for the framework to externalize tasks like creating objects and managing object lifetime. In ASP.NET Web API, this is also the mechanism used to plug in inversion-of-control (IOC) containers such as Ninject and Castle Windsor. An instance of a dependency resolver is registered with the framework through the DependencyResolver property on the HttpConfiguration object, and when the property is non-null, the framework will call methods such as GetService(Type serviceType) in order to create object instances rather than create instances of those types directly. This facility can promote more loosely coupled and extensible designs, both for the ASP.NET Web API Framework itself and for your own services.

In the event that a dependency resolver has not been registered with the framework or if the dependency resolver cannot create an instance for the requested controller type, the default controller activator attempts to create an instance of the supplied controller type by executing the type’s parameterless constructor.

After the instance of the controller is created by the controller activator, it is passed back to the controller dispatcher. The dispatcher then passes control flow into the controller pipeline by calling ExecuteAsync on the controller object as follows:

return httpController.ExecuteAsync(controllerContext, cancellationToken);

Like the majority of the components discussed, the controller’s ExecuteAsync method is asynchronous and returns an instance of Task, thereby helping to improve the throughput of the Web API framework itself, as none of its components will block the thread of execution with I/O operations. This efficiency enables the framework to handle an increased number of requests given a finite number of computing resources.

The Controller Pipeline

While the message handler pipeline provides abstractions for the lower-level processing of HTTP requests and responses, the controller pipeline enables a developer to work with higher-level programming abstractions such as controllers, actions, models, and filters. Orchestrating all of the objects used in processing requests and responses is the controller instance itself—hence the term controller pipeline.

ApiController

At a foundational level, an ASP.NET Web API controller is any class that implements the IHttpController interface. This interface consists of a single, asynchronous execute method, which by default is called by the underlying dispatcher:

public interface IHttpController
{
    Task<HttpResponseMessage> ExecuteAsync(
        HttpControllerContext controllerContext,
        CancellationToken cancellationToken);
}

While this simple interface provides a great deal of flexibility in its simplicity, it is devoid of much of the functionality that ASP.NET developers have grown accustomed to. This kind of functionality includes capabilities like authorization, model binding, and validation. In order to provide these capabilities while preserving the simplicity of the interface and reducing the amount of coupling between the message handler pipeline and the controller pipeline, the ASP.NET Web API team created the ApiController base class. ApiController extends the core controller abstraction and provides two types of services to the controller classes that derive from it:

  • A processing model that includes filters, model binding, and action methods.
  • Additional context objects and helpers. These include objects for the underlying configuration, request message, model state, and others.

ApiController Processing Model

The processing model orchestrated by ApiController is made up of several different stages, and, like the lower-level message handler pipeline, provides many different points for extending the default data flow with custom logic.

In general, the controller pipeline enables an action method to be selected for processing a request, maps properties of the request to the parameters of the selected method, and allows for the execution of a variety of filter types. Request processing through ApiController looks similar to Figure 12-4.

The controller pipeline
Figure 12-4. The controller pipeline

Similar to the message handler pipeline, the controller pipeline constructs a “Russian doll” structure wherein a request flows from an outermost scope through a series of nested scopes to the action method, which is the innermost scope. The action method generates a response, and that response flows back from the innermost scope to the outermost scope. Scopes in the controller pipeline are implemented via filters, and just as with the message handler pipeline, all components of the controller pipeline are implemented asynchronously via tasks. This is evident in pipeline interfaces such as IActionFilter:

public interface IActionFilter : IFilter
{
    Task<HttpResponseMessage> ExecuteActionFilterAsync(
        HttpActionContext actionContext,
        CancellationToken cancellationToken,
        Func<Task<HttpResponseMessage>> continuation);
    }
}

Filters will be described in more detail later in the chapter. First, we will explore the process for selecting the correct action method on the controller based on aspects of the request.

Action selection

One of the first steps taken inside of the ApiController.ExecuteAsync method is action selection. Action selection is the process of selecting a controller method based on the incoming HttpRequestMessage. As in the case of controller selection, action selection is delegated to a type whose primary responsibility is action selection. This type can be any class that implements the IHttpActionSelector interface. The signature for IHttpActionSelector looks as follows:

public interface IHttpActionSelector
{
    HttpActionDescriptor SelectAction(HttpControllerContext controllerContext);

    ILookup<string, HttpActionDescriptor> GetActionMapping(
        HttpControllerDescriptor controllerDescriptor);
}

As in the case of IHttpControllerSelector, the IHttpActionSelector technically has two responsibilities: selecting the action from the context and providing a list of action mappings. The latter responsibility enables the action selector to participate in ASP.NET Web API’s API explorer feature.

We can easily supply a custom action selector by either explicitly replacing the default action selector (discussed next) or using a dependency resolver—generally in concert with an IoC container. For example, the following uses the Ninject IoC container to replace the default action selector with a custom selector, appropriately named CustomActionSelector:

var kernel = new StandardKernel();
kernel.Bind<IHttpActionSelector>().To<CustomActionSelector>();

config.DependencyResolver = new NinjectResolver(kernel);

In order to decide whether it makes sense to supply a custom action selector, you must first understand the logic implemented by the default action selector. The default action selector provided by Web API is the ApiControllerActionSelector. Its implementation is effectively a series of filters that are expected to yield a single action from a list of candidate actions. The algorithm is implemented in ApiControllerActionSelector’s FindMatchingActions method and is illustrated in Figure 12-5.

Default action selector logic
Figure 12-5. Default action selector logic

The initial and pivotal decision point in the default action selection algorithm is whether or not the matched routes are standard routes (i.e., those declared in global Web API configuration code via methods such as MapHttpRoute), or whether they are attribute-based routes created as a result of decorating an action with the RouteAttribute attribute.

public class ValuesController
{
    [ActionName("do")]
    public string ExecuteSomething() {
        ...
    }
}

If no action methods are found that match the value of the action route parameter, an HTTP 404 Not Found response is returned. Otherwise, the list of matched actions is then filtered once more to remove actions that are not allowed for the specific method associated with the incoming request and returned as the initial action candidate list.

If the route data does not have an explicit entry for the action method, then the initial action candidate selection logic tries to infer the action method name from the HTTP method name. For example, for a GET request, the selector will search for qualified action methods whose name begins with the string "Get".

If the request matches a route that has been declared via attribute-based routing, the initial candidate action list is provided by the route itself and then filtered to remove those candidate actions that are not appropriate for the HTTP method of the incoming request.

After establishing the initial list of candidate action methods, the default action selection logic executes a sequence of refinements to narrow down the list of candidate actions to exactly one. Those refinements are as follows:

  • Filter out those methods that do not contain the set of parameters required for the matched route.
  • Narrow the list of candidate actions to the actions with the lowest evaluation order. You can control the order of candidate actions for attribute-based routing using the RouteAttribute’s Order property. By default, Order is set to zero, resulting in the entire set of candidate actions being returned from this refinement stage.
  • Narrow the list of candidate actions to those with the highest precedence. Precedence is used for attribute-based routes and is determined algorithmically by the RoutePrecedence.Compute function based on the matched route.
  • Group the remaining list of candidate actions by the number of parameters and then return the first candidate action from the group with the most parameters.

At this point, a single action method should be all that remains in the list of candidate actions. Hence, the default action selector performs a final check and takes one of the following three actions based on the number of candidate actions returned:

  • If 0, return an HTTP 405 message if a candidate action exists but is not allowed for the HTTP method of the request; return an HTTP 404 message if there is no matching action.
  • If 1, return the matching action descriptor so that it can be invoked.
  • If > 1, throw an InvalidOperationException for an ambiguous match.

Filters

As depicted in Figure 12-4, filters provide a set of nested scopes that you can use to implement functionality that cuts across multiple controllers or actions. While conceptually similar, filters are broken down into four categories based on when they are run and what kind of data they have access to. The four categories are authentication filters, authorization filters, action filters, and exception filters. This factoring is illustrated in Figure 12-6.

Filter classes
Figure 12-6. Filter classes

At a fundamental level, a filter in Web API is any class that implements the IFilter interface, which consists of a single method:

public interface IFilter
{
    bool AllowMultiple { get; }
}

In addition to providing the IFilter interface, Web API provides a base class that implements the interface and also derives from the .NET Framework’s Attribute class. This enables all derived filters to be added in one of two ways. First, they can be added directly to the global configuration object’s Filters collection as follows:

config.Filters.Add(new CustomActionFilter());

Alternately, filters that derive from FilterAttribute can be added as an attribute to either a Web API controller class or action method as follows:

[CustomActionFilter]
public class ValuesController : ApiController
{
    ...
}

Regardless of how filters are applied to a Web API project, they are stored in the HttpConfiguration.Filters collection, which stores them as a collection of IFilter objects. This generic design enables the HttpConfiguration object to contain many different types of filters, including filter types that go beyond the four current categories of authentication filter, authorization filter, action filter, and exception filter. Such new filter types can be created and added to HttpConfiguration without breaking the application or requiring changes to HttpConfiguration. They can then be later discovered and run by a new, custom controller class.

The ordering and execution of filters is orchestrated by ApiController based on the following criteria:

Filter type
ApiController groups filters by type and executes each group as a different nested scope, as shown in Figure 12-4.
Where applied
Filters added as a part of global configuration (HttpConfiguration.Filters.Add(..)) are added to the Filters collection before filters added as attributes (ActionFilterAttribute, AuthorizationFilterAttribute, ExceptionFilterAttribute) at the class or action method level. Filters added via attribute to the controller class are added before filters added to action methods. When filters are run, they execute in the reverse order of how they were added. Therefore, filters added globally are run first, followed by filters added via attribute to the controller, followed by filters added to the action method.
Order added
After filters have been grouped, they are executed within the group based on the order in which they were added.

Authentication filters

Authentication filters have two responsibilities. First, they examine a request as it flows through the pipeline and validate a set of claims to establish an identity for the calling user. In the event that the identity cannot be established from the provided claims, the authentication filter may also be used to modify the response to provide further instructions to the user agent for establishing a user’s identity. This response is known as a challenge response. Authentication filters are discussed at greater length in Chapter 15.

Authorization filters

Authorization filters apply policy to enforce the level to which a user, client application, or other principal (in security terms) can access an HTTP resource or set of resources provided by Web API. The technical definition of an authorization filter is simply a class that implements the IAuthorizationFilter interface. This interface contains a single method for running the filter asynchronously:

Task<HttpResponseMessage> ExecuteAuthorizationFilterAsync(
    HttpActionContext actionContext,
    CancellationToken cancellationToken,
    Func<Task<HttpResponseMessage>> continuation);

While this interface is the only requirement for running authorization filters, it is not the most developer-friendly programming model—largely because of the complexities associated with asynchronous programming and the .NET Framework’s task API. As a result, Web API provides the AuthorizationFilterAttribute class. This class implements the IAuthorizationFilter interface as well as the ExecuteAuthorizationFilterAsync method. It then provides the following virtual method, which can be overridden by derived classes:

public virtual void OnAuthorization(HttpActionContext actionContext);

The AuthorizationFilterAttribute calls OnAuthorization from its ExecuteAuthorizationFilterAsync method, which enables derived authorization filters to be written in a more familiar, synchronous style. After calling OnAuthorization, the base class then examines the state of the HttpActionContext object and decides whether to let request processing continue or whether to return a new Task with a new HttpResponseMessage indicating an authorization failure.

When you are authoring a custom authorization filter that is derived from AuthorizationFilterAttribute, the way to indicate an authorization failure is to set an HttpResponseMessage object instance on actionContext.Response, as follows:

public class CustomAuthFilter : AuthorizationFilterAttribute
{
   public override void OnAuthorization(HttpActionContext actionContext)
   {
      actionContext.Response = actionContext.Request.CreateErrorResponse(
         HttpStatusCode.Unauthorized, "denied");
   }
}

When the call to OnAuthorize is finished, the AuthorizationFilterAttribute class uses the following code to analyze the state of the context and either continue processing or return a response immediately:

if (actionContext.Response != null)
{
    return TaskHelpers.FromResult(actionContext.Response);
}
else
{
    return continuation();
}

Additionally, if an exception is thrown from within OnAuthorize, the AuthorizationFilterAttribute class will catch the exception and halt processing, returning an HTTP response with the 500 Internal Server Error response code:

try
{
    OnAuthorization(actionContext);
}
catch (Exception e)
{
    return TaskHelpers.FromError<HttpResponseMessage>(e);
}

When you’re designing and implementing a new authorization filter, a good starting point is the existing AuthorizeAttribute provided by Web API. This attribute uses Thread.CurrentPrincipal to get the identity (and optionally role membership information) for an authenticated user and then compares it to policy information provided in the attribute’s constructor. Additionally, an interesting detail to note about the AuthorizeAttribute is that it checks for the presence of the AllowAnonymousAttribute on both the action method and the containing controller, and exits successfully if the attribute is present.

Action filters

Action filters are conceptually very similar to authorization filters. In fact, the signature of the IActionFilter execute method looks identical to the corresponding method on IAuthorizationFilter:

Task<HttpResponseMessage> IActionFilter.ExecuteActionFilterAsync(
    HttpActionContext actionContext,
    CancellationToken cancellationToken,
    Func<Task<HttpResponseMessage>> continuation)

Action filters differ from authorization filters in a couple of respects, however. The first difference is when action filters will be called. As we discussed earlier, filters are grouped by type and different groups are executed at different times. Authorization filters are run first, followed by action filters, followed by exception filters.

The second, and more notable, difference between action filters and the other two filter types is that action filters give the developer the ability to process a request on both sides of the call to the action method. This capability is exposed by the following two methods on the ActionFilterAttribute class:

public virtual void OnActionExecuting(HttpActionContext actionContext);

public virtual void OnActionExecuted(
      HttpActionExecutedContext actionExecutedContext);

Just like in the case of the other filter types, ActionFilterAttribute implements the IActionFilter interface to abstract the complexities of working directly with the Task API as well as to derive from Attribute, thereby enabling action filters to be applied directly to controller classes and action methods.

As a developer, you can easily create action filters by simply deriving from ActionFilterAttribute and overriding either or both the OnActionExecuting and OnActionExecuted methods. For example, Example 12-3 performs some basic auditing of action methods.

Example 12-3. An example action filter that audits action methods
public class AuditActionFilter : ActionFilterAttribute
{
   public override void OnActionExecuting(HttpActionContext c)
   {
      Trace.TraceInformation("Calling action {0}::{1} with {2} arguments",
         c.ControllerContext.ControllerDescriptor.ControllerName,
         c.ActionDescriptor.ActionName,
         c.ActionArguments.Count);
   }

   public override void OnActionExecuted(HttpActionExecutedContext c)
   {
      object returnVal = null;
      var oc = c.Response.Content as ObjectContent;
      if (oc != null)
         returnVal = oc.Value;

      Trace.TraceInformation("Ran action {0}::{1} with result {2}",
        c.ActionContext.ControllerContext.ControllerDescriptor.ControllerName,
        c.ActionContext.ActionDescriptor.ActionName,
        returnVal ?? string.Empty);
   }
}

If an exception is thrown from within an action filter, a Task<HttpResponseMessage> containing the error is created and returned, thereby halting request processing by other components in the pipeline. This is consistent with the logic discussed for authorization filters. Because action filters enable processing both before and after the action method, ActionFilterAttribute contains additional logic to nullify the context of the response message if an exception is thrown in the OnActionExecuted side of the filter. It accomplishes this by simply wrapping the call to OnActionExecuted in a try..catch block and setting the response to null in the catch block:

try
{
   OnActionExecuted(executedContext);
   ...
}
catch
{
   actionContext.Response = null;
   throw;
}

Exception filters

Exception filters exist for the purpose of, as their name suggests, enabling the custom handling of exceptions that are thrown during the controller pipeline. As in the case of authorization and action filters, you define exception filters by implementing the IExceptionFilter interface. Additionally, the framework provides the base class, ExceptionFilterAttribute, which provides .NET Framework attribute capabilities and a more simplified programming model to classes that derive from it.

Exception filters can be extremely helpful in preventing the flow of potentially sensitive information outside of your service. As an example, database exceptions typically contain details identifying your database server or schema design. This kind of information could be used by an attacker to launch attacks against your database. The following is an example of an exception filter that logs exception details to the .NET Framework’s diagnostics system and then returns a generic error response:

public class CustomExceptionFilter : ExceptionFilterAttribute
{
   public override void OnException(
      HttpActionExecutedContext actionExecutedContext)
   {
      var x = actionExecutedContext.Exception;

      Trace.TraceError(x.ToString());

      var errorResponse = actionExecutedContext.Request.CreateErrorResponse(
         HttpStatusCode.InternalServerError,
         "Please contact your server administrator for more details.");

      actionExecutedContext.Response = errorResponse;
   }
}

When you apply this filter (globally, or at the controller or action level), action methods such as the following:

[CustomExceptionFilter]
public IEnumerable<string> Get()
{
   ...
   throw new Exception("Here are all of my users credit card numbers...");
}

will return a sanitized error message to the client:

$ curl http://localhost:54841/api/values
{"Message":"Please contact your server administrator for more details."}

But what happens if your exception filter throws an exception? More broadly, what if you don’t have an exception filter? Where are unhandled exceptions ultimately caught? The answer is the HttpControllerDispatcher. If you remember from earlier in the chapter, the HttpControllerDispatcher is by default the last component in the message handler pipeline, and it is responsible for calling the ExcecuteAsync method on a Web API controller. In addition, it wraps this call in a try..catch block, as shown here:

protected override async Task<HttpResponseMessage> SendAsync(
   HttpRequestMessage request, CancellationToken cancellationToken)
{
   try
   {
      return await SendAsyncCore(request, cancellationToken);
   }
   catch (HttpResponseException httpResponseException)
   {
      return httpResponseException.Response;
   }
   catch (Exception exception)
   {
      return request.CreateErrorResponse(HttpStatusCode.InternalServerError,
          exception);
   }
}

As you can see, the dispatcher is capable of returning the HttpResponseMessage object attached to an HttpResponseException. Additionally, in the case where an unhandled exception is caught, the dispatcher contains a generic exception handler that turns the exception into an HttpResponseMessage containing details of the exception along with an HTTP 500 Internal Server Error response code.

Model binding and validation

Chapter 13 will focus on model binding, so we will not spend a significant amount of time describing it here. However, the key point from the perspective of the controller pipeline is that model binding occurs just before the action filters are processed, as shown by the following fragment from the ApiController class source code:

private async Task<HttpResponseMessage> ExecuteAction(
   HttpActionBinding actionBinding, HttpActionContext actionContext,
   CancellationToken cancellationToken, IEnumerable<IActionFilter> actionFilters,
   ServicesContainer controllerServices)
{

   ...

   await actionBinding.ExecuteBindingAsync(actionContext, cancellationToken);
   _modelState = actionContext.ModelState;

   ...
}

The order is important here, as it means that the model state is available to action filters, making it trivial to, for example, build an action filter that automatically returns an HTTP 400 Bad Request response in the event of an invalid model. This enables us to stop inserting code like the following into every PUT and POST action method:

public void Post(ModelValue v)
{
   if (!ModelState.IsValid)
   {
      var e = Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
      throw new HttpResponseException(e);
   }
}

Instead, this model state check can be pulled into a simple action filter, as shown in Example 12-4.

Example 12-4. Model state validation filter
public class VerifyModelState : ActionFilterAttribute
{
   public override void OnActionExecuting(HttpActionContext actionContext)
   {
      if (!actionContext.ModelState.IsValid)
      {
         var e = actionContext.Request.CreateErrorResponse(
            HttpStatusCode.BadRequest, actionContext.ModelState);
            actionContext.Response = e;
      }
   }
}

Action invocation

The final step in the controller pipeline is to invoke the selected action method on the controller. This responsibility falls to a specialized Web API component called the action invoker. An action invoker is any class that implements the IHttpActionInvoker interface. This interface has the following signature:

public interface IHttpActionInvoker
{
   Task<HttpResponseMessage> InvokeActionAsync(
      HttpActionContext actionContext, CancellationToken cancellationToken);
}

ApiController requests the action invoker from DefaultServices, which means that you can replace it using either the Replace method on DefaultServices or using DependencyResolver in conjunction with a dependency injection framework. However, the default implementation supplied by the framework, ApiControllerActionInvoker, should be sufficient for most requirements.

The default invoker performs two primary functions, as illustrated here:

object actionResult = await actionDescriptor.ExecuteAsync(controllerContext,
   actionContext.ActionArguments,
   cancellationToken);

return actionDescriptor.ResultConverter.Convert(controllerContext, actionResult);

The first responsibility is, as you would expect, to invoke the selected action method. The second responsibility is to convert the result of the action method call into an HttpResponseMessage. For this task, the invoker uses a specialized object called an action result converter. An action result converter implements the IActionResultConverter interface, which contains a single method accepting some context and returning an HttpResponseMessage. Currently, Web API contains three action result converters:

ResponseMessageResultConverter
Used when an action method returns an HttpResponseMessage directly; passes the responses message through.
ValueResultConverter<T>
Used when an action method returns a standard .NET Framework type; creates an HttpResponseMessage using the associated HttpRequestMethod’s CreateResponse<T> method.
VoidResultConverter
Used when an action method has a void return value; creates a new HttpResponseMessage with a status of 204 No Content.

Once the return value has been converted to an HttpResponseMessage, that message can begin the flow back out of the controller and message handler pipelines, and then over the wire to the client.

Conclusion

In this chapter, we explored in depth the two pipelines that handle request processing in ASP.NET Web API: the low-level message handler pipeline and the controller pipeline. Each has associated benefits as well as trade-offs. For example, the message handler pipeline is executed early in the handling of requests, so it can be advantageous to leverage when your component could prevent the needless execution of more expensive code paths. However, this comes at the cost of having to work at the level of HttpRequestMessage and HttpResponseMessage. The controller pipeline, on the other hand, provides its components access to the higher-level programming model objects, such as those that describe the controller, action methods, and associated attributes. Both of these pipelines provide a complete set of default components as well as a flexible model for extensibility using either HttpConfiguration or a custom DependencyResolver.

In the next chapter, we will take a much deeper look at the core building blocks that make up the message handler pipeline, including the message handlers themselves as well as the HTTP primitives, HttpRequestMessage and HttpResponseMessage.



[7] This pipeline style can be contrasted with a style whereby the pipeline is external to its components and data flows as the result of the pipeline calling a component, obtaining its response, calling the next component, and so on.

[8] The full code for overriding the default controller suffix is more involved than simply providing a new controller selector.

Get Designing Evolvable Web APIs with ASP.NET now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.