Reputation: 139
I'm have a small project that uses the Asp.Net Core Identity framework together with EF Core. One function calls the UserManager.FindByIdAsync(id) and it returns the proper object. However, it only works a few minutes after the application is started. As long as the server is busy it works fine, but as soon as the application is idle more than 1-2 minutes the request fails.
It fails with:
*OperationCanceledException: The operation was canceled.
System.Threading.CancellationToken.ThrowOperationCanceledException()*
The stacktrace looks like this:
*System.Threading.CancellationToken.ThrowOperationCanceledException()
Microsoft.AspNetCore.Identity.EntityFrameworkCore.UserStore.FindByIdAsync(string userId, CancellationToken cancellationToken)
Microsoft.AspNetCore.Identity.UserManager.FindByIdAsync(string userId)
MyProject.Areas.Admin.ControllerServices.UserService+<GetUser>d__11.MoveNext() in UserService.cs*
I'm still logged in as other pages are working fine.
A simple call to the EF context.Users.FindAsync(new object[] { id })
will work as expected, but the next line containing FindByIdAsync
will fail.
All this works perfect in the dev environment, the error occurs when the application is installed on the server running IIS on WS 2008 R2. Recycling the app pool will make it work again until it is idle again for a few minutes.
I have noted that when lines like 'Connection id "0HL5E91K33IIQ" reset.' are being logged, then the app starts to fail. Prior to that it works.
FindByIdAsync is not the only identity function to fail, many other functions fails with the same error.
What am I missing?
Upvotes: 5
Views: 2071
Reputation: 3154
In my case it boiled down to the exact same issue, but was caused by a very subtle design "flaw" in ASP.Net Core's DI implementation. For various reasons I prefer using SimpleInjector, but stuffing ASP.Net Identity Core into it is hard, compared to the nice extension methods provided for the build in container. So I put the framework stuff in the framework container, and my business stuff in SimpleInjector, deciding that "Authentication and Authorization" is considered "framework". Only the AccountController is resolved by the framework container using cross wiring. However, using app.ApplicationServices.GetService<AccountController>()
outside a request scope does not fail but returns a Singleton that will survive! Unfortunately exactly this happens when you let SimpleInjector verify it's configuration. The first request causing a malfunction (bad login) leaves your whole runtime with a defect singleton instance. Solution for this is well documented in SimpleInjectors documentation, use their extension app.GetRequiredRequestService<AccountController>()
instead.
Now, you won't get the fishy singleton instance, but an exception:
System.InvalidOperationException: 'Cannot resolve scoped service 'WebApplication15.Controllers.AccountController' from root provider.'
Upvotes: 0
Reputation: 139
I will answer my own question, and hopefully this will help someone else in the future.
For me, it all boiled down to the lifetime of the injected services.
UserManager
depends on IHttpContextAccessor
(this is where the CancellationToken
comes from) and it behaves incorrectly when lifetimes do not match up.
IHttpContextAccessor
is added as a Singleton service, while the UserManager
is added as a scoped service. My service that used the UserManager
was added as a Singleton service.
Changing this to Scoped made the errors go away.
Upvotes: 8