ipjohnson / Grace

Grace is a feature rich dependency injection container library
MIT License
336 stars 33 forks source link

LocateException after trying to locate root dependency from child container #150

Closed krasin-ga closed 6 years ago

krasin-ga commented 6 years ago

Code to reproduce:

 internal class Program
    {
        private static void Main(string[] args)
        {
            var container = new DependencyInjectionContainer();
            container.Configure(ConfigureRoot);
            var child = container.CreateChildScope(ConfigureChild);
            child.Locate<RootDependency>();
        }

        private static void ConfigureChild(IExportRegistrationBlock obj)
        {
            obj.Export<ChildDependency>().As<IChildDependency>();
        }

        private static void ConfigureRoot(IExportRegistrationBlock obj)
        {
            obj.Export<RootDependency>();
        }
    }

    public interface IChildDependency
    {
    }

    public class ChildDependency : IChildDependency
    {
    }

    public class RootDependency
    {
        public RootDependency(IChildDependency childDependency)
        {
        }
    }
ipjohnson commented 6 years ago

hi @krasin-ga

By design Grace won't look for dependencies in child containers unless you configure it to (for performance reasons). You can configure it on an individual basis or on the container wide basis (usually you can get away with just doing it in certain spots).

Try changing the RootDependency registration to this

obj.Export<RootDependency>().WithCtorParam<IChildDependency>().IsDynamic();
krasin-ga commented 6 years ago

Hello! Thank you for the quick reply. 👍 How can i configure it on container wide basis? I have dependencies that must be available only to child scope, but child scope must be able to resolve dependencies from root scope.

ipjohnson commented 6 years ago

You can change the constructor selection behavior to be dynamic all the type. This make all constructor selection dynamic dependent on what's available and it will also make all paramters dynamic and resolvable from child containers.

new DependencyInjectionContainer(c => c.Behaviors.ConstructorSelection = ConstructorSelectionMethod.Dynamic);
krasin-ga commented 6 years ago

Is there something bad in doing something like this?

    internal class ExportMissingFromRootScope : IMissingExportStrategyProvider
    {
        private readonly IInjectionScope _rootScope;

        public ExportMissingFromRootScope(IExportLocatorScope rootScope)
        {
            _rootScope = rootScope.GetInjectionScope();
        }

        public bool CanLocate(IInjectionScope scope, IActivationExpressionRequest request)
        {
            return _rootScope.CanLocate(request.ActivationType);
        }

        public IEnumerable<IActivationStrategy> ProvideExports(IInjectionScope scope, IActivationExpressionRequest request)
        {
            return _rootScope.MissingExportStrategyProviders.SelectMany(s => s.ProvideExports(_rootScope, request));
        }
    }
        private static void Main(string[] args)
        {
            var root = new DependencyInjectionContainer();
            root.Configure(ConfigureRoot);

            var child = root.CreateChildScope(c => ConfigureChild(root, c));
            child.Locate<RootDependency>();
        }

        private static void ConfigureChild(IExportLocatorScope root, IExportRegistrationBlock obj)
        {
            obj.AddMissingExportStrategyProvider(new ExportMissingFromRootScope(root));
            obj.Export<ChildDependency>().As<IChildDependency>();
        }

Somehow its working 33x faster than using dynamic constructor selection.

ipjohnson commented 6 years ago

@krasin-ga I don't think that's going to work the way you want it to. That said your idea gave me a thought of maybe there is a way to use IMissingExportStrategyProvider to provide strategies to route missing dependencies to the child container. I'll see if I can find some time to put together a prototype of what I'm thinking.

krasin-ga commented 6 years ago

It would be nice, thank you! 👍

ipjohnson commented 6 years ago

@krasin-ga I've thought some more about the problem and I can see to ways of doing it but they both have caveats.

Ultimately you have to be able to provide some basic knowledge of what's missing and what should be resolved form the child container. If you know that than either one of the solution mentioned will work, but if there is no way to know what's going to be registered in the child container that I don't see a way of safely speeding it up.

krasin-ga commented 6 years ago

Why not use scope.CanLocate() to determine what to resolve with child contrainer?

I tried to add it to the InjectionContextValueProvider just to test if it will work: image

And everything seems to work as expected.

ipjohnson commented 6 years ago

@krasin-ga if that's working for you perfect. My concern was that CanLocate is a hash lookup and then a list lookup so it's slightly costly (certainly not like dynamic constructors but not the fastest solution).

ipjohnson commented 6 years ago

@krasin-ga I'm going to close this. IF you have any more questions feel free to open a new issue