Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How should a DelegatingHandler make an async call (ASP.NET MVC Web API)?

I am comfortable with executing synchonous work before calling the inner handler's SendAsync(), and executing synchronous work after the inner handler has completed via a completion. e.g:

protected override Task<HttpResponseMessage> SendAsync( 
    HttpRequestMessage request, CancellationToken cancellationToken) 
{ 
    // do some sync work before inner handler here 

    var sendTask = base.SendAsync(request, cancellationToken); 
    return sendTask.ContinueWith( 
        task => { // do some sync work afterwards here }); 
} 

However, I now need to call an IO bound operation from within a delegating handler. The IO bound operation is already wrapped up as a Task<bool>. I need to use the result to determine whether to continue on to the inner handler.

An example would be making a network call to authorize a request. I have to do this to integrate with an existing system. In general, I think there are valid scenarios for this problem, and it should have a workable solution.

What's the right way to implement SendAsync in this case, so that I execute the IO bound Task asynchronously and then continue to asynchronously execute the inner handler?

The key point is that I want to be certain that the request thread is not left blocked at any point.

like image 835
James World Avatar asked Jun 14 '12 14:06

James World


1 Answers

OK, I think I have this cracked. I'm illustrating this with an authentication scenario: I want to authenticate a user asynchronously and use the result to decide whether to return a 401 or continue with the message handler chain.

The central problem is that you cannot call the inner handler SendAsync() until you have the result from the asynchronous authentication.

The key insight for me was to use a TaskCompletionSource (TCS) to control the flow of execution. This enabled me to return the Task from the TCS and set a result on it whenever I liked - and most importantly to delay the call the SendAsync() until I know I need it.

So, I set up the TCS and then started a task to do the authorization. In the continuation to this, I look at the result. If authorized, I invoke the inner handler chain and attach a continuation to this (avoiding any thread blocking) that completes the TCS. If authentication fails I just complete the TCS there and then with a 401.

The result of this is that both asynchronous tasks are executed in turn without any thread blocking. I load tested this and it seems to work just fine.

It's all much nicer in .NET 4.5 with the async/await syntax though... although the approach with the TCS is still basically happening under the covers, the code is much simpler.

Enjoy!

The first snippet was built on .NET 4.0 with the Web API Beta - the second on .NET 4.5/Web API RC.

protected override Task<HttpResponseMessage> SendAsync(
    HttpRequestMessage request, CancellationToken cancellationToken)
{
    var taskCompletionSource = new TaskCompletionSource<HttpResponseMessage>();

    // Authorize() returns a started
    // task that authenticates the user
    // if the result is false we should
    // return a 401 immediately
    // otherwise we can invoke the inner handler
    Task<bool> authenticationTask = Authorize(request);

    // attach a continuation...
    authenticationTask.ContinueWith(_ =>
    {
        if (authenticationTask.Result)
        {
            // authentication succeeded
            // so start the inner handler chain
            // and write the result to the
            // task completion source when done
            base.SendAsync(request, cancellationToken)
                .ContinueWith(t => taskCompletionSource.SetResult(t.Result));
        }
        else
        {
            // authentication failed
            // so complete the TCS immediately
            taskCompletionSource.SetResult(
                new HttpResponseMessage(HttpStatusCode.Unauthorized));
        }
    });

    return taskCompletionSource.Task;
}

Here is a .NET 4.5 / Web API Release Candidate version which is a lot sexier with the new async/await syntax:

protected override async Task<HttpResponseMessage> SendAsync(
    HttpRequestMessage request, CancellationToken cancellationToken)
{
    // Authorize still has a Task<bool> return type
    // but await allows this nicer inline syntax
    var authorized = await Authorize(request);

    if (!authorized)
    {
        return new HttpResponseMessage(HttpStatusCode.Unauthorized)
        {
            Content = new StringContent("Unauthorized.")
        };
    }

    return await base.SendAsync(request, cancellationToken);
}
like image 81
James World Avatar answered Nov 10 '22 13:11

James World