Extending the Pipeline
ASP.NET Web API is a framework. The key defining attribute of a framework is that it controls the execution flow and calls the application-specific code written by a developer like you at the appropriate time. You don’t call the framework but it calls your code, in line with the Hollywood principle of “don’t call us, we’ll call you.” The most fundamental lever that you use to harness the power of the ASP.NET Web API framework in building a service is the controller, the ApiController subclass that you write. It is the business end where all the application-specific action happens.
ASP.NET Web API framework receives an HTTP request and goes about processing it. At some point during the processing, it calls the action method you have implemented in the ApiController subclass, passing in the parameters, and formats the output returned by your method to ultimately send an HTTP response back to the client. The sequence of steps from the time a request is received to the time the response is sent back defines the processing architecture of ASP.NET Web API. See Figure 8-1.
Figure 8-1. The ASP.NET Web API pipeline
ASP.NET Web API, being a framework, has various points of extensibility built in for us to hook our code in and extend the processing. We have already seen some of them in the preceding chapters: creating a new media type formatter deriving from MediaTypeFormatter, creating a new model binder implementing IModelBinder, creating a new value provider implementing IValueProvider, and creating a new parameter binding by deriving from HttpParameterBinding. There are other extensibility points available as well, but in this chapter I cover the message handlers, filters, and controller selectors. For a detailed illustration of the pipeline and extensibility points, refer to the chart from Microsoft available for download at http://www.microsoft.com/en-us/download/details.aspx?id=36476.
8.1 Creating a Message Handler
In this exercise, you will create a message handler that simply reads a request header and adds a response header. In the ASP.NET Web API pipeline, the first message handler to run is HttpServer. All of the other, custom message handlers run after HttpServer. A custom message handler is a class that inherits from the class DelegatingHandler. HttpServer gets to look at the request before any other message handler. For the outgoing response, the last handler in the chain gets to see the output first and HttpServer gets to see the response last.
This is a great model because your important handlers get to see the request first, and they are the last ones to do anything with the response. If a bad request (whatever the criteria are for the request to be classified bad) comes in, your important handlers see it first, and they can decide whether the inner handlers should be allowed to see the request or it should be stopped at that point in the pipeline. Similarly, on the way out, the important handlers get to decide as late as possible in the pipeline whether to send the response out, stop it, or make a last-minute alteration to the response. See Figure 8-2 for an illustration of the call sequence.
Figure 8-2. The levels of message handlers can be compared to Chinese boxes
Listing 8-1. The MyImportantHandler Class
using System;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
public class MyImportantHandler : DelegatingHandler
{
private const string REQUEST_HEADER = "X-Name";
private const string RESPONSE_HEADER = "X-Message";
private const string NAME = "Voldemort";
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
// Inspect and do your stuff with request here
string name = String.Empty;
if (request.Headers.Contains(REQUEST_HEADER))
{
name = request.Headers.GetValues(REQUEST_HEADER).First();
}
// If you are not happy for a reason,
// you can reject the request right here like this
if (NAME.Equals(name, StringComparison.OrdinalIgnoreCase))
return request.CreateResponse(HttpStatusCode.Forbidden);
var response = await base.SendAsync(request, cancellationToken);
// Inspect and do your stuff with response here
if (response.StatusCode == HttpStatusCode.OK &&
!String.IsNullOrEmpty(name))
{
response.Headers.Add(RESPONSE_HEADER,
String.Format("Hello, {0}. Time is {1}",
name,
DateTime.Now.ToString("MM/dd/yyyy hh:mm:ss.fff tt")));
}
return response;
}
}
Listing 8-2. Changes to MyNotSoImportantHandler
public class MyNotSoImportantHandler : DelegatingHandler
{
private const string REQUEST_HEADER = " X-Name2";
private const string RESPONSE_HEADER = " X-Message2";
private const string NAME = " Potter";
// The rest of the code is the same as MyImportantHandler
}
Figure 8-3. Breakpoints in the message handler
Listing 8-3. Registering the Message Handlers
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.MessageHandlers.Add(new MyImportantHandler());
config.MessageHandlers.Add(new MyNotSoImportantHandler());
}
}
GET http://localhost:39188/api/employees/1 HTTP/1.1
X-Name: Badri
X-Name2: Badri
Host: localhost:39188
HTTP/1.1 200 OK
X-Message2: Hello, Badri. Time is 04/18/2013 07:41: 22.864PM
X-Message: Hello, Badri. Time is 04/18/2013 07:41: 23.723PM
Date: Thu, 18 Apr 2013 14:11:24 GMT
Content-Length: 91
{"Id":1,"FirstName":"John","LastName":"Human","DepartmentId":1,"RowVersion":"AAAAAAAAF3U="}
We have registered our handlers as global handlers, so they will be plugged into the pipeline for all the routes. It is possible to configure the handlers specifically for a route, as per-route message handlers.
Listing 8-4. Registering the Per-Route Handlers
using System.Web.Http;
using System.Web.Http.Dispatcher;
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
var handler = new MyImportantHandler()
{
InnerHandler = new MyNotSoImportantHandler()
{
InnerHandler = new HttpControllerDispatcher(config)
}
};
config.Routes.MapHttpRoute(
name: "premiumApi",
routeTemplate: "premium/{controller}/{id}",
defaults: new { id = RouteParameter.Optional },
constraints: null,
handler: handler
);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
//config.MessageHandlers.Add(new MyImportantHandler());
//config.MessageHandlers.Add(new MyNotSoImportantHandler());
}
}
8.2 Creating an Exception Filter
An exception filter runs when a controller method throws any unhandled exception. To create an exception filter, you must implement the IExceptionFilter interface or derive from the ExceptionFilterAttribute abstract class (which implements the IExceptionFilter interface) and override the OnException method. In this exercise, you will create an exception filter.
Listing 8-5. The PUT Action Method
public void Put(int id, Employee employee)
{
repository.Update(employee);
uow.Save();
}
{"Id":1,"FirstName":"John","LastName":"Human","DepartmentId":1,"RowVersion":"AAAAAAAAB9Q="}
{"Message":"An error has occurred.","ExceptionMessage":"Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. Refresh ObjectStateManager entries.","ExceptionType":" System.Data.OptimisticConcurrencyException","StackTrace":" at System.Data.Mapping.Update.Internal.UpdateTranslator.ValidateRowsAffected(Int64 rowsAffected, UpdateCommand source) at System.Data.Mapping.Update.Internal.UpdateTranslator.Update(IEntityStateManager stateManager, IEntityAdapter adapter) at System.Data.EntityClient.EntityAdapter.Update(IEntityStateManager entityCache) at System.Data.Objects.ObjectContext.SaveChanges(SaveOptions options) at System.Data.Entity.Internal.InternalContext.SaveChanges()"}
config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Never;
Listing 8-6. The ConflictExceptionHandlerAttribute Class
using System.Data.Entity.Infrastructure;
using System.Net;
using System.Net.Http;
using System.Web.Http.Filters;
public class ConflictExceptionHandlerAttribute : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext context)
{
string message = "Changes not saved because of missing or stale ETag. ";
message += "GET the resource and retry with the new ETag";
if (context.Exception is DbUpdateConcurrencyException)
{
context.Response = context.Request.CreateErrorResponse(
HttpStatusCode.Conflict,
message);
}
}
}
Listing 8-7. A PUT Action Method with an Exception Filter
[ConflictExceptionHandler]
public void Put(int id, Employee employee)
{
repository.Update(employee);
uow.Save();
}
HTTP/1.1 409 Conflict
Cache-Control: no-cache
Content-Type: application/json; charset=utf-8
Date: Fri, 19 Apr 2013 04:50:07 GMT
Content-Length: 110
{"Message":"Changes not saved because of missing or stale ETag. GET the resource and retry with the new ETag"}
8.3 Creating an Action Filter to Handle Concurrency
In this exercise, you will create an action filter. Building on Exercise 8.2, you will use ETags to implement optimistic concurrency. ETags are typically associated with web caching, but they can also be used for optimistic concurrency management.
Concurrency management is essential to ensure data integrity in multiuser environments such as the web. Since HTTP is stateless, locking a resource before the update, as we do in pessimistic locking, is not a feasible option. A better approach is to be optimistic that there will be no intermediate changes between the time of the read and the subsequent update, and failing the update if there is an intermediate change.
As part of the GET response, the web API sends an ETag in the ETag response header. Subsequently, if the same resource has to be updated through a PUT, the client sends the same ETag in the If-Match request header. If the ETag sent by the client matches the ETag in the database (the row_version column of type timestamp), the table is updated. If there is a mismatch, a status code of 409 – Conflict is sent back to the client. The client can follow this with a fresh GET and retry the update. See Figure 8-4.
Figure 8-4. The ETag for optimistic locking
In this exercise, you will create an action filter that sets the ETag response header based on the row_version column of the employee table. We have been sending the version information as part of the response message body, but the standard way of doing that is to use the ETag response header. Also, when we issue a PUT from Fiddler, we send the version information in the request message body. The standard way of doing this is to send the same information in the If-Match request header.
The action filter you create as part of this exercise will take the version information from the RowVersion property of the EmployeeDto on the way out and copy that data into the ETag response header, immediately after the controller completes executing the action method for GET. Also, the filter will take the version information from the If-Match request header and copy that to the RowVersion property of the EmployeeDto parameter on the way in, just before the controller starts executing the action method for PUT.
Listing 8-8. The IVersionable Interface
public interface IVersionable
{
byte[] RowVersion { get; set; }
}
Listing 8-9. The Department and Employee Classes Implementing IVersionable
public class Employee : IIdentifiable, IVersionable
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
// Foreign key association
public int DepartmentId { get; set; }
// Independent association
public virtual Department Department { get; set; }
public byte[] RowVersion { get; set; }
}
public class Department : IIdentifiable, IVersionable
{
public int Id { get; set; }
public string Name { get; set; }
public byte[] RowVersion { get; set; }
}
Listing 8-10. The OptimisticLockAttribute with OnActionExecuted
using System;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
using TalentManager.Domain;
public class OptimisticLockAttribute : ActionFilterAttribute
{
// OnActionExecuting method goes here
public override void OnActionExecuted(HttpActionExecutedContext context)
{
var request = context.Request;
if (request.Method == HttpMethod.Get)
{
object content = (context.Response.Content as ObjectContent).Value;
if (content is IVersionable)
{
byte[] rowVersion = ((IVersionable)content).RowVersion;
var etag = new EntityTagHeaderValue(""" +
Convert.ToBase64String(rowVersion) + """);
context.Response.Headers.ETag = etag;
}
}
}
Listing 8-11. The OptimisticLockAttribute with OnActionExecuting
public override void OnActionExecuting(HttpActionContext context)
{
var request = context.Request;
if (request.Method == HttpMethod.Put)
{
EntityTagHeaderValue etagFromClient = request.Headers.IfMatch.FirstOrDefault();
if (etagFromClient != null)
{
var rowVersion = Convert.FromBase64String(
etagFromClient.Tag.Replace(""", String.Empty));
foreach (var x in context.ActionArguments.Values.Where(v => v is IVersionable))
{
((IVersionable)x).RowVersion = rowVersion;
}
}
}
}
Listing 8-12. The GET by ID and PUT Action Methods
[OptimisticLock]
public HttpResponseMessage Get(int id)
{
var employee = repository.Find(id);
if (employee == null)
{
var response = Request.CreateResponse(HttpStatusCode.NotFound,
"Employee not found");
throw new HttpResponseException(response);
}
return Request.CreateResponse<EmployeeDto>(
HttpStatusCode.OK,
mapper.Map<Employee, EmployeeDto>(employee));
}
[OptimisticLock]
[ConflictExceptionHandler]
public void Put(int id, EmployeeDtoemployeeDto)
{
var employee = mapper.Map<EmployeeDto, Employee>(employeeDto);
repository.Update(employee);
uow.Save();
}
Request
GET http://localhost:39188/api/employees/1 HTTP/1.1
Host: localhost:39188
Response
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
ETag: "AAAAAAAAF44="
Content-Length: 93
{"Id":1,"FirstName":"Johnny","LastName":"Human","DepartmentId":1, "RowVersion":"AAAAAAAAF44="}
Request
PUT http://localhost:39188/api/employees/1 HTTP/1.1
Host: localhost:39188
Content-Type: application/json
Content-Length: 65
{"Id":1,"FirstName":"Johnny","LastName":"Human","DepartmentId":1}
Response
HTTP/1.1 409Conflict
Content-Type: application/json; charset=utf-8
Date: Fri, 19 Apr 2013 17:13:20 GMT
Content-Length: 110
{"Message":"Changes not saved because of missing or stale ETag. GET the resource and retry with the new ETag"}
Request
PUT http://localhost:39188/api/employees/1 HTTP/1.1
Host: localhost:39188
Content-Type: application/json
Content-Length: 65
If-Match: " AAAAAAAAF44="
{"Id":1,"FirstName":"Johnny","LastName":"Human","DepartmentId":1}
Response
HTTP/1.1 204 No Content
8.4 Creating a Controller Selector for Versioning
In this exercise, you will create a custom controller selector to implement a simple versioning system for your web API. As part of the ASP.NET Web API pipeline processing, a controller is selected to handle the request. The controller selection logic is implemented in the SelectController method of the DefaultHttpControllerSelector class. This class implements the IHttpControllerSelector interface. The SelectController method calls another public virtual method, named GetControllerName. This method simply returns the controller name from the route data. You will override this method to implement your own logic.
You have EmployeesController, which is selected when the request comes to the URI such as this: http://localhost:39188/api/employees/1. As you make changes to the action methods, some client applications may stop working if the changes you made are breaking changes. Versioning your web API is a technique that helps prevent this situation. In our case, you will leave the base version of EmployeesController untouched and make the breaking changes to the next version of EmployeesController, say 2.0. The older clients will work off version 1.0, while the newer clients that need the enhanced version with new functionality can work off version 2.0. There are multiple ways versioning can be done. Some of them are as follows:
In this exercise, we will use the header approach. Versioning is a complex topic, and all of the preceding options have pros and cons that you must weigh for the requirements you have at hand. I do not intend to give you a production-strength versioning solution through this exercise. The objective here is just to demonstrate how you can select a controller class on the fly by implementing a custom controller selector.
Listing 8-13. MyControllerSelector (Incomplete)
using System;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Dispatcher;
public class MyControllerSelector : DefaultHttpControllerSelector
{
public MyControllerSelector(HttpConfiguration configuration) : base(configuration) {}
public override string GetControllerName(HttpRequestMessage request)
{
string controllerName = base.GetControllerName(request);
// Our customization Step 1 goes here
// Our customization Step 2 goes here
return controllerName;
}
}
Listing 8-14. The GetControllerName Method
// Having controllers like EmployeesV2Controller or EmployeesV3Controller is
// our internal business. A client must not make a request directly like /api/employeesv2.
int version;
int length = controllerName.Length;
if (Int32.TryParse(controllerName.Substring(length - 1, 1), out version))
{
if (controllerName.Substring(length - 2, 1).Equals("V", StringComparison.OrdinalIgnoreCase))
{
string message = "No HTTP resource was found that matches the request URI {0}";
throw new HttpResponseException(
request.CreateErrorResponse(
HttpStatusCode.NotFound,
String.Format(message, request.RequestUri)));
}
}
Listing 8-15. The GetControllerName Method (Continuation)
// If client requests a specific version through the request header, we entertain it
if (request.Headers.Contains("X-Version"))
{
string headerValue = request.Headers.GetValues("X-Version").First();
if (!String.IsNullOrEmpty(headerValue) &&
Int32.TryParse(headerValue, out version))
{
controllerName = String.Format("{0}v{1}", controllerName, version);
HttpControllerDescriptor descriptor = null;
if (!this.GetControllerMapping().TryGetValue(controllerName, out descriptor))
{
string message = "No HTTP resource was found that matches the request URI {0} and version {1}";
throw new HttpResponseException(
request.CreateErrorResponse(
HttpStatusCode.NotFound,
String.Format(message, request.RequestUri, version)));
}
}
}
config.Services.Replace(typeof(IHttpControllerSelector), new MyControllerSelector(config));
Note When a nonnumeric value appears in the X-Version request header, our selector switches to EmployeesController. This is just by design and can be changed to send back an error.
Summary
ASP.NET Web API, being a framework, is in control of the execution flow and calls the application-specific code written by a developer like you at the appropriate time. You don’t call the framework code but it calls your code, in line with the Hollywood principle. The ASP.NET Web API framework receives an HTTP request and goes about processing it. At some point during the processing, it calls the action method you have implemented in the ApiController subclass, passing in the parameters, and formats the output returned by your method to ultimately send an HTTP response back to the client. The sequence of steps that happens from the time a request is received to the time the response is sent back defines the processing architecture of ASP.NET Web API.
Because it is a framework, ASP.NET Web API also has various points of extensibility built in, allowing us to hook our code in and extend the pipeline processing. In this chapter, I covered three of them: the message handlers, filters, and controller selectors. Message handlers run earlier in the pipeline and they run for all the requests. The lowest granularity possible is per-route. Filters run just before the execution of the action method. A filter can be applied to an action method or a controller (in which case it runs for all the action methods of that specific controller) or globally in the WebApiConfig class (in which case it runs for all requests). The controller selector runs before the filters. The default implementation of the controller selector is DefaultHttpControllerSelector, which implements the IHttpControllerSelector interface. The SelectController method selects the controller for a given request.