0

In ASP Identity Framework 2 UserManager has sync wrappers for many of its async methods. Unfortunately this sync wrappers are using AsyncHelper, say FindById(...):

 return AsyncHelper.RunSync(() => manager.FindByIdAsync(userId));

Examining the RunSync method it turns out it runs the method in an other thread:

    public static TResult RunSync<TResult>(Func<Task<TResult>> func)
    {
        var cultureUi = CultureInfo.CurrentUICulture;
        var culture = CultureInfo.CurrentCulture;
        return _myTaskFactory.StartNew(() =>
        {
            Thread.CurrentThread.CurrentCulture = culture;
            Thread.CurrentThread.CurrentUICulture = cultureUi;
            return func();
        }).Unwrap().GetAwaiter().GetResult();
    }

However in this other thread the HttpContext.Current will be null, because it is thread local. Unfortunately the ASP Identity Framework 2 relies on HttpContext.Current, for example the Seed method calls InitializeIdentityForEF, which tries to access the context this way, and throws a null reference exception:

    protected override void Seed(ApplicationDbContext context)
    {
        InitializeIdentityForEF(context);
        base.Seed(context);
    }

    //Create User=Admin@Admin.com with password=Admin@123456 in the Admin role        
    // ReSharper disable once InconsistentNaming
    public static void InitializeIdentityForEF(ApplicationDbContext db)
    {
        var userManager = HttpContext.Current.GetOwinContext().GetUserManager<ApplicationUserManager>();
        var roleManager = HttpContext.Current.GetOwinContext().Get<ApplicationRoleManager>();

My main question: How can I call the UserManager's (or others) async methods without risking to marshalled to an other thread, where HttpContext.Current will be null, which can cause unexpected behavior (within the identity framework itself)? Obviously the provided sync wrappers are unusable.

Before someone are asking why I would like to call in sync: Say I would like to use UserManager in a property get, where await is not an option. Besides of this the sync wrappers do exist (for a reason I think) just unusable, or at least introduce the worst kind of bug: a random null in random places.

g.pickardou
  • 32,346
  • 36
  • 123
  • 268
  • 2
    Can't you use the `.Result` for the properties? Like `manager.FindByIdAsync(userId).Result;` I think it is not necessary to create a task for this both block until the result is populated (like the `AsyncHelper`) – Martijn van Put Apr 28 '15 at 19:41
  • Are you actually trying to run `Seed` method in `Global.asax.cs`? – trailmax Apr 28 '15 at 21:52
  • trailmax: No. Please read my comments below starting with "The Seed method runs implicitly..." – g.pickardou Apr 29 '15 at 09:49
  • Try to create tasks with `TaskScheduler.FromCurrentSynchronizationContext()`. This can help for the providing the `HttpContext` to your async code. – VMAtm Apr 29 '15 at 09:50

1 Answers1

1

HttpContext.Current is thread-safe and available on thread other than the one running your controller actions.

I suspect you are running your Seed method in Application_Start where async operations are not available. Also there is no HTTP request going, because the application starting up and it can't accept requests. Hence you get null in HttpContext.Current.

To avoid this issue, instead of trying to get your UserManager from OwinContext, just create them yourself. Create all the dependencies and inject everything that needs to be injected.

trailmax
  • 34,305
  • 22
  • 140
  • 234
  • The Seed method runs implicitly, not I am who is running it explicitly. It runs if detects the database must be created and filled with init data. No, I am not running it from Application_Start. It runs (implicitly) when I use UserManager in my HomeController. However as I described for some reason I use the sync version of the FindById, which uses AsyncHelper, so the method will run in an other thread consequently the Seed will run in an other thread. In this thread the HttpContext.Current is null, (while it was not null in that thread where I called the sync FindById. – g.pickardou Apr 29 '15 at 09:48
  • 1
    That's strange, because I could totally create a new thread in controller and still access `HttpContext.Current` there. And sync methods worked for me with no problem in the same scenarios you describe. – trailmax Apr 29 '15 at 20:19