Search code examples
c#asp.net-web-apiasync-awaittaskaction-filter

IActionFilter returning a new Task<HttpResponseMessage> never returns


In an ASP.NET Web API project, I have an action filter which checks for model state errors and returns the Bad Request status code if there are any. It looks like this:

public class ValidationFilter : IActionFilter
{
    public Task<HttpResponseMessage> ExecuteActionFilterAsync(HttpActionContext context,
                                                    CancellationToken cancellationToken,
                                           Func<Task<HttpResponseMessage>> continuation)
    {
        if(!actionContext.ModelState.IsValid)
        {
            return new Task<HttpResponseMessage>(() =>
                  actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest,
                                                            actionContext.ModelState);
        }
        return continuation();
    }
}

Now, for some reason, any request with a model state error never returns. It just hangs there. If I debug, I get to the next filter in the pipeline, which starts with

var result = await continuation(); 

If I "Step Over" that line, the debugger sort of drops out to "waiting" mode, but no more code seems to be run.

I assume all of this is because I've somehow misunderstood how all these things interact, but despite hours of googling and reading, I still can't figure out how to make this work properly. Any help - both for understanding and bugfixing - is deeply appreciated.


Solution

  • You never start your task. You need to call Start when you use the Task constructor. Instead of calling the constructor and then Start a better option would be to use Task.Run:

    if(!actionContext.ModelState.IsValid)
    {
        return Task.Run(() =>
              actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest,
                                                        actionContext.ModelState);
    }
    

    In your case there's nothing really asynchronous about your operation, so you can simply use Task.FromResult to create a task with the result you get synchronously:

    public Task<HttpResponseMessage> ExecuteActionFilterAsync(
        HttpActionContext context,
        CancellationToken cancellationToken,
        Func<Task<HttpResponseMessage>> continuation)
    {
        if(!actionContext.ModelState.IsValid)
        {
            return Task.FromResult(actionContext.Request.CreateErrorResponse(
                HttpStatusCode.BadRequest, 
                actionContext.ModelState);
        }
        return continuation();
    }