Question

When trying out the LightInject IoC container http://www.lightinject.net/ it throws a stackoverflow exception when resolving the type ISomeService:

All types are registered in App_Start:

container.RegisterAssembly("MyApp*.dll");

And then when I try to resolve it in the controller it fails and throws a stackoverflow exception:

    public SomeController(ISomeService someService)
    {
         _someService = someService;
    }

It also has the same error when using ServiceLocator:

ServiceLocator.Current.GetInstance<ISomeService>();

I have traced it through and I can see that it is failing in the LightInject ServiceContainer class right here, but I don't see why it is failing yet.

public object GetInstance(Type serviceType)
{
    return GetDefaultDelegate(serviceType, true)(constants.Items);
}

After calling GetDefaultDelegate, the execution path ends back in GetInstance again, causing an infinite loop and a stack overflow.

EDIT 2 - Have tracked this down further and it appears to be caused by SomeService having both constructor and property injection at the same time:

public class SomeService : ISomeService 
{
    public IAnotherService AnotherService { get; set; }
    public SomeService(IAnotherService anotherService)
    {
        AnotherService = anotherService;
    }
}

The dependency IAnotherService AnotherService is being injected via the constructor and the property, but there was no intention to use property injectors.

EDIT 3

There are several layers in the app that all use ServiceLocator, so I initially added LI to its own project using NuGet, so I had:

MyApp.IoC.LightInject
MyApp.Repositories
MyApp.Services
MyApp.Web.UI

But it isn't actually necessary to add it to its own project because the service locator provider can be set in the UI layer:

var serviceLocator = new LightInjectServiceLocator(container);
ServiceLocator.SetLocatorProvider(() => serviceLocator);

So I have just now removed the extra project and put the NuGet packages into the UI layer instead. I've also installed LightInject.Annotation and deliberately not called the container.EnableAnnotatedPropertyInjection() method, to ensure that only constructor injection is used. It is still throwing a stackoverflow.

To test if the resolving is working I am just doing this in my HomeController.Index() method:

public ActionResult Index()
{
    var a = ServiceLocator.Current.GetInstance<ISomeService>();
}

I added some console logging to the ServiceController.GetInstance methods so that I could see what the flow of method calls was that was leading to the stackoverflow. This is the log, and the results are a bit unexpected. You can see that when it calls CreateDelegate() for ISomeService that it ends up trying to get an instance of HomeController first - why is that?

DoGetInstance: ISomeService. Key = ''
GetInstance: ISomeService
TryGetValue: ISomeService not found
TryAddValue: trying to add ISomeService now
CreateDynamicMethodDelegate: ISomeService
CreateDynamicMethodDelegate: calling CreateDelegate() for ISomeService
DoGetInstance: HomeController. Key = ''
GetInstance: HomeController
TryGetValue: HomeController not found
TryAddValue: trying to add HomeController now
CreateDynamicMethodDelegate: HomeController
CreateDynamicMethodDelegate: calling CreateDelegate() for HomeController
DoGetInstance: ISomeService. Key = ''
GetInstance: ISomeService
TryGetValue: ISomeService not found
TryAddValue: trying to add ISomeService now
CreateDynamicMethodDelegate: ISomeService
CreateDynamicMethodDelegate: calling CreateDelegate() for ISomeService
DoGetInstance: HomeController. Key = ''
GetInstance: HomeController
TryGetValue: HomeController not found
TryAddValue: trying to add HomeController now
CreateDynamicMethodDelegate: HomeController
CreateDynamicMethodDelegate: calling CreateDelegate() for HomeController
DoGetInstance: ISomeService. Key = ''
GetInstance: ISomeService
TryGetValue: ISomeService not found
TryAddValue: trying to add ISomeService now
CreateDynamicMethodDelegate: ISomeService
CreateDynamicMethodDelegate: calling CreateDelegate() for ISomeService
DoGetInstance: HomeController. Key = ''
GetInstance: HomeController
TryGetValue: HomeController not found
TryAddValue: trying to add HomeController now
CreateDynamicMethodDelegate: HomeController
CreateDynamicMethodDelegate: calling CreateDelegate() for HomeController
DoGetInstance: ISomeService. Key = ''
GetInstance: ISomeService
TryGetValue: ISomeService not found
TryAddValue: trying to add ISomeService now
CreateDynamicMethodDelegate: ISomeService
CreateDynamicMethodDelegate: calling CreateDelegate() for ISomeService

When I comment out the service's constructor, the resolving works and all dependencies are resolved by property injection. If the constructor is included then it throws the stackoverflow exception.

And although the resolving ISomeService works when using properties only, I cannot resolve IAnotherService if that service includes ISomeService in its dependencies. I hope the log above will shed some light on the problem. The performance of LightInject so far has been significantly better than Unity

Was it helpful?

Solution

Being the author of LightInject I will try to answer the best I can.

First of all, LightInject should never throw a StackOverflowException, so that is something that needs to be looked into based on what you describe here.

And I clearly understand that property injection was not your intention here as it would make little sense to inject the service twice (Constructor and Property).

There are actually several things we can do to avoid the dependency to be injected into the property.

  1. Make the property read-only by removing the public setter.
  2. Register the service by using what the documentation refers to as "explicit" service registration.

    container.Register<ISomeService>(f => new SomeService(f.GetInstance<IAnotherService>())); 
    

    This will cause the public properties to be ignored because we have now been explicit about how to resolve the dependencies of the SomeService class.

  3. Make use of LightInject.Annotation

    This will make sure that only properties marked with the InjectAttribute will have its dependencies injected. In your specific case this would mean to just leave the property as is without the attribute. At the composition root, we can enable this by making this simple configuration.

    container.EnableAnnotatedPropertyInjection(); 
    

I will as said before investigate further into the StackOverflowException and make proper adjustments for this case. My first thought would be to throw an exception if the container finds a dependency that is eligible for injection both as a constructor dependency and a property dependency.

Hope this helps.

Best regards

Bernhard Richter

EDIT

Although the problem seem to be solved for your case, I would still like to be able to reproduce this in a test. The reason that you get an exception is probably not as simple as the service having both a constructor dependency and a property dependency of the same type.

Consider this service

public class FooWithConstructorAndPropertyDependency : IFoo
{
    public FooWithConstructorAndPropertyDependency(IBar bar)
    {
        Bar = bar;
    }

    public IBar Bar { get; set; }
}

LightInject will happily inject the dependency.

container.Register<IBar, Bar>();
container.Register<IFoo, FooWithConstructorAndPropertyDependency>();

container.GetInstance<IFoo>();

So there must be something else that is causing the exception. I would appreciate if you could come up with the smallest example that reproduces the exception so that this can be dealt with. It is a little hard for me do further investigations without something that fails:) If you choose to make this effort you can post it here or mail me at bernhard.richter@gmail.com.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top