10

I'm building a .NET core web app in F#, and am trying to set up Identity. I have got a basic version of a Register method working, which creates a user in the database and creates the cookie:

[<HttpPost>]
[<AllowAnonymous>]
member this.Register([<FromBody>]model: RegisterViewModel) =
    if not (isNull model.Email) && model.Password = model.ConfirmPassword then
        let user = ApplicationUser(userName = model.Email, email = model.Email, password = model.Password)
        let result = userManager.CreateAsync(user, model.Password) |> Async.AwaitTask |> Async.RunSynchronously
        if result.Succeeded then
            signInManager.SignInAsync(user, isPersistent = false) |> Async.AwaitTask |> Async.RunSynchronously
            true
        else
           false 
    else
        false

However, my Login implementation hangs:

[<HttpPost>]
[<AllowAnonymous>]
member this.Login([<FromBody>]model: LoginViewModel) =
    if not (isNull model.Email && isNull model.Password) then
        let result = signInManager.PasswordSignInAsync(model.Email, model.Password, false, lockoutOnFailure = false) |> Async.AwaitTask |> Async.RunSynchronously
        if result.Succeeded then true else false
    else
        false

This is where the application hangs:

info: Microsoft.EntityFrameworkCore.Storage.IRelationalCommandBuilderFactory[1]
  Executed DbCommand (5ms) [Parameters=[@__normalizedUserName_0='?' (Size = 256)], CommandType='Text', CommandTimeout='30']
  SELECT TOP(1) [u].[Id], [u].[AccessFailedCount], [u].[ConcurrencyStamp], [u].[Email], [u].[EmailConfirmed], [u].[LockoutEnabled], [u].[LockoutEnd], [u].[NormalizedEmail], [u].[NormalizedUserName], [u].[PasswordHash], [u].[PhoneNumber], [u].[PhoneNumberConfirmed], [u].[SecurityStamp], [u].[TwoFactorEnabled], [u].[UserName]
  FROM [AspNetUsers] AS [u]
  WHERE [u].[NormalizedUserName] = @__normalizedUserName_0

Any idea what the problem could be?

Dan O'Leary
  • 2,660
  • 6
  • 24
  • 50
  • Have you tried attaching a debugger? Is it hanging inside the call to `PasswordSignInAsync`? – kvb Dec 14 '16 at 15:38
  • yep, I'm pretty sure it's hanging inside PasswordSignInAsync - I haven't been able to attach a debugger in vs code for F# though. Strangely, when signInManager.SignInAsync is called from the Register method, that works fine. – Dan O'Leary Dec 14 '16 at 15:54
  • I doubt this is the issue, but it seems like you should be using `||` instead of `&&` in the check in `Login`. – kvb Dec 14 '16 at 21:01
  • You're right, that was wrong. It wasn't the issue causing the hang though. I've built a simple .net core app in c# on the same machine, and the login functionality works ok there. I tried using the same db with my F# app, and the problem persists, so it does seem like it's a code issue somewhere. – Dan O'Leary Dec 15 '16 at 09:25
  • What happens if you just use `_.Result` instead of `_ |> Async.AwaitTask |> Async.RunSynchronously`? There seems to be no benefit to creating F# `Async<_>` objects from your `Task<_>` in this case. – kvb Dec 15 '16 at 15:38
  • 4
    I can't tell for sure, but it sounds like maybe [this GitHub issue](https://github.com/aspnet/Identity/issues/789) is describing a similar issue? Does `Login` hang when you pass in incorrect information, too? – kvb Dec 15 '16 at 15:47
  • it seems like it might be related to that issue, but I am using the default identity roles. If I try and login with an email for which there isn't a user, the method returns false (as expected). – Dan O'Leary Dec 16 '16 at 12:01
  • Ah, you were correct! It was actually the same as the issue on Github. I'd added an extra constructor to the ApplicationUser class. Removing that has somehow resolved it. Thanks for your help! – Dan O'Leary Dec 16 '16 at 19:39
  • 1
    Is there a particular reason you're not actually letting this run async? – Chris Pratt Mar 29 '18 at 15:09
  • 1
    Using `Async.RunSynchronously` is the same as using `.Wait()` or `.Result`. It will block the current thread. Don't do it. Make the controller action asynchronous instead of blocking it. – Panagiotis Kanavos Dec 19 '18 at 10:30
  • See my SO question [here](https://stackoverflow.com/questions/57869267/calling-c-sharp-async-method-from-f-results-in-a-deadlock) - I am sure it's the same or similar problem. I used to use the same `|> Async.AwaitTask |> Async.RunSynchronously` chain and started getting deadlocks. – zidour Mar 01 '20 at 16:41

1 Answers1

1

According to this article, the handlers can be async (in the C# sense) so it makes sense to rewrite your route-handler to be non-blocking, thus removing the Async.RunSynchronously that is likely causing the issue.

We can write the actual logic in an F# async workflow, because that is more idomatic, and then convert it to a Task<_> in order to match the expected C# signature.

[<HttpPost>]
[<AllowAnonymous>]
member this.Login([<FromBody>]model: LoginViewModel) =
  async {
    if not (isNull model.Email && isNull model.Password) 
    then
      let! result = 
        signInManager.PasswordSignInAsync(model.Email, model.Password, false, lockoutOnFailure = false) 
        |> Async.AwaitTask 

      return result.Succeeded
    else
      return false
  }
  |> Async.StartAsTask

Dead-locks can be very hard to hunt down, so when using F# async you should try to have only one call to Async.RunSynchronously in your application, usually in the "main" method.

sdgfsdh
  • 33,689
  • 26
  • 132
  • 245