How to inject DbContext in an Action Filter

0

I have an action filter which logs the request in a database.

I use the usual construction injector method.

public ServerLogFilterAttribute(OrderEntryContext context)
{
  this.context = context;
}

public override void OnActionExecuting(ActionExecutingContext context)
{
    var descriptor = context.ActionDescriptor;
    if (descriptor != null && descriptor.RouteValues.Count > 0)
    {
        var actionName = descriptor.RouteValues["action"];
        var controllerName = descriptor.RouteValues["controller"];

        if (context.HttpContext.User != null && context.HttpContext.User.Identity != null)
        {
            var claimsIdentity = context.HttpContext.User.Identity as ClaimsIdentity;

            if (claimsIdentity != null && claimsIdentity.IsAuthenticated)
            {
                var username = claimsIdentity.FindFirst(ClaimTypes.NameIdentifier).Value;
                var userId = claimsIdentity.Claims.First(c => c.Type == "userId").Value;

                var serverLog = new ServerLog()
                {
                    AspNetUserId = userId,
                    Controller = controllerName,
                    Action = actionName,
                    TimeStamp = DateTime.Now
                };

                LoggerRepository loggerRepository = new LoggerRepository(this.context);
                Task.Run(() => loggerRepository.InsertServerCallLog(serverLog));
            }
        }
    }

    base.OnActionExecuting(context);

}

But this throws an exception during SaveChanges():

System.ObjectDisposedException
  HResult=0x80131622
  Message=Cannot access a disposed object. A common cause of this error is disposing a context that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling Dispose() on the context, or wrapping the context in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.
  Source=Microsoft.EntityFrameworkCore
  StackTrace:
   at Microsoft.EntityFrameworkCore.DbContext.CheckDisposed()
   at Microsoft.EntityFrameworkCore.DbContext.get_InternalServiceProvider()
   at Microsoft.EntityFrameworkCore.DbContext.get_DbContextDependencies()
   at Microsoft.EntityFrameworkCore.DbContext.<SaveChangesAsync>d__48.MoveNext()
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at GGL.OrderEntry.Data.OrderEntryContext.<SaveChangesAsync>d__251.MoveNext() in C:\Dev\GGL\GGL.OrderEntry\GGL.OrderEntry.Data\Context\OrderEntryContext.Partial.cs:line 306

As far as I have been able to figure, the issue is that the lifetime of the Filter is longer than the lifetime of the context (which was set to "Transient)

options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")), ServiceLifetime.Transient);

How then should I be injecting the DbContext?

c#
asp.net-core
entity-framework-core
asked on Stack Overflow Aug 30, 2018 by Greg Gum • edited Aug 30, 2018 by Camilo Terevinto

1 Answer

1

You're using Task.Run(() => loggerRepository.InsertServerCallLog(serverLog)); which assigns the context to variable loggerRepository, and telling it to run on the thread pool, thus letting the method continue execution. More than likely the method exits before the thread pool finishes execution and the object has been disposed, just let the call to InsertServerCallLog run on the main thread so that execution does not continue until it has finished that method

answered on Stack Overflow Aug 30, 2018 by Ryan Wilson • edited Aug 30, 2018 by Camilo Terevinto

User contributions licensed under CC BY-SA 3.0