Let's talk about some of the internal workings of ASP.NET Web API and how a request is received and a corresponding response generated.
Anatomy of the API of ASP.NET Web API
Before we understand the request and response lifecycle within ASP.NET Web API, it is important to comprehend some of the principal types and their usage within the pipeline.
The core assemblies for ASP.NET Web API can be installed using the Microsoft.AspNet.WebApi
NuGet package, which is distributed under the MS license, this will install all the required dependencies to develop an ASP.NET Web API service.
Note
NuGet is the package manager for the Microsoft development platform, including .NET. The NuGet client tools provide the ability to produce and consume packages. NuGet is installed as part of the Visual Studio 2013 release. To know more about NuGet, visit https://www.nuget.org/.
To install the runtime packages via the NuGet Package Manager Console in Visual Studio use the following command:
Alternatively, Visual Studio provides a NuGet user interface dialog that can be used to install the package. For more information on using the NuGet dialog in Visual Studio, visit https://docs.nuget.org/consume/package-manager-dialog.
Note
Note that a project created using the ASP.NET Visual Studio 2013 template does not require adding these packages explicitly.
The following figure describes the core assemblies and their dependencies that get downloaded when the Microsoft.AspNet.WebApi
package is installed. There are other NuGet packages that are published by the ASP.NET Web API team (such as CORS support, Help Pages, OWIN host). This figure provides the bare minimal to get started with ASP.NET Web API development:
Now, let's look at some of the important runtime types that enable the communication and execution in the ASP.NET Web API pipeline.
As mentioned earlier, the execution of a request implements a Russian Doll Model. A DelegatingHandler
is a runtime type that enables the creation of a handler that can participate in the chain of request and response pipeline for ASP.NET Web API. Although used heavily by the ASP.NET Web API infrastructure, DelegatingHandler
is a type defined in System.Net.Http
.
DelegatingHandler
is derived from HttpMessageHandler
, which is the abstract base class for all HTTP message handlers. The most critical method exposed by HttpMessageHandler
is the abstract SendAsync
method. It is responsible for maintaining the chain of the request and response pipeline. DelegatingHandler
can enrich the request message before calling SendAsync
; once called, it sends the incoming request to the next handler for processing.
DelegatingHandler
acts as a base type for built-in message handlers such as HttpServer
. In its base implementation, it enables assigning an inner handler during construction and delegating the SendAsync
request to the inner handler, thus enabling a chain between the handlers. Any type that inherits from DelgatingHandler
can then participate in the pipeline by ensuring that SendAsync
on DelgatingHandler
is called.
Alternatively, an inherited type may return a response instead of invoking base.SendAsync
. For example, a handler generating a response. Another useful scenario could be where a handler detects an exception and wants to stop processing the request.
Note
Note that the call to SendAsync
is asynchronous, thus satisfying the "asynchronous to the core" design principle of REST.
All message handlers in ASP.NET Web API are contained in an ordered collection as part of the HttpConfiguration.MessageHandlers
collection. What this means is that the handlers are always executed in sequence of their addition. The ASP.NET Web API framework allows for defining message handlers at the configuration level on a per route basis using HttpRoutingDispatcher
. We discuss more details about this feature in the Routing and dispatching section.
HttpRequestMessage
is a runtime type defined in the System.Net.Http
namespace; it acts as an unified abstraction for all HTTP requests irrespective of client or server. Any incoming message is first converted into HttpRequestMessage
before it traverses through the pipeline; this provides an ability to have a strong type HTTP message throughout the pipeline.
An HttpRequestMessage
may contain values for the following attributes:
HttpResponseMessage
represents the other side of the story and provides an abstraction for all responses generated for corresponding HTTP requests. It is also defined in the System.Net.Http
namespace. Any response within the pipeline will be represented as HttpResponseMessage
before it gets converted and sent to the caller.
HttpResponseMessage
may contain values for the following attributes:
HttpResponse
also exposes the EnsureSuccessStatusCode
method, which is especially useful in testing scenarios where a successful response is expected. The method throws an exception if HttpResponseMessage
does not return with a success HTTPStatusCode
in the range of 200 to 209. Note that in case of a failure response, the EnsureSuccessStatusCode
internally calls Dispose
on the stream if the Content
property for the response is not null. Essentially, we cannot access the Content
stream post with this method when a failure status code is returned.
ASP.NET MVC has the concept of controllers since its inception; conceptually, Web API controllers follow a similar approach to leverage the goodies from the ASP.NET framework. However, Web API controllers are targeted towards providing an abstraction over the HTTP request-response pipeline. Moreover, Web API controllers return only response data in contrast to HTML views in ASP.NET MVC.
All Web API controllers implement the IHttpController
interface to classify them as Web API controllers. As we will see in the next section, the Web API request dispatch, and route matching pipeline attempts to look for an IHttpController
instead of a particular controller implementation. The following code snippet shows the definition for the IHttpController
interface.
While we can create our controllers by implementing the IHttpController
interface, ASP.NET Web API provides a useful base class that abstracts most of the low-level plumbing required for the request-response pipeline.
A typical APIController.ExecuteAsync
implementation looks like this:
The series of actions performed by the API controller in the preceding code is as follows:
The API controller invokes the action selection logic for the HTTP request.
It then initializes and invokes the registered authentication filters.
Subsequently, it initializes and invokes the authorization filters.
Next, the action filters are initialized and invoked followed by the exception filters in case of exceptions.
In the last step, it invokes the Parameter Binding and content negotiation for the requests and responses.
Finally, the operation is executed.
Now that we have some context of the main APIs involved in the Web API request-response pipeline, let's take a look at how a request flows through the Web API pipeline and how a response is directed back to the client.
At a broad level, the message pipeline can be categorized into three main stages:
Let's walk through the message pipeline using a sample Web API. The Web API is a simple Hello World API that allows the client to issue a GET
request and returns the string Hello world!
in JSON. For now, don't worry about the implementation details of this Web API. In the next sections, we will get into deeper detail on how to create, test, and deploy a Web API.
A host listener refers to a component listening for incoming requests. For years, Microsoft Web Components had an inherent dependency of using Internet Information Services (IIS) as the host. The ASP.NET Web API breaks out of this legacy model and allows itself to be hosted outside IIS. As a matter of fact, we can host a Web API in an executable, which then acts as the server receiving requests for the Web API. There are many options available to host a Web API; we will explore some of them in detail in Chapter 2, Extending the ASP.NET Web API. For now, we will focus on using either of the hosting options and enable listening for requests and responding with a response.
Considering the earlier Hello World example, the incoming request will be processed as shown in the following diagram:
In the preceding diagram:
The client code issues a GET
request using HTTP 1.1 as the protocol. The request is issued to an endpoint, which represents a resource on the server.
The configured host listener will then receive the request and convert it into HTTPRequestMessage
. The host inherits from the HTTPServer
type, which by itself is a message handler and implements DelegatingHandler
. So, essentially it is just another message handler that delegates the request to the next handler (remember the Russian Doll Model we talked about earlier).
The request is then sent through to the routing and dispatching components and then to the controller components for further processing and returning the response.