dcg
dcg

Reputation: 4219

Asynchronous task pending in custom action filter?

I need your help! I've been a while around this and I haven't been able to figure it out. Let's suppose we have the following controller:

class NotifierController : MyBaseController {
    ... 
    [HttpGet]
    [CanNotifyUser]
    public async Task<ActionResult> NotifyUser(string id)
    {
        return View(await DataBase.Users.FindAsync(id));
    }
    // More actions decorated with the CanNotifyUserAttribute attribute
}

DataBase is a property defined in MyBaseController like:

static ApplicationDbContext _appDbContext;
public ApplicationDbContext DataBase 
{ 
    get { return _appDbContext ?? (_appDbContext = new ApplicationDbContext()); } 
}

ApplicationDbContext inherits from IdentityDbContext<MyUser> where MyUser inherits from IdentityUser. So, my problem (I think) comes to the custom action filter CanNotifyUserAttribute, whose OnActionExecuting override is defined as (removing all checks to reduce as much as possible):

public override async void OnActionExecuting(ActionExecutingContext filterContext)
{
    NotifierController controller = filterContext.Controller as NotifierController;
    Boss boss = await controller.DataBase.Bosses.FindAsync(filterContext.HttpContext.User.Identity.GetUserId());
    object id = filterContext.ActionParameters["id"];
    User user = await controller.DataBase.Users.FindAsync(id.ToString());
    if (user.Department.Id != boss.Department.Id)
    {
        filterContext.Result = new RedirectToRouteResult(
            new RouteValueDictionary(
                new
                {
                    action = "Login",
                    controller = "Account",
                    returnUrl = controller.Url.Action("NotifyUser", new { id = id })
                }));
    }
    base.OnActionExecuting(filterContext);
}

The idea with this attribute is to verify that a boss sends notifications to the users of his department only. Now comes the strange part, this only happens the first time I try to send a notification to an user (no matter whether it's of the same department or not), the error thronw is:

[InvalidOperationException: An asynchronous module or handler completed while an asynchronous operation was still pending.]

If I try (after the error) the same request (i.e., notify the user) it works as expexted either redirecting or sending the notification.

According to this post I might be finishing the request (If I understood well) before an asynchronous task is finished, but all the tasks I've been using are all awaited so I don't know where this error may come from. Hope you can help me. Thanks in advance!

Upvotes: 2

Views: 2566

Answers (1)

Felix K.
Felix K.

Reputation: 15653

You are trying to enforce async behaviour on your filter by turning OnActionExecuting into an async void. asnyc void methods should only be used in the context of asynchronous event handlers due to their special error-handling semantics and the "inability" of the calling process to wait for the tasks executed inside your (filter-)method's body (for further reading, consider this great article by Stephen Cleary).

Therefore, with ASP.NET Core, consider using the IAsyncActionFilter interface instead.

Filters support both synchronous and asynchronous implementations through different interface definitions. [...] Asynchronous filters define a single OnStageExecutionAsync method. This method takes a FilterTypeExecutionDelegate delegate which executes the filter's pipeline stage. (Filters in ASP.NET Core)

So instead of...

public override async void OnActionExecuting(ActionExecutingContext filterContext)

...use:

public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)

For further instructions on how to use this see: Async OnActionExecuting in ASP.NET Core's ActionFilterAttribute

Upvotes: 2

Related Questions