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
以了解该操作。它可以是 ControllerActionDescriptor
或 PageActionDescriptor
。
我想创建一个全局操作过滤器来审核对我的 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
以了解该操作。它可以是 ControllerActionDescriptor
或 PageActionDescriptor
。