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

时间:2016-05-18 06:38:00

标签: c# asp.net-core asp.net-core-mvc

执行 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()

我简化了代码,以便更容易阅读。我在控制器生命周期中两次调用 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);
    }

稍后编辑: 这是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;
        }
    }

3 个答案:

答案 0 :(得分:3)

using构造是围绕

的语法糖
DbContext context = null;
try 
{
    context = new DbContext();
    ...stuff inside the using block ...
}
finally 
{
    if(context!=null)
        context.Dispose()
}

与调用

相同
using(DbContext context = new DbContext()) 
{
    ...stuff inside the using block ...
}

块。这样可以确保尽快处理对象,即使发生异常(最后总是调用块)。

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

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

scoped生命周期是推荐的生命周期,因为DbContext在生存时间很长时会占用大量内存,因为DbContext会跟踪所有记录的更改,直到您将其丢弃为止。

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

如果您真的需要瞬态解决方案,您可以创建一个工厂方法并将其注入您的服务,例如:

services.AddTransient<Func<MyDbContext>>( (provider) => new Func<MyDbContext>( () => new MyDbContext()));

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

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);
        }
    }
}

这不会导致问题,但比必要的更复杂。如果你需要交易,这可能不会很好,因为交易是每个DbContext实例,而身份将始终使用范围的

答案 1 :(得分:0)

似乎userManager.ResetPasswordAsync()方法使用了用户变量的一些延迟加载属性。由于用户变量不在数据库查询范围内,因此无法访问该属性。

答案 2 :(得分:0)

我使用内置的userManager查询替换了我的自定义用户查询,这些查询执行相同的操作并且现在可以正常工作:

ForgotPass 功能中:

var user = await userManager.FindByEmailAsync(model.UserName);

ResetPass 功能中:

var user = await userManager.FindByIdAsync(model.UserId);

一旦我确切地知道为什么我的初始方法无效,我就会更新答案。