I want to create a global action filter to audit all requests to my API. I want to use a globally registered ActionFilter to ensure all API Controller Actions are audited. I want to inject a scoped instance of AuditService, however, I get a System.ObjectDisposedException when calling _auditService.Create. What is the correct way to inject a scoped service into an ActionFilter so that it doesn't get disposed of before OnActionExecuted is called? The service is also disposed of before the OnActionExecuting event.
Startup code:
public void ConfigureServices(IServiceCollection services)
{
// ..
services.AddControllers(c => c.Filters.Add<AuditFilter>());
services.AddDbContext<MyDbContext>(c => c.UseSqlServer("ConnectionString"));
services.AddScoped<IAuditService, AuditService>();
// ..
}
Action Filter:
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);
}
}
Audit Service:
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;
}
// ..
}
IAuditServiceimplementation is using the scopedDbContextwhen it gets disposed after the request. Use theHttpContext.RequestServicesto create a local scope and perform your function. And there is also theasync voidbeing used.IResourceFilterto ensure that all resource accesses are audited (page handlers will not invokeIActionFilter). Secondly I think this may not show the whole picture, the problem could be somewhere else, or something wrong inside the implementation of yourIAuditService. Generally speaking, this could be caused by wrong caching, wrong way of running background tasks, explicitly disposing the DbContext ... you need to scan through all your project to find it.AuditServiceclass to question. I will look intoIResourceFilter.Disposeimplementation may be the issue here, somehow that's called before theDbContextis being used again. You don't need to explicitly dispose theDbContextlike that. Because it will be managed by the DI container. AndDisposeis usually for unmanaged resources.