4

Ok so bear with me this might take some explaining, i have a simple Account controller thus;

[RoutePrefix("api/account")]
[Authorize]
[HmacAuthentication]
public class AccountController : ApiController
{
    public async Task<IHttpActionResult> Register(UserModel userModel)
    {
        if (!this.ModelState.IsValid)
        {
            return this.BadRequest(this.ModelState);
        }

        IdentityResult result = await this._userService.RegisterUser(userModel);

        var errorResult = this.GetErrorResult(result);
        if (errorResult != null)
        {
            return errorResult;
        }

        return this.Ok();
    }
}

The HmacAuthentication attribute is here:

public class HmacAuthenticationAttribute : Attribute, IAuthenticationFilter
{
    public Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
    {
        ...

        var isValid = this.IsValidRequest(req, appId, incomingBase64Signature, nonce, requestTimeStamp);

        if (isValid.Result)
        {
            ...
        }
        ...
    }

    private async Task<bool> IsValidRequest(
        HttpRequestMessage req, 
        string appId, 
        string incomingBase64Signature, 
        string nonce, 
        string requestTimeStamp)
    {
        ...
        var user = await this.UserService.FindUser(userId); // this never gets a return value
        ...
    }
}

The method called in the UserService is this:

public async Task<ApplicationUserModel> FindUser(int id)
{
    var user = await this._userBusiness.FindAsync(id);
    return this.MapToModel(user);
}

and in the business class is this:

public async Task<ApplicationUser> FindAsync(int id)
{
    var result = await this._userManager.FindByIdAsync(id);
    return result;
}

The problem that i am facing is that when the Register method is called the HmacAuthentication attribute fires and the AuthenticateAsync filter method executes. The call in IsValidRequest to lookup a user is never actually getting a return value, if i try a request through postman it never completes.

Can anyone help with this please?

Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
Neil Stevens
  • 3,534
  • 6
  • 42
  • 71
  • What do you do with `isValid`? Do you do an `await` on it anywhere? – sstan Jul 04 '15 at 01:57
  • 1
    Can you tell if the code is stuck at the line `isValid.Result`? If so, you are probably experiencing a deadlock. Read [here](http://stackoverflow.com/questions/17248680/await-works-but-calling-task-result-hangs-deadlocks) for why you usually do not want to use `Task.Result` to wait on a task. – sstan Jul 04 '15 at 02:03
  • sometimes var is a bad thing, it makes you forget the await... – Ryan Chu Jul 04 '15 at 02:04
  • @sstan thanks i just assumed was the above line since the debugger never got that far, the link helped as well as the answer below – Neil Stevens Jul 04 '15 at 02:15

2 Answers2

5
public async Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
    {
        ...

        var isValid = await this.IsValidRequest(req, appId, incomingBase64Signature, nonce, requestTimeStamp);

        if (isValid)
        {
            ...
        }
        ...
    }

As the compiler suggested, you can only use the await keyword inside of a method marked as async. So I have updated the signature for AuthenticateAsync accordingly. Also note that you can just check isValid now instead of doing isValid.Result since the value of the boolean will be available past the await line.

This can be a little confusing as the interface for IAuthenticationFilter doesn't specify async (interfaces can't, they can only indicate that the method will return a Task). It is up to the implementer to determine whether or not the method will simply return the task or will supply an async so that values can be awaited inside the method body.

Jesse Carter
  • 20,062
  • 7
  • 64
  • 101
  • I tried that but i get a compile time error `The 'await' operator can only be used in a method or lambda marked with the 'async' modifier` the method i am calling from is on a framework interface `public Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)` – Neil Stevens Jul 04 '15 at 02:03
  • I didnt realize you could decorate the method with async that wasnt your method i am still trying to get my head around the await/async, but that did the trick, also thanks on the comment above from @sstan it was indeed blocked on the `isValid.Result` assumed it was the preceeding line since i never got that far when stepping through – Neil Stevens Jul 04 '15 at 02:13
  • You can only do it on interface methods that return Tasks. async/await definitely takes a little bit to wrap your head around but it is insanely powerful and once you get into it you'll find yourself wondering how you ever coded without it :) – Jesse Carter Jul 04 '15 at 02:14
4

The accepted answer is correct in how to fix your problem, but doesn't explain why this happens.

This:

var isValid = this.IsValidRequest(
                  req, 
                  appId, 
                  incomingBase64Signature, 
                  nonce, 
                  requestTimeStamp);

if (isValid.Result)

Is causing your code to deadlock. You're synchronously blocking on an async method. This happens because when the compiler sees an async method, it generates a state-machine which takes everything after your first await and wraps it as a continuation. The compiler also sees if there are any synchronization contexts involved, and if there are, it attempts to marshal the continuation back onto the AspNetSynchronizationContext, which is blocked by your .Result call, effectively causing a deadlock.

This is why you shouldn't block on async code

Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321