UserManager 在 .Net Identity 中的奇怪行为

Posted

技术标签:

【中文标题】UserManager 在 .Net Identity 中的奇怪行为【英文标题】:Odd behavior by UserManager in .Net Identity 【发布时间】:2014-10-31 08:31:52 【问题描述】:

为了使这个问题简单,我将描述更高级别的问题,然后在需要时进入任何实现细节。

我在正在开发的应用程序中使用 ASP.NET 标识。在一系列请求的特定场景下,UserManager首先获取当前用户(至少一个FindById请求),用户是从哪里获取的。在随后的请求中,我更新了由 U​​serManager.Update 保存的有关此用户的信息,并且我可以看到数据库中保留的更改。

问题在于,在后续请求中,从 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 指出的,我不应该使用静态 UserManager。但是,如果我将 GetUserDummyTest 中的 UserManager 保持在绑定到 HttpContext(每个 HttpRequest 持久化)以防我想在请求期间多次使用它,它仍然会缓存它通过 Id 获得的第一个 User 对象,而忽略任何更新来自另一个用户管理器。因此表明真正的问题确实是我使用了两个不同的 UserManager,正如 trailmax 所建议的那样,而且它不是为这种用途而设计的。

在上面的示例中,如果我将 GetUserDummyTestClass 中的 UserManager 保持在 HttpRequest 上,添加一个更新方法并仅在控制器中使用它,一切都会按预期正常工作。

因此,如果要得出结论,是否正确声明如果我想在控制器范围之外使用来自 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 不再是问题。

【问题讨论】:

我知道在发布一个新问题后应该多待一会儿,但不幸的是我的时区迫使我暂时离开。如果有人有问题,请耐心等待。 您能否发布一个重现您的问题的示例代码? 我怀疑因为你得到了 2 个不同的ApplicationDbContext,你得到一个缓存的对象,另一个直接从数据库中获取。我稍后再确认——刚刚从办公室逃跑了。 【参考方案1】:

您的问题是您正在使用两个不同的 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);
    

【讨论】:

感谢您关于它是静态的提示,我不知道它的状态是特定于某个用户的。但至少我应该注意到它包含一个 dbcontext 的实例,并且这绝对不应该是静态的。但是出于好奇,这将如何解释 GetUserDummyTestClass 中的 UserManager 对数据库进行查询只是为了忽略它获得的更新数据?还是只是未指定使用静态实例时的行为(并且肯定是不相关的)? 再次更新问题。如果我想通过 HttpRequest(在 HttpContext 中)持久化 UserManager,我只需要在执行流程中使用一个实例? 这仍然是创建 UserManager 新实例的正确方法吗?

以上是关于UserManager 在 .Net Identity 中的奇怪行为的主要内容,如果未能解决你的问题,请参考以下文章

Mediatr 无法解析 ASP.Net Core 中的 UserManager

ASP.NET Core Identity 3.1:UserManager.RemoveFromRoleAsync 总是返回 UserNotInRole

ASP.NET 核心标识。使用 ApplicationDbContext 和 UserManager。他们是不是共享上下文?

如何在 C# 页面 ASP.NET Core MVC 上使用 SignInManager 和 Usermanager

ASP.NET Core 标识不注入 UserManager<ApplicationUser>

如何将 UserManager 注入 ASP.NET Core 2.0 中的另一个服务