ipjohnson / Grace

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

Locate over two Scopes #163

Closed SeriousM closed 6 years ago

SeriousM commented 6 years ago

Hi, I'm trying to locate a service in a root scope which depends on an service in a child scope.

void Main()
{
    var rootScope = new DependencyInjectionContainer(c => c.AutoRegisterUnknown = false);
    rootScope.Configure(reg =>
    {
        reg.AddMemberInjectionSelector(new PublicMemeberInjectionSelector(mix => true, true, true));
        reg.Export<RootService>();
    });

    var childScope = rootScope.CreateChildScope(reg => reg.Export<ChildService>());

    childScope.CanLocate(typeof(RootService)); // -> true
    childScope.CanLocate(typeof(ChildService)); // -> true
    childScope.Locate<RootService>(); // -> Exception: 
    /*
    Could not locate Type ChildService
    1 Importing RootService 
    2 Importing ChildService  for property ChildProp    
    */
}

class RootService
{
    [Import]
    public ChildService ChildProp { get; set; }
}

class ChildService { }

Since I operate on the childScope the dependencies should be satisfied as I can locate everything separately. What do I do wrong?

This is the content of "WhatDoIHave":

--------------------------------------------------------------------------------
Exports for scope '' with id 46ea6d74-158e-4809-849c-144fec4b79a6
--------------------------------------------------------------------------------
Export Type: UserQuery+ChildService
Priority: 0
Externally Owned: False
Lifestyle: Transient
Depends On
  None
--------------------------------------------------------------------------------
Export Type: System.Func`2[[System.Type, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Object, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]
Priority: 0
Externally Owned: False
Lifestyle: Transient
Depends On
  None
--------------------------------------------------------------------------------
Exports for scope 'RootScope' with id f3998fb5-5593-496f-b5cf-ba71d15b040e
--------------------------------------------------------------------------------
Export Type: UserQuery+RootService
Priority: 0
Externally Owned: False
Lifestyle: Transient
Depends On
  Dependency Type: Property
  Member Name: ChildProp
  Import Type: UserQuery+ChildService
  Has Filter: False
  Has Value Provider: False
  Is Satisfied: False

--------------------------------------------------------------------------------
Export Type: System.Func`2[[System.Type, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Object, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]
Priority: 0
Externally Owned: False
Lifestyle: Transient
Depends On
  None
ipjohnson commented 6 years ago

For performance reasons by default parent scopes don't look for dependencies in child scope (it's expensive). If it was a constructor parameter you could mark it as "Dynamic" (tells the container to look in child scopes first) but as I look at it I think that option is missing from PublicMemeberInjectionSelector.

I'll see about adding it in the coming days.

SeriousM commented 6 years ago

I just copied the implementation of PublicMemberInjectionSelector. Do I have to add IsDynamic = true to yield return new MemberInjectionInfo... to get this lookup behavior?

SeriousM commented 6 years ago

Another approach: Can I achieve the desired behavior if I add a MissingExportStrategyProvider? The flow would be to locate the RootService but the ChildService can't be found. My MissingExportStrategyProvider would go to the ChildContainer and Locate the service. This way it's not so expensive I guess?

ipjohnson commented 6 years ago

@SeriousM I actually kinda like the MissingExportStrategyProvider because it kind of fills in the blanks by routing calls to the child container.

Your question of adding IsDynamic I believe that would work but I think your other idea is better.

SeriousM commented 6 years ago

That's how you can do it with MEF as every container that you create is a ExportProvider that can be used to compose other containers - actually the same concept as the MissingExportStrategyProvider.

Could you help me creating the Provider? I just have too less insight in all the types you have...

public class ContainerLookupMissingExportStrategyProvider : IMissingExportStrategyProvider
{
  private readonly IInjectionScope injectionScope;

  public ContainerLookupMissingExportStrategyProvider(IInjectionScope injectionScope)
  {
    this.injectionScope = injectionScope;
  }

  public bool CanLocate(IInjectionScope scope, IActivationExpressionRequest request)
  {
    var canLocate = injectionScope.CanLocate(request.ActivationType, key: request.LocateKey);

    return canLocate;
  }

  public IEnumerable<IActivationStrategy> ProvideExports(IInjectionScope scope, IActivationExpressionRequest request)
  {
    // how to create the export from an existing container?

    yield return export;
  }
}

Btw, a few lines in the wiki about your namings of items would help tremendously (eg. Compiled, ...)!

ipjohnson commented 6 years ago

I'll see if I can put something together in the next day or two. It's definitely possible I'm just busy with a couple other things this week.

SeriousM commented 6 years ago

Perfect, thank you!

SeriousM commented 6 years ago

Hi @ipjohnson, were you able to investigate the issues? Thank you!

ipjohnson commented 6 years ago

@SeriousM I did but I I ran into an infinite loop if the dependency is missing. I'm looking at another option to using an IConstructorExpressionCreator

ipjohnson commented 6 years ago

@SeriousM I've pushed version 6.4.0-Beta679 to nuget. It contains a new feature IMissingDependencyExpressionProvider you can find an implementation that should work for you use case here

SeriousM commented 6 years ago

This solution seems to work! I'm honest, I don't understand all the magic in the ChildContainerExpressionProvider. Are you about to add the ChildContainerExpressionProvider to the nuget package?

ipjohnson commented 6 years ago

At first I was going to just leave it in the tests but if you think it would be useful to have it part of Grace proper I’d be willing to move it in.

SeriousM commented 6 years ago

Seems it is not fully the way it should work.

Imagine this construct: Root-Chuld1-Child2

Root has serviceA depending on serviceB in Child1.

Child2 should locate serviceA. It finds it in root but not serviceB. Now the search should start on Child3 and traverse up till root to find the missing piece (in Child2).

Do you think this is doable?

Another question: I'm able to hide serviceB in Child2 if I add another implementation of serviceB in Child3? This would be helpful in test scenarios to replace services with mocks.

ipjohnson commented 6 years ago

Hmmm I’m actually surprised it’s not working that way. Would it be possible to put together a simple console app? Also do you happen to have it going through a lifestyle?

I’ll take a closer look this evening

ipjohnson commented 6 years ago

Ok I see what's wrong and it's actually a bug in Grace itself dealing with child scopes. I'm going to address it now and look to release another beta in the next couple days with the AllowNull feature for factories.

There is a nightly build nuget feed you can test here https://ci.appveyor.com/nuget/grace-master

SeriousM commented 6 years ago

Hey @ipjohnson, I have bad news for you. I will be honest with you: I've spent so many hours to get your container running and wasn't able to get it working with my scenarios. I switched to DryIoc and have to abort our little journey.

Here is my testsuite if you want to look at it: https://github.com/SeriousM/DryIocTests

I home you're doing well. Bernhard

ipjohnson commented 6 years ago

No worries it’s not for everyone/all situations.