首页 > 解决方案 > 尝试在 Startup ExceptionHandler 中访问 EF Core DbContext 时“无法访问已释放的上下文”

问题描述

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

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>(); 抛出以下内容:

无法访问已处置的对象。对象名称:“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 ...抛出:

无法访问已释放的上下文实例。此错误的一个常见原因是释放从依赖注入中解析的上下文实例,然后尝试在应用程序的其他地方使用相同的上下文实例。如果您在上下文实例上调用“Dispose”或将其包装在 using 语句中,则可能会发生这种情况。如果你使用依赖注入,你应该让依赖注入容器负责处理上下文实例。

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

我很感激任何意见。谢谢大家。

标签: c#entity-framework.net-coredependency-injectionentity-framework-core

解决方案


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

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

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
}

推荐阅读