Asynchronous action filters in ASP.NET Web API

Β· 589 words Β· 3 minutes to read

It is rather to common to use filters in Web API to perform common tasks around your actions in an AOP (aspect oriented programming) way.

To create a filter you simply inherit from an abstract ActionFilterAttribute class and override the relevant method:

public abstract class ActionFilterAttribute : FilterAttribute, IActionFilter  
{  
public virtual void OnActionExecuting(HttpActionContext actionContext)  
{}

public virtual void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)  
{}  
}  

This is all nice and simple, but what if the operation you need to perform, should by asynchronous? Surely async void is a terrible idea.

Avoiding the async void issue πŸ”—

So if you read the linked article about the async void it’s quite clear we cannot use that approach.

However, there is nothing that forces you to create filters by inheriting from ActionFilterAttribute - instead you can implement IActionFilter manually.

That one is completely asynchronous:

public interface IActionFilter : IFilter  
{  
Task<HttpResponseMessage> ExecuteActionFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> continuation);  
}  

ActionFilterAttribute simply wraps around this interface and hides (!) its asynchronous nature, only exposing the void virtual hooks.

Another point worth mentioning, is that - as you have probably already noticed - the interface doesn’t have pre- and post- action hooks but rather a single method and a continuation.

This means that to perform an activity before action executes, we do it straight away, and to perform an activity after the execution, we have to await the continuation and perform our activity afterwards.

Implementing IActionFilter πŸ”—

To illustrate all this best, let’s start the implementation:

public class AsyncFilter : FilterAttribute, IActionFilter  
{  
public async Task<HttpResponseMessage> ExecuteActionFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken,  
Func<Task<HttpResponseMessage>> continuation)  
{  
await InternalActionExecuting(actionContext, cancellationToken);

if (actionContext.Response != null)  
{  
return actionContext.Response;  
}

HttpActionExecutedContext executedContext;

try  
{  
var response = await continuation();  
executedContext = new HttpActionExecutedContext(actionContext, null)  
{  
Response = response  
};  
}  
catch (Exception exception)  
{  
executedContext = new HttpActionExecutedContext(actionContext, exception);  
}

await InternalActionExecuted(executedContext, cancellationToken);  
return executedContext.Response;  
}

public virtual Task InternalActionExecuting(HttpActionContext actionContext, CancellationToken cancellationToken)  
{  
//pre execution hook  
}

public virtual Task InternalActionExecuted(HttpActionExecutedContext actionExecutedContext,  
CancellationToken cancellationToken)  
{  
//post execution hook  
}  
}  

So what we did here is we have added two methods returning Task representing our pre/post processing hooks (I marked them public virtual so that the filter can be reused, but you might as well use privates if that suits you). The methods return Task so they can be async and await for stuff internally.

Then in the actual interface implementing method we await for the pre-processing hook, and if it sets a response (a.k.a. a “short-circuited” response), we return it. Otherwise, we await the continuation (which is the actual action executing) and continue with the post processing hook (we have to create HttpActionExecutedContext manually).

It’s all pretty simple and quite easy to implement by hand and as a result we have fully fledged support for async filters in our Web API.

Why isn’t this part of the core framework? πŸ”—

You might ask, why do you have to jump through these hoops to have async filter. It clearly seems to be an oversight in the framework code.

Fortunately, the team has already addressed that and in the next upgrade to Web API, async filters will be supported out of the box, and the new ActionFilterAttribute will have two additional methods to override (this is actually already available in the nightly builds):

public abstract class ActionFilterAttribute : FilterAttribute, IActionFilter  
{  
public virtual void OnActionExecuting(HttpActionContext actionContext)  
{}

public virtual void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)  
{}

public virtual Task OnActionExecutingAsync(HttpActionContext actionContext)  
{}

public virtual Task OnActionExecutedAsync(HttpActionExecutedContext actionExecutedContext)  
{}  
}  

Until that upgrade, you can use the workaround shown here.

About


Hi! I'm Filip W., a software architect from ZΓΌrich πŸ‡¨πŸ‡­. I like Toronto Maple Leafs πŸ‡¨πŸ‡¦, Rancid and quantum computing. Oh, and I love the Lowlands 🏴󠁧󠁒󠁳󠁣󠁴󠁿.

You can find me on Github, on Mastodon and on Bluesky.

My Introduction to Quantum Computing with Q# and QDK book
Microsoft MVP