Message handlers in the WebAPI feature a single method to process an HTTP request and turn the request into a response. SendAsync is the method name, and following the TAP, the method returns a Task<HttpResponseMessage>.
There are several different techniques available to return the Task result from a message handler. The samples on the ASP.NET site demonstrate a few of these techniques, although there is one new trick for .NET 4.5.
What you'll notice in these samples is that there is never a need to call Task.Run, or Task.Factory.StartNew, or use a Task constructor.
First, some message handlers will only need to process or modify the incoming request and not touch the response. In these cases the code can just return the result of the base SendAsync method (from DelegatingHandler). A good example is the MethodOverrideHandler sample on the ASP.NET site, which uses the presence of a header to change the HTTP method and thereby support clients or environments where HTTP PUT and DELETE messages are otherwise impossible.
public class MethodOverrideHandler : DelegatingHandler { readonly string[] _methods = { "DELETE", "HEAD", "PUT" }; const string _header = "X-HTTP-Method-Override"; protected override Task<HttpResponseMessage> SendAsync( HttpRequestMessage request, CancellationToken cancellationToken) { if (request.Method == HttpMethod.Post && request.Headers.Contains(_header)) { var method = request.Headers.GetValues(_header).FirstOrDefault(); if (_methods.Contains(method, StringComparer.InvariantCultureIgnoreCase)) { request.Method = new HttpMethod(method); } } return base.SendAsync(request, cancellationToken); } }
Notice the code to return base.SendAsync as the last line of the method.
In other cases, a message handler might only want to change the response and not touch the request. An example might be a message handler to add a custom header into the response message (again a sample from the ASP.NET site). Regardless of which framework version is in use, the code will need to wait for base.SendAsync to create the response. With the C# 4 compiler, a friendly way to do this is using a task's ContinueWith method to create a continuation.
protected override Task<HttpResponseMessage> SendAsync( HttpRequestMessage request, CancellationToken cancellationToken) { return base.SendAsync(request, cancellationToken).ContinueWith( (task) => { var response = task.Result; response.Headers.Add("X-Custom-Header", "This is my custom header."); return response; } ); }
With C# 5.0, the async and await magic removes the need for ContinueWith:
async protected override Task<HttpResponseMessage> SendAsync( HttpRequestMessage request, CancellationToken cancellationToken) { var response = await base.SendAsync(request, cancellationToken); response.Headers.Add("X-Custom-Header", "This is my custom header."); return response; }
Another set of handlers will want to short-circuit the pipeline processing and send an immediate response. These types of handlers are generally validating an incoming message and returning an error if something is wrong (like checking for the presence of API required header or authorization token). There is no need to call into base.SendAsync, instead the handler will create a response and not allow the rest of the pipeline to execute. For example, a message handler to require an encrypted connection:
protected override Task<HttpResponseMessage> SendAsync( HttpRequestMessage request, CancellationToken cancellationToken) { if (!Uri.UriSchemeHttps.Equals( request.RequestUri.Scheme, StringComparison.OrdinalIgnoreCase)) { var error = new HttpError("SSL required"); var response = request.CreateErrorResponse(HttpStatusCode.Forbidden, error); var tsc = new TaskCompletionSource<HttpResponseMessage>(); tsc.SetResult(response); return tsc.Task; } return base.SendAsync(request, cancellationToken); }
There is still a call to base.SendAsync for the case when the request is encrypted, but over a regular connection the code returns an error response using a TaskCompletionSource. I think of TaskCompletionSource as an adapter for Task, since I can take code that doesn't require asynch execution (or is based on asynch events, like a timer), and make the code appear task oriented.
In .NET 4.5, Task.FromResult can accomplish the same goal with less code:
var error = new HttpError("SSL required"); var response = request.CreateErrorResponse(HttpStatusCode.Forbidden, error); return Task.FromResult(response);
This concludes another electrifying post on the ASP.NET WebAPI.