ASP.Net custom attributes share a DbContext and cause thread hell
Or, “How come I get errors about ‘A second operation’ starting but I’m using await right with no lazy loading?!”
ASP.Net WebAPI attributes are cached by the framework, making them singletons.
It’s very popular in .Net world to create a custom attribute for authentication. In our case, the auth attribute used an ISecurityService
to authenticate some request headers from the client against the database. Despite the service being injected by Ninject, all instances of the attribute logic were using the same service, causing us a gamut of errors like:
- There is already an open DataReader associated with this Command which must be closed first.
- The context cannot be used while the model is being created.
- A second operation started on this context before a previous asynchronous operation completed. Use ‘await’ to ensure that any asynchronous operations have completed before calling another method on this context.
- The changes to the database were committed successfully, but an error occurred while updating the object context. The ObjectContext might be in an inconsistent state.
Please note that if you’re using synchronous calls to the database (no tasks, await, or async) then you’ll probably see errors (1) and/or (2). If you’re using async, you’re likely to see errors (3) and/or (4).
Example setup
Say you have some set-up code to register your attribute in WebApiConfig.cs
like this:
public static void Register(HttpConfiguration config)
{
// ...
var securityService = WebApiApplication.LocalKernel.Get<ISecurityService>();
config.Filters.Add(new MyAuthFilter(securityService));
}
You’ve bound the service nicely in some __Module.cs
somewhere, using Ninject’s default of transient scope:
Bind<ISecurityService>().To<SecurityService>();
…and all of the methods in the service correctly make use of async
and await
, that is, you’re not using ConfigureAwait(false)
or omitting the await
operator. Or you’re not using async at all.
Lastly, in your underlying SecurityContext
which inherits from DbContext
, you’ve even turned off LazyLoading and ProxyCreation because you’ve heard that they don’t work very well with ASP.Net
Looks like you did everything right. But WebAPI is here to foil your best-laid plans. Cached attributes - a hidden implementation detail - mean that the same DbContext will be reused by multiple threads in a non-thread safe way, leading to the errors listed above, as explained by pfx in this stack overflow answer.
Solution
Instead of loading one ISecurityService
at application start time and passing it to your attribute, you need to give attributes some way of getting a fresh ISecurityService
any time they run their ExecuteActionFilterAsync
method - a factory.
You have to change the signature for the constructor of your filter:
// Before:
public MyAuthFilter(ISecurityService securityService)
// After:
public MyAuthFilter(Func<ISecurityService> securityServiceFactoryDelegate)
You can make the necessary factory in one line of code, where you used to grab an injectable service:
public static void Register(HttpConfiguration config)
{
// ...
// Create a closure that produces a fresh ISecurityService anytime
Func<ISecurityService> securityServiceFactoryDelegate = () => WebApiApplication.LocalKernel.Get<ISecurityService>();
// Pass it to the MyAuthFilter constructor
config.Filters.Add(new MyAuthFilter(securityServiceFactoryDelegate));
}
Within the ExecuteActionFilterAsync
, do a simple securityService = securityServiceFactoryDelegate()
to get a service instance. After this change, each execution of the filter will get its own service, with it’s own DbContext
, meaning they will be thread-safe. Yay!
I hope this helps you. I couldn’t have fixed it without the answer from pfx linked earlier, so big thanks to him.