Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RegisterAssembly duplicates services registrations #554

Closed
DmitriyPahovskiy opened this issue Mar 9, 2021 · 4 comments
Closed

RegisterAssembly duplicates services registrations #554

DmitriyPahovskiy opened this issue Mar 9, 2021 · 4 comments

Comments

@DmitriyPahovskiy
Copy link

Hi!
When I use RegisterAssembly method to add types from some library to the container and then try to resolve IEnumerable of some service, I get 2x instances.
For instance, if I have only one implementation of an interface and ask the container for all possible implementations, I expect to get the only implementation, but it provides me with two services, which looks like a bug:

        public interface IDoSomeWork
        { }

        public class DoSomeWork : IDoSomeWork
        { }

        [Test]
        public void LightInject_AssemblyScan_Test()
        {
            var container = new ServiceContainer();

            var assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.FullName.StartsWith("My"));

            container.RegisterAssembly(assembly);
            
            var actualServices = container.GetInstance<IEnumerable<IDoSomeWork>>();
           
            // here I get 2 instead of 1. actualServices contains two objects of DoSomeWork type
            actualServices.Should().HaveCount(1);
        }
@seesharper
Copy link
Owner

Actually this is not a bug and it has been this way all the time.

When scanning an assembly without an ICompositionRoot, LightInject will register everything it finds as services.

In this case we will get two services like this

ServiceType ImplementingType
IDoSomeWork DoSomeWork
DoSomeWork DoSomeWork

Notice that the implementing type DoSomeWork is registered both as IDoSomeWork AND DoSomeWork as the service type.

So next we ask for all services with service type IDoSomeWork

var actualServices = container.GetInstance<IEnumerable<IDoSomeWork>>();

Since variance is enabled by default we will get all services that implements IDoSomeWork which in turn includes both IDoSomeWork AND DoSomeWork.

The recommended way of dealing with this is to implement an ICompositionRoot and explicitly register services.

public class CompositionRoot : ICompositionRoot
{
    public void Compose(IServiceRegistry serviceRegistry)
    {
        serviceRegistry.Register<IDoSomeWork, DoSomeWork>();
    }
}

This is much more descriptive and also prevents the container to be filled up with registrations that are not going to be used.

@DmitriyPahovskiy
Copy link
Author

Thanks for the explanation!

Yes, I`ve found out this two registration in the sources and understand now how it works.

But still, if not pay attention to the containers implementation details, lets look at the problem in abstract way. I do not understand why the container should work this way. We have an interface and the only implementation. When I ask someone (container in this case) "ok, look at this assembly, remember all the typings and provide me with all of the implementations of that interface", I expect getting just one, because getting two does not reflect the types model - it is not natural for my opinion. What is the feature of having two of the same services here?

Concerning ICompositionRoot. In my scenario I have quite big application with a couple of dozens assemblies. Using ICompositionRoot with explicit registration is not convenient - having just one snippet of code which registers all available assemblies is much more usefull. The problem of filling up the container can be solved with filtering assemblies and types.

@seesharper
Copy link
Owner

seesharper commented Mar 9, 2021

The reason it works this way is that not all services are backed by an interface. Even a simple class like this can be a service and LightInject takes the route of registering all classes as services.

public class Foo
{
}

But this behaviour can be changed

container.RegisterAssembly(assembly, (serviceType, implementingType) => serviceType.IsInterface);

or any other condition we should want for registering services 👍😊

@DmitriyPahovskiy
Copy link
Author

Yep, this works, thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants