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?
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With