JasperFx / lamar

Fast Inversion of Control Tool and Successor to StructureMap
https://jasperfx.github.io/lamar
MIT License
563 stars 118 forks source link

Default registration for "Injectables" #321

Closed rizi closed 2 years ago

rizi commented 2 years ago

Hi, I want to know if it's possible to register a type in the "root" container AND mark it as injectable.

My goal is that I have a default registration that is used for the root container and for nested containers and when I use nestedContainer.Inject(..........) it should overwrite the default registration for this specific nested container.

Is there a way to achieve this?

As soon as I mark a type as Injectable lamar always returns NULL (ignores the registration) as long as I don't use nestedContainer.Inject(.....)

Here is a small sample.

           //arrange
            Container container = new Container(config =>
                                                {
                                                    config
                                                        .For<INamedRegistration>()
                                                        .Use<ClassToBeNamedRegistered>(); //this should be the default registration

                                                    config
                                                        .Injectable<INamedRegistration>();
                                                });

            INestedContainer nestedContainer = container.GetNestedContainer();

            nestedContainer.Inject(typeof(INamedRegistration), new SpecialClassToBeNamedRegistered(), true);

            //act
            INamedRegistration namedRegistration = container.GetInstance<INamedRegistration>();
            INamedRegistration nestedRegistration = nestedContainer.GetInstance<INamedRegistration>();

            //assert
            namedRegistration.Should().NotBeNull(); // should be of type ClassToBeNamedRegistered and should not be null, **but it's null here!**
            nestedRegistration.Should().NotBeNull(); //should not be null and should be of type SpecialClassToBeNamedRegistered
jeremydmiller commented 2 years ago

"Injectables" are scoped, so you technically can have a value scoped to the root container, and then to each nested container. There is not formal fallback mechanism, and to be straightforward, that's a conscious design decision in Lamar to not support that kind of use case as it was extremely problematic to support in StructureMap.

You could fake this behavior by having a singleton holder type in the parent, then a scoped holder in a nested container. Use a Lambda registration to first check if the scoped handler is not null, then programmatically fall back to the parent. No change to Lamar is necessary to do that.

And also just to throw this out, I generally recommend against doing this kind of thing since it's going to confuse many devs and lead to subtle errors if you're not careful.

rizi commented 2 years ago

"Injectables" are scoped, so you technically can have a value scoped to the root container, and then to each nested container. There is not formal fallback mechanism, and to be straightforward, that's a conscious design decision in Lamar to not support that kind of use case as it was extremely problematic to support in StructureMap.

You could fake this behavior by having a singleton holder type in the parent, then a scoped holder in a nested container. Use a Lambda registration to first check if the scoped handler is not null, then programmatically fall back to the parent. No change to Lamar is necessary to do that.

And also just to throw this out, I generally recommend against doing this kind of thing since it's going to confuse many devs and lead to subtle errors if you're not careful.

@jeremydmiller Thx for the reply!

Could you provide a little bit more technical details. Do I have to mark the type as injectable? Or register it as singleton?

How do I check in the lambda instance if there is a needed container and how do I get access to the nested container?

Br

jeremydmiller commented 2 years ago

Something like this:

public class ParentHolder<T>
{
   public T Tracked {get;set;
}

var container = Container.For(s => {
     s.AddSingleton<ParentHolder<Foo>>(new ParentHolder<Foo>{Tracked = new Foo()});

     // need this just to set up the injectable slot
     s.Injectable<Foo>();

     // And here's what I mean logically by the fall back. So Injectable<T> is scoped, ParentHolder<T> is at the root.
     s.For<Foo>().Use(s => {
          return s.GetInstance<Foo>() ?? s.GetInstance<ParentHolder<Foo>>().Tracked;
     });

});
rizi commented 2 years ago

Something like this:

public class ParentHolder<T>
{
   public T Tracked {get;set;
}

var container = Container.For(s => {
     s.AddSingleton<ParentHolder<Foo>>(new ParentHolder<Foo>{Tracked = new Foo()});

     // need this just to set up the injectable slot
     s.Injectable<Foo>();

     // And here's what I mean logically by the fall back. So Injectable<T> is scoped, ParentHolder<T> is at the root.
     s.For<Foo>().Use(s => {
          return s.GetInstance<Foo>() ?? s.GetInstance<ParentHolder<Foo>>().Tracked;
     });

});

Thank you very much!