LightInject
LightInject copied to clipboard
GetInstance with constructor argument fails to resolve the dependency
I am trying to pass an extra argument when getting instance from the container.
When class with specific argument is registered in the container, instance with this argument is returned (TestRegisteredArgument).
When class itself without argument is registered in the container, GetInstance crashes due to unresolved dependency.
In my opinion as the original class was registered in the container already and we are explicitly calling the GetInstance with correct additional argument type, LightInject should pick the best matching constructor as it does without argument and set the argument to explicitly given instance - without registering.
Reasoning behind it is that in simple cases default behaviour would not add registration maintenance burden on developer, especially if injected types are local to the instance and not accessible where registrations are implemented.
GetInstance could throw an error if additional argument is ubiquitous in the constructor (same type many times) or if given the argument there are still some unresolved dependencies.
Also if explicit argument is already registered in container - explicit argument should be used instead of registered instance as it can be assumed as intent of a developer using explicit argument.
All above can be wrong if there is an option already to allow explicit arguments take precedence over registrations but I could not find one.
Please find the unit test code below, hopefully it won't be a big thing to implement.
using LightInject;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace LightInjectConstructorParameterTest
{
[TestClass]
public class ConstructorTests
{
[TestMethod]
public void TestRegisteredArgument()
{
var container = new ServiceContainer();
container.Register<RegisteredClass>();
container.Register<DummyExtraClass>();
container.Register<LocalClass, RegisteredClass>((factory, value) => new RegisteredClass(value, factory.GetInstance<DummyExtraClass>()));
var localInstance = new LocalClass { Id = "I am unique" };
// does not crash
var test = container.GetInstance<LocalClass, RegisteredClass>(localInstance);
Assert.AreEqual(localInstance, test.Local);
}
[TestMethod]
public void TestOverridenArgument()
{
var container = new ServiceContainer();
container.Register<RegisteredClass>();
container.Register<DummyExtraClass>();
container.Register<LocalClass, RegisteredClass>((factory, value) => new RegisteredClass(value, factory.GetInstance<DummyExtraClass>()));
var localInstance = new LocalClass { Id = "I am unique" };
var dummyOverrideClass = new DummyExtraClass { DummyId = "local dummy" };
// should not crash
var test = container.GetInstance<LocalClass, DummyExtraClass, RegisteredClass>(localInstance, dummyOverrideClass);
Assert.AreEqual(localInstance, test.Local);
Assert.AreEqual(dummyOverrideClass, test.Dummy);
}
[TestMethod]
public void TestAdHocArgument()
{
var container = new ServiceContainer();
container.Register<RegisteredClass>();
container.Register<DummyExtraClass>();
var localInstance = new LocalClass { Id = "I am unique" };
// should not crash
var test = container.GetInstance<LocalClass, RegisteredClass>(localInstance);
Assert.AreEqual(localInstance, test.Local);
}
public class RegisteredClass
{
public LocalClass Local { get; private set; }
public DummyExtraClass Dummy { get; set; }
public RegisteredClass(LocalClass local, DummyExtraClass dummy)
{
Local = local;
Dummy = dummy;
}
}
public class DummyExtraClass
{
public string DummyId { get; set; }
}
public class LocalClass
{
public string Id { get; set; }
}
}
}
Another note - the registration of argument along with a class (container.Register<LocalClass, RegisteredClass>
) forces to instantiate the RegisteredClass manually. If any properties need to be injected, it has to be done manually as well. Moreover in the callback (factory, value), factory does not implement IServiceContainer which means it can be either casted to IServiceContainer (tested, works, hackish solution) or global instance of ServiceContainer should be used (not always possible/elegant).
+1 for this issue. It's painful.
A small update for the issue: RegisterConstructorDependency seems to solve the main problem for me (a local instance of a dependency passed to a constructor) which is great. In the amended test (below), TestOverridenArgument still fails as there is a global registration of DummyExtraClass present and it is selected over a constructor dependency which should take precedence in my opinion.
Many thanks for all the hard work on LightInject - it is a real gem.
using LightInject;
namespace LightInject.Tests
{
using System;
using Xunit;
public class ConstructorInjectionOverrideTests
{
[Fact]
public void TestRegisteredArgument()
{
var container = new ServiceContainer();
container.Register<RegisteredClass>();
container.Register<DummyExtraClass>();
container.Register<LocalClass, RegisteredClass>((factory, value) => new RegisteredClass(value, factory.GetInstance<DummyExtraClass>()));
var localInstance = new LocalClass { Id = "I am unique" };
// does not crash
var test = container.GetInstance<LocalClass, RegisteredClass>(localInstance);
Assert.Equal(localInstance, test.Local);
}
[Fact]
public void TestOverridenArgument()
{
var container = new ServiceContainer();
container.Register<RegisteredClass>();
container.Register<DummyExtraClass>();
container.RegisterConstructorDependency((factory, info, arguments) => (LocalClass)arguments[0]);
container.RegisterConstructorDependency((factory, info, arguments) => (DummyExtraClass)arguments[1]);
var localInstance = new LocalClass { Id = "I am unique" };
var dummyOverrideClass = new DummyExtraClass { DummyId = "local dummy" };
// should not crash
var test = container.GetInstance<LocalClass, DummyExtraClass, RegisteredClass>(localInstance, dummyOverrideClass);
Assert.Equal(localInstance, test.Local);
Assert.Equal(dummyOverrideClass, test.Dummy);
}
[Fact]
public void TestAdHocArgument()
{
var container = new ServiceContainer();
container.Register<RegisteredClass>();
container.Register<DummyExtraClass>();
container.RegisterConstructorDependency((factory, info, arguments) => (LocalClass)arguments[0]);
var localInstance = new LocalClass { Id = "I am unique" };
// does not crash
var test = container.GetInstance<LocalClass, RegisteredClass>(localInstance);
Assert.Equal(localInstance, test.Local);
}
public class RegisteredClass
{
public LocalClass Local { get; private set; }
public DummyExtraClass Dummy { get; set; }
public RegisteredClass(LocalClass local, DummyExtraClass dummy)
{
Local = local;
Dummy = dummy;
}
}
public class DummyExtraClass
{
public string DummyId { get; set; }
}
public class LocalClass
{
public string Id { get; set; }
}
}
}