5

I want to be able to load a user from a cloud database on each request and have that available on the request in a controller using asp.net mvc. The problem is the current framework does not support doing async operations from action filters. So OnActionExecuting, OnAuthorization methods do not allow me to do this.. for example I have the following code which DOES NOT work (so don't try it).. You get an exception : "An asynchronous module or handler completed while an asynchronous operation was still pending."

protected async override void OnAuthorization(AuthorizationContext filterContext)
{
  var user = filterContext.HttpContext.User;
  if (!user.Identity.IsAuthenticated)
  {
    HandleUnauthorizedRequest(filterContext);
    return;
  }

  using (var session = MvcApplication.DocumentStore.OpenAsyncSession())
  {
    User currentUser = await session.LoadAsync<User>(user.Identity.Name);
    if (currentUser == null)
    {
      HandleUnauthorizedRequest(filterContext);
      return;
    }

    filterContext.HttpContext.Items["User"] = currentUser;
  }
}

So is there any other way of being able to do this? I notice there is a begin execute method in the base Controller:

protected override IAsyncResult BeginExecute(RequestContext requestContext, AsyncCallback callback, object state)
{
  return base.BeginExecute(requestContext, callback, state);
}

Could I do it there possibly?

superlogical
  • 14,332
  • 9
  • 66
  • 76
  • You can [vote for async action filters here](https://aspnet.codeplex.com/workitem/9582). – Stephen Cleary May 15 '13 at 17:16
  • I've recently [published a library](https://www.nuget.org/packages/Hydrogen.Extensions.Mvc5.Async) that adds proper support for async filters (heavily based on code in from [ASP.NET MVC Core](https://github.com/aspnet/Mvc)). Source is also available here: https://github.com/jdaigle/Hydrogen.Extensions.Mvc5. – Joseph Daigle Mar 21 '17 at 02:05

1 Answers1

10

The question is three months old so I guess you've managed to work around this. Anyway, I'll add my solution here, as I had to do something similar.

I used the ToAsync method from the ParallelExtensionsExtras library. This is my class:

public class AsyncControllerBase : Controller
{
    protected override IAsyncResult BeginExecute(System.Web.Routing.RequestContext requestContext, AsyncCallback callback, object state)
    {
        return ExecuteCoreAsync(requestContext, state).ToAsync(callback, state);
    }

    protected override void EndExecute(IAsyncResult asyncResult)
    {
        IAsyncResult baseAsyncResult = ((Task<IAsyncResult>)asyncResult).Result;
        base.EndExecute(baseAsyncResult);
    }

    protected virtual async Task<IAsyncResult> ExecuteCoreAsync(System.Web.Routing.RequestContext requestContext, object state)
    {
        await DoStuffHereOrInDerivedClassAsync();

        var baseBeginExecuteCompletion = new TaskCompletionSource<IAsyncResult>();

        AsyncCallback callback = ar =>
        {
            baseBeginExecuteCompletion.SetResult(ar);
        };

        // OnActionExecuting will be called at this point
        var baseAsyncResult = base.BeginExecute(requestContext, callback, state);

        await baseBeginExecuteCompletion.Task;

        return baseAsyncResult;
    }

    protected override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        base.OnActionExecuting(filterContext);
    }
}

See also this documentation from Microsoft on converting between Task and IAsyncResult.

Johann
  • 4,107
  • 3
  • 40
  • 39
Csaba Fabian
  • 846
  • 9
  • 19
  • 1
    Note that `Controller.Request` and many other properties are `null` inside `DoStuffHereOrInDerivedClassAsync()`, better be passing `RequestContext` as an argument to access certain features. – deerchao Feb 21 '15 at 19:55
  • Why the 'empty' override of OnActionExecuting? – Dirk Boer Jul 14 '15 at 13:29
  • Hi Dirk, it's been two-and-a-half years since I wrote this, so I honestly can't remember :) Maybe may original intention was to seal the override to avoid confusion in the derived classes - but forgot to actually do it?! – Csaba Fabian Jul 14 '15 at 15:13
  • Still have to do this in mvc 5.2.3. Found an improvement: override `(Begin|End)ExecuteCore` instead of `(Begin|End')Execute`, then you can use `Controller.Request`. – deerchao Sep 19 '16 at 06:32