z4kn4fein / stashbox

A lightweight, fast, and portable dependency injection framework for .NET-based solutions.
https://z4kn4fein.github.io/stashbox
MIT License
141 stars 10 forks source link

Unknown type resolution does not work recursively #77

Closed schuettecarsten closed 4 years ago

schuettecarsten commented 4 years ago

I try to implement a function that resolves my service registration on demand by using Stashbox' WithUnknownTypeResolution feature. Look at the following classes:

    [UsedImplicitly]
    public class BillingPeriodService : BaseServiceWithUnitOfWork
    {
        public BillingPeriodService(IServiceContextWithUnitOfWork serviceContext, AccountService accountService)
            : base(serviceContext)
        {
    [UsedImplicitly]
    public class AccountService : BaseServiceWithUnitOfWork
    {
        public AccountService(IServiceContextWithUnitOfWork serviceContext)
            : base(serviceContext)
        {
    [UsedImplicitly]
    public class ServiceContextWithUnitOfWork : ServiceContext, IServiceContextWithUnitOfWork
    {
        public ServiceContextWithUnitOfWork(IObjectProvider objectProvider)
            : base(objectProvider)
        {

When I try to resolve BillingPeriodService, my UnknownTypeResolution-function is invoked with a context to BillingPeriodService. I can check my service descriptors and complete the registration context with correct lifetime and implementation type. After that, I get an exception that the type not be resolved. I would expect that my UnknownTypeResolution-function is invoked again and again for all required constructor parameters...?

z4kn4fein commented 4 years ago

Is this still an issue with the latest pre-release version? A covered issue could have led to this, could you please check again? Thanks!

schuettecarsten commented 4 years ago

Yes, the issue is still there with https://github.com/z4kn4fein/stashbox/commit/34d7caf469566106dd790d72e812883eeaf97874

Stashbox.Exceptions.ResolutionFailedException
  HResult=0x80131500
  Message=Could not resolve type BillingPeriodService.
Constructor Void .ctor(IServiceContextWithUnitOfWork) found with unresolvable parameter: (IServiceContextWithUnitOfWork)serviceContext.

  Source=ObjectProvider.Stashbox
  StackTrace:
   at Stashbox.BuildUp.Expressions.MethodExpressionBuilder.SelectConstructor(IContainerContext containerContext, RegistrationContext registrationContext, ResolutionContext resolutionContext, ConstructorInfo[] constructors, Expression[]& parameterExpressions) in ObjectProvider.Stashbox\src\BuildUp\Expressions\MethodExpressionBuilder.cs:line 80

The full callstack vom Visual Studio debugger:

>   ObjectProvider.Stashbox.dll!Stashbox.BuildUp.Expressions.MethodExpressionBuilder.SelectConstructor(Stashbox.IContainerContext containerContext, Stashbox.Registration.RegistrationContext registrationContext, Stashbox.Resolution.ResolutionContext resolutionContext, System.Reflection.ConstructorInfo[] constructors, out System.Linq.Expressions.Expression[] parameterExpressions) Line 79    C#
    ObjectProvider.Stashbox.dll!Stashbox.BuildUp.Expressions.ExpressionBuilder.CreateInitExpression(Stashbox.IContainerContext containerContext, Stashbox.Registration.RegistrationContext registrationContext, Stashbox.Resolution.ResolutionContext resolutionContext, System.Reflection.ConstructorInfo[] constructors) Line 204 C#
    ObjectProvider.Stashbox.dll!Stashbox.BuildUp.Expressions.ExpressionBuilder.ConstructExpression(Stashbox.IContainerContext containerContext, Stashbox.Registration.IServiceRegistration serviceRegistration, Stashbox.Resolution.ResolutionContext resolutionContext, System.Type serviceType) Line 110  C#
    ObjectProvider.Stashbox.dll!Stashbox.BuildUp.ObjectBuilders.DefaultObjectBuilder.PrepareExpression(Stashbox.IContainerContext containerContext, Stashbox.Registration.IServiceRegistration serviceRegistration, Stashbox.Resolution.ResolutionContext resolutionContext, System.Type resolveType) Line 38   C#
    ObjectProvider.Stashbox.dll!Stashbox.BuildUp.ObjectBuilders.DefaultObjectBuilder.GetExpressionInternal(Stashbox.IContainerContext containerContext, Stashbox.Registration.IServiceRegistration serviceRegistration, Stashbox.Resolution.ResolutionContext resolutionContext, System.Type resolveType) Line 30   C#
    ObjectProvider.Stashbox.dll!Stashbox.BuildUp.ObjectBuilderBase.BuildDisposalTrackingAndFinalizerExpression(Stashbox.IContainerContext containerContext, Stashbox.Registration.IServiceRegistration serviceRegistration, Stashbox.Resolution.ResolutionContext resolutionContext, System.Type resolveType) Line 54   C#
    ObjectProvider.Stashbox.dll!Stashbox.BuildUp.ObjectBuilderBase.GetExpression(Stashbox.IContainerContext containerContext, Stashbox.Registration.IServiceRegistration serviceRegistration, Stashbox.Resolution.ResolutionContext resolutionContext, System.Type resolveType) Line 23 C#
    ObjectProvider.Stashbox.dll!Stashbox.Lifetime.LifetimeDescriptor.GetNewFactoryDelegate(Stashbox.IContainerContext containerContext, Stashbox.Registration.IServiceRegistration serviceRegistration, Stashbox.Resolution.ResolutionContext resolutionContext, System.Type resolveType) Line 106  C#
    ObjectProvider.Stashbox.dll!Stashbox.Lifetime.LifetimeDescriptor.GetFactoryDelegate(Stashbox.IContainerContext containerContext, Stashbox.Registration.IServiceRegistration serviceRegistration, Stashbox.Resolution.ResolutionContext resolutionContext, System.Type resolveType) Line 100 C#
    ObjectProvider.Stashbox.dll!Stashbox.Lifetime.ScopedLifetime.GetLifetimeAppliedExpression(Stashbox.IContainerContext containerContext, Stashbox.Registration.IServiceRegistration serviceRegistration, Stashbox.Resolution.ResolutionContext resolutionContext, System.Type resolveType) Line 21    C#
    ObjectProvider.Stashbox.dll!Stashbox.Lifetime.LifetimeDescriptor.GetExpression(Stashbox.IContainerContext containerContext, Stashbox.Registration.IServiceRegistration serviceRegistration, Stashbox.Resolution.ResolutionContext resolutionContext, System.Type resolveType) Line 34   C#
    ObjectProvider.Stashbox.dll!Stashbox.Registration.ServiceRegistration.GetExpression(Stashbox.IContainerContext containerContext, Stashbox.Resolution.ResolutionContext resolutionContext, System.Type resolveType) Line 100 C#
    ObjectProvider.Stashbox.dll!Stashbox.Resolution.ResolutionStrategy.BuildResolutionExpression(Stashbox.IContainerContext containerContext, Stashbox.Resolution.ResolutionContext resolutionContext, Stashbox.Entity.TypeInformation typeInformation, System.Collections.Generic.IEnumerable<Stashbox.Entity.InjectionParameter> injectionParameters, bool forceSkipUnknownTypeCheck) Line 76 C#
    ObjectProvider.Stashbox.dll!Stashbox.Resolution.Resolvers.UnknownTypeResolver.GetExpression(Stashbox.IContainerContext containerContext, Stashbox.Resolution.IResolutionStrategy resolutionStrategy, Stashbox.Entity.TypeInformation typeInfo, Stashbox.Resolution.ResolutionContext resolutionContext) Line 23 C#
    ObjectProvider.Stashbox.dll!Stashbox.Resolution.ResolutionStrategy.BuildResolutionExpressionUsingResolvers(Stashbox.IContainerContext containerContext, Stashbox.Entity.TypeInformation typeInfo, Stashbox.Resolution.ResolutionContext resolutionContext, bool forceSkipUnknownTypeCheck) Line 106 C#
    ObjectProvider.Stashbox.dll!Stashbox.ResolutionScope.Activate(Stashbox.Resolution.ResolutionContext resolutionContext, System.Type type, object name) Line 304  C#
    ObjectProvider.Stashbox.dll!Stashbox.ResolutionScope.Resolve(System.Type typeFrom, bool nullResultAllowed, object[] dependencyOverrides) Line 86    C#
    ObjectProvider.Stashbox.dll!Stashbox.DependencyResolverExtensions.Resolve<CloudManagementTool.Connector.Azure.PartnerCenter.Services.CspInvoiceNotificationMailService>(Stashbox.IDependencyResolver resolver, bool nullResultAllowed, object[] dependencyOverrides) Line 20    C#
    [External Code] 
z4kn4fein commented 4 years ago

Okay, I'm moving forward to this then, thanks!

z4kn4fein commented 4 years ago

Hey, the issue was that the UnknownTypeResolver was not able to resolve your IServiceContextWithUnitOfWork because it's an interface type, so it was bypassed by the resolver completely. I turned off this behavior when the configuration delegate for the unknown type resolution is set, so it will be called for the interfaces too. Could you please try the new version? Thanks!

schuettecarsten commented 4 years ago

There is a new side effect now.

My code gets an InvalidRegistrationException when I try to get an "optional" interface value from the container using Resolve<ISomeInterface>(true). In this case, it's correct that the UnknownTypeResolver is not able to resolve this type, and it's expected that this could happen - null is allowed here.

z4kn4fein commented 4 years ago

Hey, thanks for reporting, that side effect should be covered now, could you check please? Thanks!

schuettecarsten commented 4 years ago

This seems to work now, but another issue (not sure if this is caused by the fix): services registered with a scope name and Lifetime.NamedScope or with DefinesScope are not found if you try to resolve them form a different scope, regardless if registered at startup or in UnknownTypeResolver.

I can also see that services that are registrered in Singleton scope are disposed when they were used by a different scoped service when the scope that owns the scoped service is disposed.

z4kn4fein commented 4 years ago

Sorry for the late response, thanks for reporting these!

That's a by-design thing, services registered with NamedScope are only picked when you resolve them within the scope that has the matching name. That's kind of a resolution condition also. In what case you consider this as an issue? Should it work differently in your opinion?

Could you give me an example of the Singleton issue? I'm not able to reproduce that. Thanks!

schuettecarsten commented 4 years ago

I have several services that are registered as "Singleton". In the current implementation, they all share their dependencies and are registered in the same scope. For example, they all use the same DbContext. So my idea was to register the services into a separate "named scope", to isolate them from each other.

z4kn4fein commented 4 years ago

Sorry, i think i wasn't clear with this one, i meant that could you please send me a code example or a sample project where the dispose issue with the singletons are reproducable? I did not find any composition to recreate this kind of issue. Singletons are disposed only by the root scope, and I can't see where could be the behavior changed.

schuettecarsten commented 4 years ago

I think my Dispose problem was caused by my very strange tries to register Singletons in a Named Scope, sorry for the confusion. I have changed my code to make sure that the Singleton dependencies are registered with Transient scope instead of Scoped/NamedScope. It seems to work now, if I run into new issues, I will create a new issue here.

Thank you for fixing everything :-)