【问题标题】:ASP.NET Core ActionFilter Dependency Injection ObjectDisposedExceptionASP.NET Core ActionFilter 依赖注入 ObjectDisposedException
【发布时间】:2021-03-28 22:34:33
【问题描述】:

我想创建一个全局操作过滤器来审核对我的 API 的所有请求。我想使用全局注册的ActionFilter 来确保所有 API 控制器操作都经过审核。我想注入AuditService 的作用域实例,但是在调用_auditService.Create 时得到System.ObjectDisposedException。将作用域服务注入ActionFilter 以使其在调用OnActionExecuted 之前不会被处理的正确方法是什么?该服务也在OnActionExecuting 事件之前被销毁。

启动代码:

public void ConfigureServices(IServiceCollection services)
{
    // ..
    services.AddControllers(c => c.Filters.Add<AuditFilter>());
    services.AddDbContext<MyDbContext>(c => c.UseSqlServer("ConnectionString"));
    services.AddScoped<IAuditService, AuditService>();
    // ..
}

动作过滤器:

public class AuditFilter : IActionFilter
{
    private readonly IAuditService _auditService;

    public AuditFilter(IAuditService auditService)
    {
        _auditService = auditService;
    }

    public void OnActionExecuting(ActionExecutingContext context)
    {
        
    }

    public async void OnActionExecuted(ActionExecutedContext context)
    {
        string username = ClaimsPrincipal.Current?.Identity?.Name;
        string remoteAddr = $"{context.HttpContext.Connection.RemoteIpAddress}:{context.HttpContext.Connection.RemotePort}";
        string queryString = context.HttpContext.Request.QueryString.HasValue ? context.HttpContext.Request.QueryString.Value : null;
        using StreamReader reader = new StreamReader(context.HttpContext.Request.Body);
        string body = await reader.ReadToEndAsync();
        body = body.Length > 0 ? body : null;
        // System.ObjectDisposedException: 'Cannot access a disposed context instance
        await _auditService.Create(username, remoteAddr, context.HttpContext.Request.Method,
            context.HttpContext.Request.Path, queryString, body, DateTime.Now);
    }
}

审计服务:

public class AuditService : IAuditService
{
    private DRSContext Context { get; }

    public AuditService(DRSContext context)
    {
        Context = context;
    }

    public async Task<bool> Create(string username, string remoteAddr, string httpMethod, string path, string query,
        string body, DateTime timestamp)
    {
        await Context.AuditLogs.AddAsync(new AuditLog
        {
            Username = username,
            RemoteAddress = remoteAddr,
            HttpMethod = httpMethod,
            Path = path,
            Query = query,
            Body = body,
            Timestamp = timestamp
        });
        return await Context.SaveChangesAsync() > 0;
    }

    // ..
}

【问题讨论】:

  • 注入的IAuditService 实现很可能在请求后被释放时使用了作用域DbContext。使用 HttpContext.RequestServices 创建本地范围并执行您的功能。还有async void被使用。
  • 首先您应该使用IResourceFilter 来确保所有资源访问都被审计(页面处理程序将调用IActionFilter)。其次,我认为这可能无法显示全貌,问题可能出在其他地方,或者您的IAuditService 的实现内部有问题。一般来说,这可能是由错误的缓存、错误的后台任务运行方式、显式处理 DbContext 造成的……您需要扫描所有项目才能找到它。
  • @KingKing 我已将我的AuditService 课程添加到问题中。我会调查IResourceFilter
  • 您的Dispose 实现可能是这里的问题,不知何故,在再次使用DbContext 之前调用了它。您不需要像那样显式处置DbContext。因为它将由 DI 容器管理。而Dispose 通常用于非托管资源。
  • 好的,我已经在我的回答中总结了这里评论的所有想法(正如@Nkosi 所指出的那样)。

标签: c# asp.net-core asp.net-web-api dependency-injection


【解决方案1】:

这里的代码有一些问题:

  • 您在OnActionExecuted 上使用async void。应该避免这种情况,因为您可能会遇到不可预测的结果。如果您想使用async 代码,请尝试实现IAsyncActionFilter
  • 您为IAuditService 的实现类实现Dispose,您在其中显式处置DbContext。您不需要手动执行此操作,这可能与 DI 为您管理 DbContext(作为范围服务)的方式不同步。通常Dispose 中的代码用于处理非托管资源。

最后我建议你改用IAsyncResourceFilter。它将由控制器操作和页面处理程序调用,而 IAsyncActionFilter 将仅由控制器操作执行。您可以检查ResourceExecutingContext.ActionDescriptor 以了解该操作。它可以是ControllerActionDescriptorPageActionDescriptor

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-01-16
    • 1970-01-01
    • 2019-02-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多