0

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.

Drake
  • 483
  • 1
  • 4
  • 12
  • I *think* the problem may have to do with the fact that you don't necessarily get a correlation between threads and requests due to the async nature of web api. I had a similar problem and ended up spending a lot of time with InRequestScope:http://stackoverflow.com/questions/23501363/inject-iowincontext-with-web-api-and-ninject. Whether you use owin or not, the concept about RequestScope is probably still relevant. Not sure this is the answer, hence just a comment. – flytzen Jul 29 '14 at 21:41
  • I was using InRequestScope, but I just changed all of those bindings to use InTransientScope and the same error occurs, so unfortunately I don't think this is immediately related to the scope. I agree it likely has something to do with asynchronous requests, the trick is finding out exactly what is going wrong. Note: none of my api controller actions are ``async`` in the C# keyword sense of the word. – Drake Jul 29 '14 at 21:49

0 Answers0