ASP.NET 核心 ActionFilter 依赖注入 ObjectDisposedException

ASP.NET Core ActionFilter Dependency Injection ObjectDisposedException

我想创建一个全局操作过滤器来审核对我的 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;
    }

    // ..
}

您的代码中存在一些错误:

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

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