UserManager.ResetPasswordAsync 上出现“无法访问已处置对象”错误

Too*_*nia 5 c# asp.net-core-mvc asp.net-core

执行userManager.ResetPasswordAsync时出现以下错误:

An unhandled exception occurred while processing the request.

ObjectDisposedException: Cannot access a disposed object.
Object name: 'TestDb'.
Microsoft.Data.Entity.DbContext.get_ServiceProvider()
Run Code Online (Sandbox Code Playgroud)

我简化了代码,使其更易于阅读。我在控制器生命周期中调用userManager两次。一次用于生成令牌,一次用于重置密码:

    private readonly UserManager<ApplicationUser> userManager;

    // controller's constructor
    public AuthController(UserManager<ApplicationUser> userManager) {
        this.userManager = userManager;
    }

    [AllowAnonymous, HttpPost]
    public async Task<ActionResult> ForgotPass(ForgotPassViewModel model) {
        //model checks

        var user = new UserQuery(db).GetUserByUserName(model.UserName);

        //check if user exists

        var token = await userManager.GeneratePasswordResetTokenAsync(user);
        var url = $"{config.Url}/auth/resetpass?user={user.Id}&token={WebUtility.UrlEncode(token)}";

        // send email with the reset url

        model.Success = "An email has been sent to your email address";
        return View(model);
    }

    [AllowAnonymous, HttpPost]
    public async Task<ActionResult> ResetPass(ResetPassViewModel model) {
        //model checks

        var user = new UserQuery(db).GetUserById(model.UserId);

        //error occurs here:
        var result = await userManager.ResetPasswordAsync(user, model.Token, model.Password);

        //check result

        model.Success = "Password successfully reset";
        return View(model);
    }
Run Code Online (Sandbox Code Playgroud)

稍后编辑: 这是来自 UserQuery 的函数(按照下面评论中的要求)。我确实正在使用“using”包装器:

    public ApplicationUser GetUserByUserName(string userName) {
        using (var db = this.dbContext) {
            var user = (from u in db.Users
                        where u.UserName == userName
                        select u).SingleOrDefault();
            return user;
        }
    }
Run Code Online (Sandbox Code Playgroud)

Tse*_*eng 3

using结构是围绕一个语法糖

DbContext context = null;
try 
{
    context = new DbContext();
    ...stuff inside the using block ...
}
finally 
{
    if(context!=null)
        context.Dispose()
}
Run Code Online (Sandbox Code Playgroud)

和打电话是一样的

using(DbContext context = new DbContext()) 
{
    ...stuff inside the using block ...
}
Run Code Online (Sandbox Code Playgroud)

堵塞。这可以确保即使发生异常(始终调用finally块),也会尽快处理该对象。

ASP.NET Core DbContext(特别是其 ASP.NET Core Identity 注册)中的 ASP.NET Core Identity 注册为具有作用域生命周期,这意味着在一个请求的持续时间内将返回相同的引用。

但是,当您在请求结束之前过早地处理它(无论是使用using块还是通过自己调用方法)时,当另一个方法尝试访问它时它就会爆炸。.Dispose()

建议使用作用域生命周期,因为 DbContext 在生命周期很长时会使用大量内存,因为 DbContext 会跟踪所有记录的更改,直到您将其释放为止。

因此,在没有依赖项注入或简单教程的传统应用程序中,您可以使用它来创建它new并尽快处理它。但在 Web 应用程序中,请求的生命周期非常短暂,并且在大多数情况下,范围内的生命周期都会保留句柄。在某些极端情况下,瞬态(AddTransientASP.NET Core IoC 容器中的方法)生命周期可能更好。

如果您确实需要瞬态解析,您可以创建一个工厂方法并将其注入到您的服务中,例如:

services.AddTransient<Func<MyDbContext>>( (provider) => new Func<MyDbContext>( () => new MyDbContext()));
Run Code Online (Sandbox Code Playgroud)

并将其注入您的服务/控制器中:

public class MyService 
{
    public readonly Func<MyDbContext> createMyContext;

    public MyService(Func<MyDbContext> contextFactory)
    {
        this.createContext = contextFactory;
    }

    public User GetUserById(Guid userId) 
    {
        // note we're calling the delegate here which 
        // creates a new instance every time
        using(var context = createContext()) 
        {
            return context.User.FirstOrDefault(u => u.Id = userId);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这不会导致问题,但会比必要的更复杂。如果您需要事务,这可能无法很好地发挥作用,因为事务是针对每个 DbContext 实例的,而 Identity 将始终使用范围内的事务