0

This is an issue I have been having for weeks now. It specifically seems to happen on my login action in my account controller. It also seems dependent on the machine I am using to browse to the site with, as I only run into the issue on my home machine, not my work machine. I know it is not browser specific as both are using the same version of Chrome. It is also not isolated to a single environment as I will get it debugging locally AND browsing the site itself.

At first I thought it had something to do with the fact that I was using an Async method as I am not as familiar with using them as I am with synchronous methods. I have tried stripping everything out and making the action as well as everything in it completely synchronous and was still getting the same result. I then tried replacing the redirecttoaction/route/locals to just returning a view and it worked. For further confirmation that was the culprit, I had my log in form hit a completely different action that was empty OTHER than a simple redirecttoaction("Index", "Home"), and the app just hung indefinitely. Unfortunately, just hitting a view does not work for my needs as I need it to hit the action itself in many cases.

In terms of its inconsistency, the only way I have been able to consistently reproduce it is to run the app locally and log in. I then close the browser and stop running the app. Then I run the app again>log out>log in, and it hangs.

I have done a good amount of research here on stackoverflow and other sites, and have tried many different recommendations such as changing configuration settings in the startup.cs, reinstalling dlls, and too many other things to count, and I have just had no luck solving this.

Here is the constructor, and the log in get and post action from the account controller

private myContext _context;
private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly RoleManager<IdentityRole> _roleManager;
private readonly IEmailSender _emailSender;
private readonly ISmsSender _smsSender;
private readonly ILogger _logger;

public AccountController(
    myContext context,
    UserManager<ApplicationUser> userManager,
    SignInManager<ApplicationUser> signInManager,
    RoleManager<IdentityRole> roleManager,
    IEmailSender emailSender,
    ISmsSender smsSender,
    ILoggerFactory loggerFactory)
{
    _context = context;
    _userManager = userManager;
    _signInManager = signInManager;
    _roleManager = roleManager;
    _emailSender = emailSender;
    _smsSender = smsSender;
    _logger = loggerFactory.CreateLogger<AccountController>();
}


// GET: /Account/Login
[HttpGet]
[AllowAnonymous]
public IActionResult Login(string returnUrl = null)
{
    ViewData["ReturnUrl"] = returnUrl;
    return View();
}

// POST: /Account/Login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
{
    ViewData["ReturnUrl"] = returnUrl;
    if (ModelState.IsValid)
    {
        ApplicationUser user = _userManager.Users.Where(a => a.Email == model.Email).FirstOrDefault();

        if (user != null)
        {
            var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: true);
            if (result.Succeeded)
            {

                _logger.LogInformation(1, "User logged in.");
                var RoleCustomer = _userManager.IsInRoleAsync(user, "Customer");
                var RoleAdmin = _userManager.IsInRoleAsync(user, "Admin");
                if (RoleCustomer.Result)
                {
                    return RedirectToLocal(returnUrl);
                }
                else if (RoleAdmin.Result)
                {

                    return RedirectToRoute(new { controller = "Home", action = "Admin" });
                    //return RedirectToLocal("/Home/Index");
                }
                else
                {
                    ModelState.AddModelError(string.Empty, "Invalid login attempt.");
                    return View(model);
                }

            }
            if (user.EmailConfirmed == false)
            {
                return View("ResendConfirm", new ResendConfirmViewModel() { user = user, returnUrl = returnUrl });
            }

            if (result.IsLockedOut)
            {
                _logger.LogWarning(2, "User account locked out.");
                return View("Lockout");
            }
            else
            {
                ModelState.AddModelError(string.Empty, "Invalid login attempt.");
                return View(model);
            }
        }
        else
        {
            ModelState.AddModelError(string.Empty, "Invalid login attempt.");
            return View(model);
        }


    }

    // If we got this far, something failed, redisplay form
    return View(model);
}

Here is the login form from the login view

<form asp-controller="Account" asp-action="Login" asp-route-returnurl="@ViewData["ReturnUrl"]" method="post" class="form-horizontal" role="form">
    <h4>Use a local account to log in.</h4>
    <hr />
    <div asp-validation-summary="All" class="text-danger"></div>
    <div class="form-group">
        <label asp-for="Email" class="col-md-2 control-label"></label>
        <div class="col-md-10">
            <input asp-for="Email" class="form-control" />
            <span asp-validation-for="Email" class="text-danger"></span>
        </div>
    </div>
    <div class="form-group">
        <label asp-for="Password" class="col-md-2 control-label"></label>
        <div class="col-md-10">
            <input asp-for="Password" class="form-control" />
            <span asp-validation-for="Password" class="text-danger"></span>
        </div>
    </div>
    <div class="form-group">
        <div class="col-md-offset-2 col-md-10">
            <div class="checkbox">
                <label asp-for="RememberMe">
                    <input asp-for="RememberMe" />
                    @Html.DisplayNameFor(m => m.RememberMe)
                </label>
            </div>
        </div>
    </div>
    <div class="form-group">
        <div class="col-md-offset-2 col-md-10">
            <button type="submit" class="btn btn-default">Log in</button>
        </div>
    </div>
    <p>
        <a asp-action="Register" asp-route-returnurl="@ViewData["ReturnUrl"]">Register as a new user?</a>
    </p>
    <p>
        <a asp-action="ForgotPassword">Forgot your password?</a>
    </p>
</form>

Here is the startup class

public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.Configure<CookiePolicyOptions>(options =>
            {
                // This lambda determines whether user consent for non-essential cookies is needed for a given request.
                options.CheckConsentNeeded = context => true;
                options.MinimumSameSitePolicy = SameSiteMode.None;
            });

            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

            services.AddDbContext<myContext>(options =>
                options.UseSqlServer(Configuration.GetConnectionString("WebApplication1ContextConnection")));

            services.AddIdentityCore<ApplicationUser>()
                .AddRoles<IdentityRole>()
                .AddEntityFrameworkStores<myContext>()
                .AddSignInManager()
                .AddDefaultTokenProviders();

            services.AddAuthentication(o =>
            {
                o.DefaultScheme = IdentityConstants.ApplicationScheme;
                o.DefaultSignInScheme = IdentityConstants.ExternalScheme;
            })
            .AddIdentityCookies(o => { });

            services.AddSession();

            services.Configure<IdentityOptions>(options =>
            {
                // Default Lockout settings.
                options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
                options.Lockout.MaxFailedAccessAttempts = 5;
                options.Lockout.AllowedForNewUsers = true;
                options.Password.RequireDigit = true;
                options.Password.RequireLowercase = true;
                options.Password.RequireNonAlphanumeric = true;
                options.Password.RequireUppercase = true;
                options.Password.RequiredLength = 6;
                options.Password.RequiredUniqueChars = 1;
                options.SignIn.RequireConfirmedEmail = true;
                options.SignIn.RequireConfirmedPhoneNumber = false;
                options.User.AllowedUserNameCharacters ="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+";
                options.User.RequireUniqueEmail = true;
            });

            services.ConfigureApplicationCookie(options =>
            {
                //should change this at some point to real access denied. something like Account/AccessDenied
                options.AccessDeniedPath = "/Home/index";
                options.Cookie.Name = "mycookie";
                options.Cookie.HttpOnly = true;
                options.ExpireTimeSpan = TimeSpan.FromMinutes(60);
                options.LoginPath = "/Account/Login";
                // ReturnUrlParameter requires 
                //using Microsoft.AspNetCore.Authentication.Cookies;
                options.ReturnUrlParameter = CookieAuthenticationDefaults.ReturnUrlParameter;
                options.SlidingExpiration = true;
            });

            services.Configure<PasswordHasherOptions>(option =>
            {
                option.IterationCount = 12000;
            });



            // Add application services.
            services.AddTransient<IEmailSender, AuthMessageSender>();
            services.AddTransient<ISmsSender, AuthMessageSender>();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }

            app.UseHttpsRedirection();

            app.UseSession();

            app.UseStaticFiles();

            app.UseCookiePolicy();

            app.UseAuthentication();

            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });
        }
    }

Thank you so much in advance for any help/guidance you are able to provide. I really appreciate it. If there is any way I can provide more information, please let me know.

  • You certainly [should](https://blog.stephencleary.com/2017/03/aspnetcore-synchronization-context.html) replace all those `.Result` calls with `await`s in `async Task Login`, but it would appear from your description that the problem is that the controller's constructor takes too long, which may have something to do with construction of classes passed to it via dependency injection. – GSerg Apr 25 '19 at 15:44
  • Yes, you are right. I should. That was left over from me stripping out the asynchronousness of it. You are saying that the controller's constructor takes too long? That's interesting. I have never heard of that being an issue before. I will take a look at that. – tylerboedwards Apr 25 '19 at 15:47
  • Also, if you don't mind me asking, what led you to the conclusion that the controller's constructor takes too long? – tylerboedwards Apr 25 '19 at 15:49
  • You said you've stripped the action of everything and made it synchronous and that did not help. So let's look at the controller, it requires 7 classes to be passed to it and they all have to be constructed too on each call, and redirects result in another request, so the controller is instantiated again. – GSerg Apr 25 '19 at 15:54
  • Ok, well how do I fix that exactly? Do I just try to strip some of the classes being injected and see if that helps? Is there a more efficient way I am unaware of to use dependency injection? – tylerboedwards Apr 25 '19 at 15:58
  • Yes, try removing them, or e.g. declaring them [as singletons](https://stackoverflow.com/q/38138100/11683) if that is suitable. – GSerg Apr 25 '19 at 16:00
  • I see. So for example, if I turn that IEmailSender from a transient into a singleton, it is called, it does not need to construct again. Is that correct? Obviously I am sure some of these cannot become singleton, my context seems dangerous to do so. Probably the identity based things as well. – tylerboedwards Apr 25 '19 at 16:13
  • Yes. Also, try Analyze - Performance profiler. – GSerg Apr 25 '19 at 16:17
  • I will do that. THank you for your help. I will try your suggestions tonight when I get home and let you know if it helps. – tylerboedwards Apr 25 '19 at 16:27

0 Answers0