I am creating a REST API using ASP.NET, and I am using Ninject as the dependency injector for my Web API filters. Unfortunately, I appear to be experiencing a race condition when using the Inject() method of the IKernel interface.
Here is a reduced version of one of my filters which performs HTTP basic authentication.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true)]
public class BasicAuthenticationAttribute : AuthorizationFilterAttribute
{
private IAccountService accountService;
[Inject]
public IAccountService AccountService
{
get { return accountService; }
set
{
Debug.WriteLine("set_AccountService called in thread {0}", Thread.CurrentThread.ManagedThreadId);
accountService = value;
}
}
public override void OnAuthorization(...)
{
// Use the injected IAccountService here...
}
}
And here is my IFilterProvider implementation that uses Ninject to initialize my filters.
public class NinjectFilterProvider : ActionDescriptorFilterProvider, IFilterProvider
{
private readonly IKernel kernel;
public NinjectFilterProvider(IKernel kernel)
{
if (kernel == null) throw new ArgumentNullException("kernel");
this.kernel = kernel;
}
public new IEnumerable<FilterInfo> GetFilters(HttpConfiguration configuration, HttpActionDescriptor actionDescriptor)
{
var filters = base.GetFilters(configuration, actionDescriptor);
foreach (var filter in filters)
{
// For demonstration purposes, I removed all but one of my filters.
var inst = (BasicAuthenticationAttribute)filter.Instance;
Debug.WriteLine("kernel.Inject() will be called in thread {0}", Thread.CurrentThread.ManagedThreadId);
kernel.Inject(inst);
if (inst.AccountService == null)
{
// This Sleep() call demonstrates the problem.
Thread.Sleep(500);
Debug.WriteLine("Before wait inst.AccountService == null: true, afterword inst.AccountService == null: {0} on thread {1}", inst.AccountService == null, Thread.CurrentThread.ManagedThreadId);
}
Debug.WriteLine("inst.AccountService == null: {0} on thread {1}", inst.AccountService == null, Thread.CurrentThread.ManagedThreadId);
Debug.WriteLine("kernel.Inject() was called in thread {0}", Thread.CurrentThread.ManagedThreadId);
}
return filters;
}
}
And finally here is the debug output generated when I make a request to one of my API controller endpoints decorated with [BasicAuthentication]
. This request is made from an iPhone app I'm developing alongside my REST API, and actually three requests are made at once from the table view controller that I experience this error on.
kernel.Inject() will be called in thread 7
kernel.Inject() will be called in thread 8
kernel.Inject() will be called in thread 6
set_AccountService called in thread 7
set_AccountService called in thread 8
inst.AccountService == null: False on thread 7
inst.AccountService == null: False on thread 8
kernel.Inject() was called in thread 7
kernel.Inject() was called in thread 8
Before wait inst.AccountService == null: true, afterword inst.AccountService == null: False on thread 6
inst.AccountService == null: False on thread 6
kernel.Inject() was called in thread 6
You can see that immediately after the call to kernel.Inject() on thread 6, inst.AccountService is still null, and it is only after sleeping the current thread for half a second that it is assigned. This means that Ninject must be resolving and assigning the dependencies on a different thread. Is there a way to force Ninject to do all that on the same thread that the kernel.Inject() call is made on? Race conditions are not welcome in my code.
EDIT:
Replacing the line kernel.Inject(inst);
with inst.AccountService = kernel.Get<IAccountService>();
fixes the problem, so the odd behavior of setting the dependency on a different thread definitely originates from the Inject() call specifically. If there's no way to configure Ninject in the way I want, my options appear to be rolling my own reflection-based Inject substitute or using a different DI library.