代码之家  ›  专栏  ›  技术社区  ›  Alex

UserManager在.Net Identity中的奇怪行为

  •  15
  • Alex  · 技术社区  · 11 年前

    为了保持这个问题的简单性,我将描述更高级别的问题,然后在需要时详细介绍实现细节。

    我在开发中的应用程序中使用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);
    }
    

    测试.cshtml

    @model string
    
    @{
        var user = GetUserDummyTestClass.GetUser(Model);
    }
    
    @user.RealName;
    

    获取用户DummyTestClass

    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获取的第一个User对象,并忽略来自另一个UserManager的任何更新。因此,这意味着真正的问题是,正如trailmax所建议的那样,我使用了两个不同的UserManagers,而且它不是为这种用途而设计的。

    在上面的示例中,如果我在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不再是问题,但了解这种行为的原因还是很有意思的。

    1 回复  |  直到 11 年前
        1
  •  10
  •   Erik Funkenbusch    11 年前

    您的问题是,您使用的是两个不同的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);
        }
    }
    
    推荐文章