Async OnActionExecuting in ASP.NET Core's ActionFilterAttribute
Asked Answered
K

3

58

ASP.NET Core's ActionFilterAttribute has these:

public virtual void OnActionExecuting(ActionExecutingContext context);
public virtual void OnActionExecuted(ActionExecutedContext context);
public virtual Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next);

I need an async version of OnActionExecuting, which doesn't exist.

However I have a feeling that I can use OnActionExecutionAsync instead, as it also has an argument of ActionExecutingContext.

Am I correct that despite the name, they trigger at the same point in the process?

Also, what do I need to do with the next argument? Once I'm done with my stuff, do I simply need to call await next()?

Is that it? I'm unsure as I can't find docs for this.

Kapor answered 15/5, 2017 at 12:43 Comment(0)
C
116

Asynchronous filters work a bit differently: first execute code that must be executed before the action, call next() for the actual logic, finally add code to be executed after the action.

public async Task OnActionExecutionAsync(ActionExecutingContext context, 
                                         ActionExecutionDelegate next)
{

    // logic before action goes here

    await next(); // the actual action

    // logic after the action goes here
}

The documentation is here: https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/filters#implementation

Conscription answered 15/5, 2017 at 12:49 Comment(4)
Actually it's in here in the second code blockKapor
@Kapor The base implementation checks the arguments and calls the two other methods. You don't need to call it if you are not overriding the two other methods.Winnah
@Kapor You can check the source code here: github.com/aspnet/Mvc/blob/dev/src/…Winnah
For those searching if the delegate threw an exception, take the result from var result = await next(); then check for result.ExceptionWycoff
O
5

Asynchronous filters always take precedence over the synchronous filter implementations.

According to the Docs:

  • It's advised to implement either the synchronous or the async version of a filter interface, not both. The runtime checks first to see if the filter implements the async interface, and if so, it calls that. If not, it calls the synchronous interface's method(s). If both asynchronous and synchronous interfaces are implemented in one class, only the async method is called.

However, you can manage to have both. For instance:

public class TimestampFilter : IActionFilter, IAsyncActionFilter 
{    
    public void OnActionExecuting(ActionExecutingContext context)    
    {         
        context.ActionDescriptor.RouteValues["timestamp"] = DateTime.Now.ToString();    
    }

    public void OnActionExecuted(ActionExecutedContext context)    
    {         
        var ts = DateTime.Parse(context.ActionDescriptor. RouteValues["timestamp"]).AddHours(1).ToString();        
        context.HttpContext.Response.Headers["X-EXPIRY-TIMESTAMP"] = ts;    
    }

     public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)    
    {        
        this.OnActionExecuting(context);        
        var resultContext = await next();
        this.OnActionExecuted(resultContext);    
    }
 }

Ortensia answered 23/9, 2019 at 6:47 Comment(0)
W
1

An ever better pattern:

public override async Task OnActionExecutionAsync(
    ActionExecutingContext context, 
    ActionExecutionDelegate next)
{
    try
    {
        // logic before action goes here
    }
    finally
    {
        // await base.OnActionExecutionAsync(context, next); //dont
        await next(); // the actual action

        // logic after the action goes here
    }
}
Wooldridge answered 4/3, 2021 at 7:47 Comment(2)
This causes actions to be invoked multiple times per request. do not use this method.Revenuer
I have improved the implementation to address your concerns @AdamKWooldridge

© 2022 - 2024 — McMap. All rights reserved.