To keep this question simple, I'll describe the higher level problem and then go into any implementation details if needed.
I use the ASP.NET Identity in my application under development. In a specific scenario on a series of requests, the UserManager first get the current user(at least one FindById request), where the user is fetched. On a subsequent request, I update information on this user that is saved by UserManager.Update and I can see the change persisted in the database.
The problem is here that on further subsequent requests, the user object gotten from FindById is not updated. That is strange, but could be something about caching in UserManager I do not understand. However, when I trace the database calls, I see that UserManager indeed is sending the sql-requests to the database for getting the user.
And this is where it gets really strange - even though the database is confirmed to be up to date, UserManager still somehow returns an old object from this process. When I myself run exactly the same query traced directly to the database, I get updated data as expected.
What is this black magic?
Obviously, something is cached somewhere, but why does it make a query to the database, just to disregard the updated data it gets?
Example
This below example updates everything as expected in the db for each request to the controller action, and when GetUserDummyTestClass is calling findById on the other instance of UserManager I can trace the sql requests, and can test these directly to the db and verify that they return updated data. However, the user object returned from that very same line of code still has the old values (in this scenario, the first edit after the application was started, regardless of how many time the Test action is invoked).
Controller
public ActionResult Test()
{
var userId = User.Identity.GetUserId();
var user = UserManager.FindById(userId);
user.RealName = "name - " + DateTime.Now.ToString("mm:ss:fff");
UserManager.Update(user);
return View((object)userId);
}
Test.cshtml
@model string
@{
var user = GetUserDummyTestClass.GetUser(Model);
}
@user.RealName;
GetUserDummyTestClass
public class GetUserDummyTestClass
{
private static UserManager<ApplicationUser> _userManager;
private static UserManager<ApplicationUser> UserManager
{
get { return _userManager ?? (_userManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext()))); }
}
public static ApplicationUser GetUser(string id)
{
var user = UserManager.FindById(id);
return user;
}
}
Update
As Erik pointed out, I should not use static UserManagers. However, if I keep the UserManager in GetUserDummyTest bound to the HttpContext (persisting it per HttpRequest) in case I want to use it several times during a request, it is still caching the first User object it gets by a Id, and disregarding any updates from another UserManager. Thus suggesting that the real issue is indeed that I'm using two different UserManagers as trailmax suggested, and that it's not designed for this kind of usage.
In my example above, if I keep the UserManager in GetUserDummyTestClass persistent over the HttpRequest, add a Update-method and only use this in the controller, everything works fine as expected.
So if going to a conclusion, would it be correct to state that if I want to use logic from a UserManager outside of the scope of the controller, I have to globalize the UserManager instance in an appropriate class where I can bind the instance to the HttpContext, if I want to avoid creating and disposing instances for one-time usage?
Update 2
Investigating a little further, I realized that I am indeed intended to use one instance per request, and that this already actually is set up for the OwinContext in Startup.Auth and later accessed like this:
using Microsoft.AspNet.Identity.Owin;
// Controller
HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>()
// Other scopes
HttpContext.Current.GetOwinContext().GetUserManager<ApplicationUserManager>()
This is actually embarrassingly obvious looking at the setup of the default AccountController provided, but I guess the rather strange and unexpected behavior described above proved quite distracting. Still, it would be interesting to understand the reason for this behavior, even though it will not be a problem anymore using OwinContext.GetUserManager.
The UserManager class we have as a dependency looks like this: This is simply a convenience subclass of UserManager<TUser, TKey> that we can use when our user ID’s are going to be strings. The base class definition is as follows:
The AdminController uses the Authorize attribute with the policy IsAdmin to authorize. The AuthenticationSchemes needs to be set to “Identity.Application”, because Identity is being used. Now admins can create, or edit Identity users. When the application is started, the ADMIN menu can be clicked, and the users can be managed by administrators.
Lets spin up a new class that inherits from the built-in UserManager: You can generate the constructor automatically with Visual Studio by using the Generate Constructor Quick Action (by pressing CTRL-ENTER). The first parameter of constructor is store and for implementing another method to add role to a user by identifier we need that.
Gets the user identifier for the specified user. Returns the Name claim value if present otherwise returns null. Gets the user name for the specified user. Returns a list of users from the user store who have the specified claim.
Your problem is that you're using two different UserManager instances, and it looks like they're both statically defined (which is a huge no-no in Web applications, since these are shared between all threads and users of the system and are not thread safe, you can't even make them thread safe by locking around them because they contain state specific to a user)
Change your GetUserDummyTestClass to this:
private static UserManager<ApplicationUser> UserManager
{
get { return new UserManager<ApplicationUser>(
new UserStore<ApplicationUser>(new ApplicationDbContext())); }
}
public static ApplicationUser GetUser(string id)
{
using (var userManager = UserManager)
{
return UserManager.FindById(id);
}
}
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