WebAPI Tip #8: Working with Tasks

Monday, April 8, 2013

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.


Comments
gravatar Anna Melo Tuesday, April 9, 2013
In .NET 4.5, rather than using Task.FromResult, thought you would just add async keyword to the SendAsync method and then return response directly (as you did with the custom header example). Is there anything wrong with this approach?
gravatar scott Tuesday, April 9, 2013
@Anna: The Task.FromResult example is creating it's own result for an error condition, I only want to call into SendAsync if I want processing to continue through the pipeline. Hope that explanation makes sense.
gravatar Anna Melo Tuesday, April 9, 2013
I mean like this: protected override async Task HttpResponseMessage SendAsync( HttpRequestMessage request, CancellationToken cancellationToken) { if (!Uri.UriSchemeHttps.Equals( request.RequestUri.Scheme, StringComparison.OrdinalIgnoreCase)) { var error = new HttpError("SSL required"); return request.CreateErrorResponse(HttpStatusCode.Forbidden, error); } return base.SendAsync(request, cancellationToken); }
gravatar scott Tuesday, April 9, 2013
@Anna: request.CreateErrorResponse(...) would be a compiler error (because we have to return a Task of HttpResponseMessage, Task.Result is the easiest way to wrap that response in a task. No need to await anything in that case since the response can be constructed without i/o.
gravatar Anna Melo Tuesday, April 9, 2013
@Scott - not if the method uses the async keyword in the definition - just like your header example where you directly return the response. Just seems a bit cleaner but was wondering if there was any downside.
gravatar scott Tuesday, April 9, 2013
@Anna: Oh, yes, sorry, so you want to go the other direction and add an await on the call to SendAsync. Either approach will be fine, it all comes down to personal preference at that point.
Comments are closed.

My Pluralsight Courses

K.Scott Allen OdeToCode by K. Scott Allen
What JavaScript Developers Should Know About ECMAScript 2015
The Podcast!