2

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!

dcg
  • 3,856
  • 1
  • 15
  • 27
  • Since you implemented [Fire and forget](https://stackoverflow.com/questions/12803012/fire-and-forget-with-async-vs-old-async-delegate) pattern the behavior is expected... Now what do you actually expect this code would do? – Alexei Levenkov Jun 03 '17 at 01:43
  • @AlexeiLevenkov hmmm So should I do `task.Wait()` in the `NotifyUser` action? I tried it in the attribute with no luck. – dcg Jun 03 '17 at 01:51
  • Not going to work. When it deadlocks make sure to read https://stackoverflow.com/questions/13140523/await-vs-task-wait-deadlock. Feels like you expect action filters to support asynchronous operations and it simply not the case (same for views)... I don't think you get anywhere without significant redesign as only top level actions in ASP.Net MVC supporting async. – Alexei Levenkov Jun 03 '17 at 01:52
  • @AlexeiLevenkov Don't know how there's dead lock happening 'cause it's only one request and isn't `IdentityDbContext` thread safe? – dcg Jun 03 '17 at 01:55
  • Sorry, my comment was about attempts to fix `OnActionExecuting` - there is nothing wrong with `NotifyUser` action. – Alexei Levenkov Jun 03 '17 at 01:58
  • @AlexeiLevenkov So should I implement this logic away from attributes? – dcg Jun 03 '17 at 02:00
  • If you need asynchronous operations and you care about result of the operation - yes. Otherwise fire-an-forget may work (I think you need to do some equivalent of WebForms' RegisterAsyncTask to get it working), but I'd not go that route. – Alexei Levenkov Jun 03 '17 at 02:11
  • @AlexeiLevenkov Thank you very much for your time. Never occurred to me to search for support for asynchronous operations on action filters. – dcg Jun 03 '17 at 02:13
  • 2
    Yeah, the fundamental problem is that this version of ASP.NET/MVC is tragically flawed when it comes to async operations in action filters. ASP.NET/WebApi is *way* better in this regard, but doesn't really help you. – Kirk Woll Jun 03 '17 at 02:14
  • This is also fixed in ASP.NET Core. But ASP.NET MVC Classic does not support async filters or async child actions. :/ – Stephen Cleary Jun 03 '17 at 03:48
  • 1
    Also see my [article on async ASP.NET](https://msdn.microsoft.com/en-us/magazine/dn802603.aspx) where I note this error is usually due to `async void` (i.e., `async void OnActionExecuting`), and also have a section on "Current State of Async Support". – Stephen Cleary Jun 03 '17 at 03:56

1 Answers1

0

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

B12Toaster
  • 8,944
  • 5
  • 48
  • 48