The majority of functions in ASP.Net Identity are asynchronous. There are helper extension methods which allow you to run these synchronously (which the ASP.Net Identity reference project uses), however:
- If I use the synchronous extension methods,
HttpContext.Current
is null. - If I call the async methods directly,
HttpContext.Current
is non-null.
The problem as described is pretty much identical to this question, but seeks to understand why it behaves this way, not just to solve the problem.
The reason I'm running into this issue is that I want to extend the login process to check the request IP address against a user's whitelist IP addresses - but it doesn't work if I can't access the HttpContext.
In trying to work out why this issue occurs, I've learned that ASP.Net does it's own thing with synchronisation contexts to make sure that HttpContext.Current
is available for async calls, which is why it works when I call the async method directly.
Why would you use the extension methods that allow async calls to run synchronously, if it breaks access to HttpContext.Current
. Is this a bug?
public class LoginPage : Page
{
public void Login(string user, string password)
{
var signInManager = HttpContext.Current.GetOwinContext().Get<MySignInManager>();
signInManager.PasswordSignIn(user, password, false, false); // this is a synchronous extension method
}
}
public class MySignInManager<TUser, TKey> : Microsoft.Aspnet.Identity.Owin.SignInManager<TUser, TKey>
{
public override async Task<SignInStatus> PasswordSignInAsync(string username, string password, bool isPersistent, bool shouldLockout)
{
...
string ipAddress = GetIpAddress();
...
}
private string GetIpAddress()
{
// HttpContext.Current is null if using the PasswordSignIn extension method
var request = HttpContext.Current.GetOwinContext().Request;
return request.RemoteIpAddress;
}
}
// namespace Microsoft.AspNet.Identity.Owin
public static class SignInManagerExtensions
{
public static SignInStatus PasswordSignIn(this SignInManager<TUser, TKey> manager, string username, string password, bool isPersistent, bool shouldLockout)
{
return AsyncHelper.RunSync(() => manager.PasswordSignInAsync(userName, password, isPersistent, shouldLockout));
}
}
// namespace Microsoft.AspNet.Identity
internal static class AsyncHelper
{
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();
}
}