Chapter 4. Processing Architecture

And now for something completely different.

The previous chapter presented the core ASP.NET Web API programming model, introducing the set of fundamental concepts, interfaces, and classes exposed by this framework. Before we address the book’s core subject of designing evolvable Web APIs, this chapter takes a short detour to look under the hood and present the underlying ASP.NET Web API processing architecture, detailing what happens between the reception of an HTTP request and the return of the corresponding HTTP response message. It also serves as a road map for the more advanced ASP.NET Web API features that we will be addressing in the third part of this book.

During this chapter we will be using the HTTP request presented in Example 4-1, associated with the controller defined in Example 4-2, as a concrete example to illustrate the runtime behavior of this architecture. The ProcessesController contains a Get action that returns a representation of all the machine’s processes with a given image name. The exemplifying HTTP request is a GET on the resource identified by http://localhost:50650/api/processes?name=explorer, which represents all the explorer processes currently executing.

Example 4-1. Sample HTTP request message
GET http://localhost:50650/api/processes?name=explorer HTTP/1.1
User-Agent: Fiddler
Host: localhost:50650
Accept: application/json
Example 4-2. Sample controller
public class ProcessesController : ApiController
{
    public ProcessCollectionState Get(string name)
    {
        if (string.IsNullOrEmpty(name))
        {
            throw new HttpResponseException(HttpStatusCode.NotFound);
        }
        return new ProcessCollectionState
        {
            Processes = Process
                .GetProcessesByName(name)
                .Select(p => new ProcessState(p))
        };
    }
}

public class ProcessState
{
    public int Id { get; set; }
    public string Name { get; set; }
    public double TotalProcessorTimeInMillis { get; set; }
    ...

    public ProcessState() { }
    public ProcessState(Process proc)
    {
        Id = proc.Id;
        Name = proc.ProcessName;
        TotalProcessorTimeInMillis = proc.TotalProcessorTime.TotalMilliseconds;
        ...
    }
}

public class ProcessCollectionState
{
    public IEnumerable<ProcessState> Processes { get; set; }
}

The ASP.NET web processing architecture, represented in Figure 4-1, is composed of three layers:

  • The hosting layer is the interface between Web API and the underlying HTTP stacks.
  • The message handler pipeline layer can be used for implementing cross-cutting concerns such as logging and caching. However, the introduction of OWIN (discussed in Chapter 11) moves some of these responsibilties down the stack into OWIN middleware.
  • The controller handling layer is where controllers and actions are called, parameters are bound and validated, and the HTTP response message is created. Additionally, this layer contains and executes a filter pipeline.

Let’s delve a bit deeper into each layer.

Simplified .ASP.NET Web API processing model
Figure 4-1. Simplified .ASP.NET Web API processing model

The Hosting Layer

The bottom layer in the Web API processing architecture is responsible for the hosting and acts as the interface between Web API and an underlying HTTP infrastructure, such as the classic ASP.NET pipeline, the HttpListener class found in the .NET Framework’s System.Net assembly, or an OWIN host. The hosting layer is responsible for creating HttpRequestMessage instances, representing HTTP requests, and then pushing them into the upper message handler pipeline. As part of processing a response, the hosting layer is also responsible for taking an HttpResponseMessage instance returned from the message handler pipeline and transforming it into a response that can be processed by the underlying network stack.

Remember that HttpRequestMessage and HttpResponseMessage are the new classes for representing HTTP messages, introduced in version 4.5 of the .NET Framework. The Web API processing architecture is built around these new classes, and the primary task of the hosting layer is to bridge them and the native message representations used by the underlying HTTP stacks.

At the time of writing, ASP.NET Web API includes several hosting layer alternatives, namely:

  • Self-hosting in any Windows process (e.g., console application or Windows service)
  • Web hosting (using the ASP.NET pipeline on top of Internet Information Services (IIS))
  • OWIN hosting (using an OWIN-compliant server, such as Katana)[2]

The first hosting alternative is implemented on top of WCF’s self-hosting capabilities and will be described in Chapter 10 in greater detail.

The second alternative—web hosting—uses the ASP.NET pipeline and its routing capabilities to forward HTTP requests to a new ASP.NET handler, HttpControllerHandler. This handler bridges the classical ASP.NET pipeline and the new ASP.NET Web API architecture by translating the incoming HttpRequest instances into HttpRequestMessage instances and then pushing them into the Web API pipeline. This handler is also responsible for taking the returned HttpResponseMessage instance and copying it into the HttpResponse instance before returning to the underlying ASP.NET pipeline.

Finally, the third hosting option layers Web API on top of an OWIN-compliant server. In this case, the hosting layer translates from the OWIN context objects into a new HttpRequestMessage object sent to Web API. Inversely, it takes the HttpResponseMessage returned by Web API and writes it into the OWIN context.

There is a fourth option, which removes the hosting layer entirely: requests are sent directly by HttpClient, which uses the same class model, into the Web API runtime without any adaptation. Chapter 11 presents a deeper exploration of the hosting layer.

Message Handler Pipeline

The middle layer of the processing architecture is the message handler pipeline. This layer provides an extensibility point for interceptors that addresses cross-cutting concerns such as logging and caching. It is similar in purpose to the middleware concept found in Ruby’s Rack, Python’s WSGI (Web Server Gateway Interface), and the Node.js Connect framework.

A message handler is simply an abstraction for an operation that receives an HTTP request message (HttpRequestMessage instance) and returns an HTTP response message (HttpResponseMessage instance). The ASP.NET Web API message handler pipeline is a composition of these handlers, where each one (except the last) has a reference to the next, called the inner handler. This pipeline organization provides a great deal of flexibility in the type of operations that can be performed, as depicted in Figure 4-2.

Message handler processing examples
Figure 4-2. Message handler processing examples

The diagram on the left illustrates the usage of a handler to perform some pre- and postprocessing over request and response messages, respectively. Processing flows from handler to handler via the InnerHandler relationship—in one direction for request processing and in the reverse direction for response processing. Examples of pre- and postprocessing are:

  • Changing the request HTTP method, based on the presence of a header such as X-HTTP-Method-Override, before it arrives to the controller’s action
  • Adding a response header such as Server
  • Capturing and logging diagnostic or business metric data

You can use handlers to short-circuit the pipeline by directly producing an HTTP response, as shown on the right side of Figure 4-2. A typical use case for this behavior is the immediate return of an HTTP response with a 401 (Unauthorized) status if the request isn’t properly authenticated.

In the .NET Framework, message handlers are classes that derive from the new HttpMessageHandler abstract class, as shown in Figure 4-3.

Message handler class hierarchy
Figure 4-3. Message handler class hierarchy

The abstract SendAsync method receives an HttpRequestMessage and asynchronously produces an HttpResponseMessage by returning Task<HttpResponseMessage>. This method also receives a CancellationToken, following the TAP (Task Asynchronous Pattern) guidelines for cancellable operations.

The message handler pipeline organization just described requires a data member to hold a reference to an inner handler as well as data flow logic to delegate requests and response messages from a handler to its inner handler. These additions are implemented by the DelegatingHandler class, which defines the InnerHandler property to connect a handler with its inner handler.

The sequence of delegating message handlers that constitute the pipeline is defined in ASP.NET Web API’s configuration object model on the HttpConfiguration.MessageHandlers collection property (e.g., config.MessageHandlers.Add(new TraceMessageHandler());). The message handlers are ordered in the pipeline according to the config.MessageHandlers collection order.

ASP.NET Web API 2.0 introduces support for the OWIN model, providing the OWIN middleware as an alternative to message handlers as a way to implement cross-cutting concerns. The main advantage of OWIN middleware is that it can be used with other web frameworks (e.g., ASP.NET MVC or SignalR), since it isn’t tied specifically to Web API. As an example, the new security features introduced in Web API 2.0 and presented in Chapter 15 are mostly implemented as OWIN middleware and reusable outside of Web API. On the other hand, message handlers have the advantage of also being usable on the client side, as described in Chapter 14.

Route Dispatching

At the end of the message handler pipeline, there are always two special handlers:

  • The routing dispatcher implemented by the HttpRoutingDispatcher class
  • The controller dispatcher implemented by the HttpControllerDispatcher class

The routing dispatcher handler performs the following:

  • Obtains the routing data from the message (e.g., when web hosting is used) or performs the route resolution if it wasn’t performed previously (e.g., when self-hosting is used). If no route was matched, it produces a response message with the 404 Not Found HTTP status code.
  • Uses the route data to select the next handler to which to forward the request, based on the matched IHttpRoute.

The controller dispatcher handler is responsible for:

  • Using the route data and a controller selector to obtain a controller description. If no controller description is found, a response message with the 404 Not Found status code is returned.
  • Obtaining the controller instance and calling its ExecuteAsync method, passing the request message.
  • Handling exceptions returned by the controller and converting them into response messages with a 500 Internal Error status code.

For instance, using the HTTP request of Example 4-1 and the default route configuration, the route data will contain only one entry with the controller key and processes value. This single route data entry is the result of matching the http://localhost:50650/api/processes?name=explorer request URI with the /api/{controller}/{id} route template.

By default, the routing dispatcher forwards the request message to the controller dispatcher, which then calls the controller. However, it is possible to explicitly define a per-route handler, as shown in Figure 4-4.

Per-route handlers and the route dispatch handler
Figure 4-4. Per-route handlers and the route dispatch handler

In this case, the request is forwarded to the handler defined by the route, not to the default controller dispatcher. Reasons for using per-route dispatching include flowing the request message through a route-specific handler pipeline. A concrete example is the use of distinct authentication methods, implemented in message handlers, for different routes. Another reason for using per-route dispatching is the substitution of an alternative framework for the Web API top layer (controller handling).

Controller Handling

The final and uppermost layer in the processing architecture is the controller handling. This layer is responsible for receiving a request message from the underlying pipeline and transforming it into a call to a controller’s action method, passing the required method parameters. It also is responsibile for converting the method’s return value into a response message and passing it back to the message handler pipeline.

Bridging the message handler pipeline and the controller handling is performed by the controller dispatcher, which is still a message handler. Its main task is to select, create, and call the correct controller to handle a request. This process is detailed in Chapter 12, which presents all the relevant classes and shows how you can change the default behavior using the available extensibility points.

The ApiController Base Class

The concrete controller that ultimately handles the request can directly implement the IHttpController interface. However, the common scenario, as presented in the previous chapter, is for the concrete controller to derive from the abstract ApiController class, as shown in Figure 4-5.

It is the job of ApiController.ExecuteAsync to select the appropriate action, given the HTTP request method (e.g., GET or POST), and call the associated method on the derived concrete controller. For instance, the GET request in Example 4-1 will be dispatched to the ProcessesController.Get(string name) method.

Concrete controller deriving from the abstract ApiController class
Figure 4-5. Concrete controller deriving from the abstract ApiController class

After the action is selected but before the method is invoked, the ApiController class executes a filter pipeline, as shown in Figure 4-6. Each action has its own pipeline composed of the following functionalities:

  • Parameter binding
  • Conversion from the action’s return into an HttpResponseMessage
  • Authentication, authorization, and action filters
  • Exception filters
Per-action filter pipeline, parameter binding, and result conversions
Figure 4-6. Per-action filter pipeline, parameter binding, and result conversions

Parameter binding

Parameter binding is the computation of the action’s parameter values, used when calling the action’s method. This process is illustrated in Figure 4-7 and uses information from several sources, namely:

  • The route data (e.g., route parameters)
  • The request URI query string
  • The request body
  • The request headers
Parameter binding
Figure 4-7. Parameter binding

When executing the action’s pipeline, the ApiController.ExecuteAsync method calls a sequence of HttpParameterBinding instances, where each one is associated with one of the action’s method parameters. Each HttpParameterBinding instance computes the value of one parameter and adds it to the ActionArguments dictionary in the HttpActionContext instance.

HttpParameterBinding is an abstract class with multiple derived concrete classes, one for each type of parameter binding. For instance, the FormatterParameterBinding class uses the request body content and a formatter to obtain the parameter value.

Formatters are classes that extend the abstract MediaTypeFormatter class and perform bidirectional conversions between CLR (Common Language Runtime) types and byte stream representations, as defined by Internet media types. Figure 4-8 illustrates the functionality of these formatters.

Formatters and conversions between message bodies and CLR objects
Figure 4-8. Formatters and conversions between message bodies and CLR objects

A different kind of parameter binding is the ModelBinderParameterBinding class, which instead uses the concept of model binders to fetch information from the route data, in a manner similar to ASP.NET MVC. For instance, consider the action in Example 4-2 and the HTTP request in Example 4-1: the name parameter in the GET method will be bound to the value explorer—that is, the value for the query string entry with name key. We’ll provide more detail on formatters, model binding, and validation in Chapter 13.

Conversion into an HttpResponseMessage

After the action method ends and before the result is returned to the filter pipeline, the action result, which can be any object, must be converted into an HttpResponseMessage. If the return type is assignable to the IHttpActionResult interface[3] (presented in Example 4-3), then the result’s ExecuteAsync method is called to convert it into a response message. There are several implementations of this interface, such as OkResult and RedirectResult, that can be used in the action’s code. The ApiController base class also includes several protected methods (not shown in Figure 4-5) that can be used by derived classes to construct IHttpActionResult implementations (e.g., protected internal virtual OkResult Ok()).

Example 4-3. The IHttpActionResult interface
public interface IHttpActionResult
{
    Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken);
}

If the return isn’t a IHttpActionResult, then an external result converter, implementing the IActionResultConverter interface as defined in Example 4-4, is selected and used to produce the response message.

Example 4-4. Result converters: Converting the action’s return into response messages
public interface IActionResultConverter
{
    HttpResponseMessage Convert(
        HttpControllerContext controllerContext,
        object actionResult);
}

For the HTTP request in Example 4-1, the selected result converter will try to locate a formatter that can read a ProcessCollectionState (i.e., the type returned by the action’s method), and produce a byte stream representation of it in application/json (i.e., the value of the request’s Accept header). In the end, the resulting response is the one presented in Example 4-5.

Example 4-5. HTTP response
HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/json; charset=utf-8
Expires: -1
Server: Microsoft-IIS/8.0
Date: Thu, 25 Apr 2013 11:50:12 GMT
Content-Length: (...)

{"Processes":[{"Id":2824,"Name":"explorer",
"TotalProcessorTimeInMillis":831656.9311}]}

Formatters and content negotiation are addressed in more detail in Chapter 13.

Filters

Authentication, authorizations, and action filters are defined by the interfaces listed in Example 4-6, and have a role similar to message handlers—namely, to implement cross-cutting concerns (e.g., authentication, authorization, and validation).

Example 4-6. Filter interfaces
public interface IFilter
{
    bool AllowMultiple { get; }
}

public interface IAuthenticationFilter : IFilter
{
    Task AuthenticateAsync(
        HttpAuthenticationContext context,
        CancellationToken cancellationToken);

    Task ChallengeAsync(
        HttpAuthenticationChallengeContext context,
        CancellationToken cancellationToken);
}

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

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

public interface IExceptionFilter : IFilter
{
    Task ExecuteExceptionFilterAsync(
        HttpActionExecutedContext actionExecutedContext,
        CancellationToken cancellationToken);
}

For authorization and action filters, the pipeline is organized similarly to the message handler pipeline: each filter receives a reference to the next one in the pipeline and has the ability to perform pre- and postprocessing over the request and the response. Alternatively, the filter can produce a new response and end the request immediately, thereby short-circuiting any further processing (namely, the action’s invocation). Authentication filters operate under a slightly different model, as we will see in Chapter 15.

The main difference between authorization and action filters is that the former are located before parameter binding takes place, whereas the latter are located after this binding. As a result, authorization filters are an adequate extension point to insert operations that should be performed as early in the pipeline as possible. The typical example is verifying if the request is authorized and immediately producing a 401 (Not authorized) HTTP response message if it is not. On the other hand, action filters are appropriate when access to the bound parameters is required.

A fourth filter type, exception filters, is used only if the Task<HttpResponseMessage> returned by the filter pipeline is in the faulted state; that is, there was an exception. Each exception filter is called in sequence and has the chance to handle the exception by creating an HttpResponseMessage. Recall that if the controller dispatcher handler receives an unhandled exception, an HTTP response message with a status code of 500 (Internal Server Error) is returned.

We can associate filters to controllers or actions in multiple ways:

  • Via attributes, similarly to what is supported by ASP.NET MVC
  • By explicitly registering filter instances in the configuration object, using the HttpConfiguration.Filters collection
  • By registering IFilterProvider implementation in the configuration service’s container

After the HttpResponseMessage instance has left the action pipeline, it is returned by ApiController to the controller dispatcher handler. Then it descends through the message handler pipeline until it is converted into a native HTTP response by the hosting layer.

Conclusion

This chapter concludes the first part of the book, the aim of which was to introduce ASP.NET Web API, the motivation behind its existence, its basic programming model, and its core processing architecture. Using this knowledge, we will shift focus in the next part to the design, implementation, and consumption of evolvable Web APIs, using ASP.NET Web API as the supporting platform.



[2] OWIN hosting support was introduced in ASP.NET Web API, version 2.

[3] The IHttpActionResult interface was introduced in version 2.0 of Web API.

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.