The routing and dispatching stage primarily involves the execution of a series of message handlers, which process the incoming request and then delegate them to the next message handler for processing. In earlier versions of ASP.NET Web API, we could only configure message handlers globally per application. All the message handlers were added to the HttpConfiguration.MessageHandlers
collection and were executed for each request. The global configuration was enabled using the Register
method in the WebApiConfig.cs
file, which gets added when creating a new ASP.NET Web API project. We talk more about the WebApiConfig
and Register
method in the Creating our first ASP.NET Web API section. The following snippet show a sample Register
method definition:
public static void Register(HttpConfiguration config) { config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); // add a new message handler to all routes config.MessageHandlers.Add(new CustomMessageHandler()); }
In the preceding code, we configured CustomMessageHandler()
to be called for all routes in the Web API. While this worked well for most basic scenarios, it became a challenge for applications where specialization for a route was required. For example, specific authorization handlers for a route. Web API 2 introduced a per route handler flow that allows granularity when routing requests to message handlers. The following code is added to WebApiConfig.cs
to configure a per route custom handler in the ASP.NET Web API 2:
public static void Register(HttpConfiguration config) { config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/common/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); config.Routes.MapHttpRoute( name: "CustomDefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional }, constraints: null, handler: new CustomMessageHandler() ); }
In the preceding code, we configured CustomMessageHandler
, which inherits from DelegatingHandler
to a particular route. We also have a global route with API/common that when called will not invoke our custom message handler, it will rather follow a global configuration. The internals of CustomMessageHandler
have been omitted since they are not necessary at this stage; all we are concerned here is how the internal routing logic works. We will discuss these concepts in greater detail in Chapter 2, Extending the ASP.NET Web API.
Let's look at how the per-route message handler routing is performed by ASP.NET Web API 2:
When a request is sent, the host listener (
HttpServer
) invokesHttpRoutingDispatcher
, which acts as the default endpoint message handler.HttpRoutingDispatcher
is responsible for discovering the route and invoking the corresponding handlers for the route.If no route is matched,
HttpRoutingDispatcher
returns an HTTP standard response code:Not Found (404)
.If a route is found, the routing handler checks if there is a handler associated with the current route. It does this by checking the
Route.Handler
property on the route.If no handler is specified for the route, it is assumed that the global configuration will be applied, and the request is delegated to
HttpControllerDispatcher
for further processing.If a handler is attached to the route,
HttpControllerDispatcher
attempts to invoke the message handler registered for the particular route. The invoked message handler may then return to the main path and delegate toHttpControllerDispatcher
, as shown:HttpControllerDispatcher
is a message handler, which is then responsible for selecting and creating the controller before delegating the request to the controller, as shown:If no controller is resolved, a 404 status code is returned by
HttpControllerDispatcher
.Once the controller is created successfully, its
ExecuteAsync
is called to delegate processing of the request:httpResponseMessage = await controller.ExecuteAsync(controllerContext, cancellationToken);
The final and the most exciting stage of the pipeline is processing the request by the controller; this is where all the magic happens to generate the appropriate response for the incoming request. As explained earlier, if the controller inherits from APIController
, much of the plumbing work is already abstracted from the developer. The following pipeline walks through a controller pipeline that inherits from APIController
:
The controller first determines the action to be invoked:
actionDescriptor = ServicesExtensions.GetActionSelector(services).SelectAction(controllerContext);
Next, a series of filters is invoked, which provides authentication and authorization. At any point, if there is a failure response (for example, client not authenticated), the filter can invoke an error response and break the response chain, as shown in the following figure:
The next step is model binding. It is a technique provided by the ASP.NET framework to bind request values to existing models. It is an abstraction layer that automatically populates controller action parameters, taking care of the mundane property mapping and type conversion code typically involved in working with ASP.NET request data. We will explore more about model and parameter binding in Chapter 2, Extending the ASP.NET Web API.
Next, the pipeline executes the actual action and any associated action filters. This will process the code that we write in our action method for processing incoming requests. The framework actually provides an ability to execute the action filters as a pre and post step to the actual method execution.
Once the controller action and the post filters are executed, the final step is to convert the response generated into an appropriate
HttpResponseMessage
and traverse the chain back to return the response to the client. This is shown in the following figure:Once
HttpResponseMessage
has been created, the response traverses back through the pipeline, and is returned to the client as an HTTP response.