"Cannot access a disposed context" 尝试在 Startup ExceptionHandler 中访问 EF Core DbContext 时

"Cannot access a disposed context" when trying to access EF Core DbContext in Startup ExceptionHandler

我有一个 .NET Core 3.0 应用程序。在我的 Startup.cs Configure 方法中,我正在处理意外错误并发送电子邮件通知。效果很好。

public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IEmailService emailService)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler(options =>
        {
            options.Run(
                async context =>
                {
                    var ex = context.Features.Get<IExceptionHandlerFeature>();
                    if (ex != null)
                    {
                        await emailService.SendErrorEmailAsync(context, ex);
                        context.Response.Redirect($"/Error/{context.Response.StatusCode}");
                    }
                });
        }
        );
        app.UseStatusCodePagesWithReExecute("/Error/{0}");
        app.UseHsts();
    }

    //...
}

我现在想通过包含一些有关用户的信息来改进它。为此,我尝试了以下操作:

private readonly MyApplicationContext _dbContext;
private readonly IServiceProvider _services;

public EmailService(MyApplicationContext dbContext, IServiceProvider services) {
    _dbContext = dbContext;
    _services = services;
}

public async Task SendErrorEmailAsync(HttpContext context, Exception ex)
{
    var _userService = _services.GetRequiredService<IUserService>();
    var user = await _userService.GetCurrentUserAsync();

    //...build email, send, etc
}

var _userService = _services.GetRequiredService<IUserService>(); 抛出以下内容:

Cannot access a disposed object. Object name: 'IServiceProvider'.

作为解决方法,我想我可以尝试直接调用数据库而不是通过 IUserService:

public async Task SendErrorEmailAsync(HttpContext context, Exception ex)
{
    User user = null;
    var isLoggedIn = context.User.Identity.IsAuthenticated;
    if (isLoggedIn)
    {
        var idString = context.User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value;
        if (idString != null)
        {
            var userId = Guid.Parse(idString);
            user = await _dbContext.Users.Include(u => u.Client).FirstOrDefaultAsync(u => u.Id == userId );
        }
    }

    //...build email, send email, etc
}

user = await _dbContext ... 抛出:

Cannot access a disposed context instance. A common cause of this error is disposing a context instance that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling 'Dispose' on the context instance, or wrapping it in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.

在研究这个问题时,最常见的原因是返回 async void 或忘记使用 awaitStartup.cs 中的 Configure 方法显然是 void,但我不确定如果这确实是问题所在,可能有什么解决方法。

感谢任何意见。谢谢大家

这更像是一个设计问题,因为在解析和注入所需的依赖项时,它们将使用启动的服务提供者而不是请求的服务提供者。

我首先建议通过委托中当前请求的上下文解析服务

public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {
    if (env.IsDevelopment()) {
        app.UseDeveloperExceptionPage();
    } else {
        app.UseExceptionHandler(options => {
            options.Run(
                async context => {
                    var ex = context.Features.Get<IExceptionHandlerFeature>();
                    if (ex != null) {
                        IEmailService emailService = context.RequestServices.GetRequiredService<IEmailService>();
                        await emailService.SendErrorEmailAsync(context, ex);
                        context.Response.Redirect($"/Error/{context.Response.StatusCode}");
                    }
                });
        });
        app.UseStatusCodePagesWithReExecute("/Error/{0}");
        app.UseHsts();
    }

    //...
}

同时重构服务以避免传递服务提供者。

private readonly MyApplicationContext dbContext;
private readonly IUserService userService;

public EmailService(MyApplicationContext dbContext, IUserService userService) {
    this.userService = userService;
    this.dbContext = dbContext;
}

public async Task SendErrorEmailAsync(HttpContext context, Exception ex) {
    var user = await userService.GetCurrentUserAsync();
    
    //...build email, send, etc
}