.Net Identity中UserManager的奇怪行为
本文关键字:Identity UserManager Net | 更新日期: 2023-09-27 18:20:49
为了简化这个问题,我将描述更高级别的问题,然后在需要时讨论任何实现细节。
我在正在开发的应用程序中使用ASP.NET标识。在一系列请求的特定场景中,UserManager首先获取当前用户(至少一个FindById请求),从中提取用户。在随后的请求中,我更新了UserManager保存的有关该用户的信息。更新后,我可以看到数据库中保留的更改。
这里的问题是,在进一步的后续请求中,从FindById获得的用户对象不会更新。这很奇怪,但可能是因为我不理解UserManager中的缓存。然而,当我跟踪数据库调用时,我发现UserManager确实在向数据库发送sql请求以获取用户。
这就是它变得非常奇怪的地方——即使数据库被确认是最新的,UserManager仍然以某种方式从这个过程中返回了一个旧对象。当我自己运行直接跟踪到数据库的完全相同的查询时,我会得到预期的更新数据。
这是什么魔法?
很明显,有些东西被缓存在某个地方,但为什么它对数据库进行查询,只是为了忽略它获得的更新数据?
示例
下面的示例为控制器操作的每个请求按预期更新数据库中的所有内容,当GetUserDummyTestClass在UserManager的另一个实例上调用findById时,我可以跟踪sql请求,并可以将这些请求直接测试到数据库,并验证它们是否返回更新的数据。但是,从同一行代码返回的用户对象仍然具有旧值(在这种情况下,无论调用测试操作多少次,都是应用程序启动后的第一次编辑)。
控制器
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;
}
}
更新
正如Erik所指出的,我不应该使用静态UserManagers。但是,如果我将GetUserDummyTest中的UserManager绑定到HttpContext(按照HttpRequest持久化它),以防我想在请求期间多次使用它,它仍然会缓存它通过Id获得的第一个用户对象,并忽略来自另一个UserManager的任何更新。因此,这表明真正的问题确实是,正如trailmax所建议的那样,我使用了两个不同的UserManager,而且它不是为这种使用而设计的。
在我上面的例子中,如果我在HttpRequest上保持GetUserDummyTestClass中的UserManager持久化,添加一个Update方法,并且只在控制器中使用它,那么一切都会正常工作。
因此,如果要得出结论,如果我想在控制器的范围之外使用UserManager的逻辑,我必须在一个适当的类中全局化UserManager实例,在这个类中我可以将实例绑定到HttpContext,如果我希望避免创建和处理一次性使用的实例,这是否正确?
更新2
进一步调查,我意识到我确实打算每个请求使用一个实例,而且这实际上已经在Startup.Auth中为OwinContext设置了,后来访问如下:
using Microsoft.AspNet.Identity.Owin;
// Controller
HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>()
// Other scopes
HttpContext.Current.GetOwinContext().GetUserManager<ApplicationUserManager>()
事实上,从提供的默认AccountController的设置来看,这是显而易见的,但我想上面描述的相当奇怪和意外的行为证明相当分散注意力。尽管使用OwinContext.GetUserManager.
您的问题是,您使用了两个不同的UserManager实例,而且它们看起来都是静态定义的(这在Web应用程序中是一个巨大的禁忌,因为它们在系统的所有线程和用户之间共享,并且不是线程安全的,您甚至无法通过锁定它们来确保它们的线程安全,因为它们包含特定于用户的状态)
将您的GetUserDummyTestClass更改为:
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);
}
}