UserManager在.Net Identity中的奇怪行为

时间:2014-09-06 15:46:06

标签: c# asp.net entity-framework asp.net-identity

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

我在开发中的应用程序中使用ASP.NET Identity。在一系列请求的特定场景中,UserManager首先获取当前用户(至少一个FindById请求),其中获取用户。在随后的请求中,我更新了UserManager.Update保存的关于此用户的信息,我可以看到数据库中的更改仍然存在。

问题在于,在进一步的后续请求中,从FindById获取的用户对象不会更新。这很奇怪,但可能是UserManager中的缓存,我不明白。但是,当我跟踪数据库调用时,我看到UserManager确实正在将sql-requests发送到数据库以获取用户。

这就是它变得非常奇怪的地方 - 即使确认数据库是最新的,UserManager仍然以某种方式返回此过程中的旧对象。当我自己运行与数据库直接跟踪的完全相同的查询时,我会按预期获得更新的数据。

这是什么黑魔法?

显然,某些内容会被缓存,但为什么它会对数据库进行查询,只是忽略它获得的更新数据?

示例

下面的示例更新了db中对控制器操作的每个请求的所有内容,当GetUserDummyTestClass在UserManager的另一个实例上调用findById时,我可以跟踪sql请求,并可以直接测试这些请求并验证他们返回更新的数据。但是,从同一行代码返回的用户对象仍具有旧值(在此方案中,应用程序启动后的第一次编辑,无论调用Test操作的时间长短。)

控制器

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

在上面的例子中,如果我将GetManager的GetManager继续保存在GetUserDummyTestClass中的UserManager,添加一个Update-method并且只在控制器中使用它,一切正常。

因此,如果得出结论,说明如果我想在控制器范围之外的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 个答案:

答案 0 :(得分:10)

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