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

Resolve enforces passed arguments to be used (and fails because of it) #646

Open
cytoph opened this issue Jun 13, 2024 · 10 comments
Open

Resolve enforces passed arguments to be used (and fails because of it) #646

cytoph opened this issue Jun 13, 2024 · 10 comments

Comments

@cytoph
Copy link

cytoph commented Jun 13, 2024

I just updated DryIoc from version 5.0.2 to 5.4.0 and an error starts occurring (of which I could not find anything in breaking changes or such). The error occurs on calling container.Resolve(serviceType, [service]). service is, in this case, a service instance that I want to be passed to the serviceType constructor in place of the instance that is registered in the container (that's why I pass it). This worked fine before the update. Now, on version 5.4.0, I get an exception, because the container tries to pass this service instance to every other (undelying) service that is resolved during this call, and not just that, it forces it to be present in every constructor, it seems, which leads to the following exception:

DryIoc.ContainerException: code: Error.UnableToResolveFromRegisteredServices;
message: Unable to resolve MyProject.Shared.Converters.IntegerConverter with passed arguments [INavigationService@0] (IsSingletonOrDependencyOfSingleton, IsWrappedInFunc)
  in MyProject.Shared.Converters.FieldValueConverter: MyProject.Shared.Converters.IConverter {ServiceKey=MyProject.Shared.Converters.FieldValueConverter, DryIoc.IfUnresolved.ReturnDefaultIfNotRegistered} FactoryId=862 with passed arguments [INavigationService@0] (IsSingletonOrDependencyOfSingleton, IsWrappedInFunc, IsDirectlyWrappedInFunc)
  in wrapper Func<MyProject.Shared.Converters.IConverter> RequiredServiceType=MyProject.Shared.Converters.IConverter, ServiceKey=MyProject.Shared.Converters.FieldValueConverter, DryIoc.IfUnresolved.ReturnDefaultIfNotRegistered} WrapperExpressionFactory  FactoryId=885 with passed arguments [INavigationService@0] (IsSingletonOrDependencyOfSingleton, IsWrappedInFunc)
  in wrapper KeyValuePair<Type, Func<MyProject.Shared.Converters.IConverter>> RequiredServiceType=MyProject.Shared.Converters.IConverter, ServiceKey=MyProject.Shared.Converters.FieldValueConverter, DryIoc.IfUnresolved.ReturnDefaultIfNotRegistered} WrapperExpressionFactoryWithSetup  FactoryId=878 with passed arguments [INavigationService@0] (IsSingletonOrDependencyOfSingleton, IsWrappedInFunc)
  in wrapper IEnumerable<KeyValuePair<Type, Func<MyProject.Shared.Converters.IConverter>>> as parameter "factories" ExpressionFactory  FactoryId=875 with passed arguments [INavigationService@0] (IsSingletonOrDependencyOfSingleton, IsWrappedInFunc)
  in Singleton MyProject.Client.Services.ConverterFactory as parameter "converterFactory" FactoryId=872 with passed arguments [INavigationService@0] (IsSingletonOrDependencyOfSingleton, IsWrappedInFunc)
  in MyProject.Client.Services.AdditionalFieldsHelperService as parameter "additionalFieldsHelperService" FactoryId=820 with passed arguments [INavigationService@0] (IsSingletonOrDependencyOfSingleton, IsWrappedInFunc)
  in MyProject.Client.Services.DocumentFactory FactoryId=823 with passed arguments [INavigationService@0] (IsSingletonOrDependencyOfSingleton, IsWrappedInFunc)
  in MyProject.Client.Processes.Sales.SalesDelivery: MyProject.Client.Processes.IProcess {ServiceKey=MyProject.Shared.Enums.Process.SalesDelivery, DryIoc.IfUnresolved.ReturnDefaultIfNotRegistered} FactoryId=826 with passed arguments [INavigationService@0] (IsSingletonOrDependencyOfSingleton, IsWrappedInFunc, IsDirectlyWrappedInFunc)
  in wrapper Func<My.Framework.Maui.Navigation.INavigationService, MyProject.Client.Processes.IProcess> RequiredServiceType=MyProject.Client.Processes.IProcess, ServiceKey=MyProject.Shared.Enums.Process.SalesDelivery, DryIoc.IfUnresolved.ReturnDefaultIfNotRegistered} WrapperExpressionFactoryWithSetup  FactoryId=886 with passed arguments [INavigationService@0] (IsSingletonOrDependencyOfSingleton)
  in wrapper KeyValuePair<MyProject.Shared.Enums.Process, Func<My.Framework.Maui.Navigation.INavigationService, MyProject.Client.Processes.IProcess>> RequiredServiceType=MyProject.Client.Processes.IProcess, ServiceKey=MyProject.Shared.Enums.Process.SalesDelivery, DryIoc.IfUnresolved.ReturnDefaultIfNotRegistered} WrapperExpressionFactoryWithSetup  FactoryId=878 with passed arguments [Constant(My.Framework.Maui.Navigation.Implementations.NavigationService, typeof(My.Framework.Maui.Navigation.Implementations.NavigationService))] (IsSingletonOrDependencyOfSingleton)
  in wrapper IEnumerable<KeyValuePair<MyProject.Shared.Enums.Process, Func<My.Framework.Maui.Navigation.INavigationService, MyProject.Client.Processes.IProcess>>> as parameter "factories" ExpressionFactory  FactoryId=875 with passed arguments [Constant(My.Framework.Maui.Navigation.Implementations.NavigationService, typeof(My.Framework.Maui.Navigation.Implementations.NavigationService))] (IsSingletonOrDependencyOfSingleton)
  in Singleton MyProject.Client.Services.ProcessService as parameter "processService" FactoryId=811 with passed arguments [Constant(My.Framework.Maui.Navigation.Implementations.NavigationService, typeof(My.Framework.Maui.Navigation.Implementations.NavigationService))] (IsSingletonOrDependencyOfSingleton)
  in resolution root MyProject.Client.Views.MainViewModel FactoryId=758 with passed arguments [Constant(My.Framework.Maui.Navigation.Implementations.NavigationService, typeof(My.Framework.Maui.Navigation.Implementations.NavigationService))]
  from container without scope
 with Rules with {TrackingDisposableTransients, SelectLastRegisteredFactory} and without {ThrowOnRegisteringDisposableTransient, VariantGenericTypesInResolvedCollection}
 with FactorySelector=SelectLastRegisteredFactory
 with Made={FactoryMethod=ConstructorWithResolvableArguments}
  with normal and dynamic registrations:
  (MyProject.Shared.Converters.IntegerConverter, {FactoryID=863, ImplType=MyProject.Shared.Converters.IntegerConverter})
   at DryIoc.Throw.It(Int32 error, Object arg0, Object arg1, Object arg2, Object arg3) in /_/src/DryIoc/Container.cs:line 14641
   at DryIoc.Container.TryThrowUnableToResolve(Request request) in /_/src/DryIoc/Container.cs:line 930
   at DryIoc.Container.DryIoc.IContainer.ResolveFactory(Request request) in /_/src/DryIoc/Container.cs:line 912
   at DryIoc.ReflectionFactory.CreateExpressionOrDefault(Request request) in /_/src/DryIoc/Container.cs:line 12013
   at DryIoc.Factory.GetExpressionOrDefault(Request request) in /_/src/DryIoc/Container.cs:line 10969
   at DryIoc.WrappersSupport.GetFuncOrActionExpressionOrDefault(Request request, Factory serviceFactory) in /_/src/DryIoc/Container.cs:line 5267
   at DryIoc.WrapperExpressionFactory.CreateExpressionWithWrappedFactory(Request request, Factory serviceFactory) in /_/src/DryIoc/Container.cs:line 12534
   at DryIoc.Factory.GetExpressionOrDefault(Request request) in /_/src/DryIoc/Container.cs:line 10969
   at DryIoc.WrappersSupport.GetKeyValuePairExpressionOrDefault(Request request, Factory serviceFactory) in /_/src/DryIoc/Container.cs:line 5339
   at DryIoc.WrapperExpressionFactory.CreateExpressionWithWrappedFactory(Request request, Factory serviceFactory) in /_/src/DryIoc/Container.cs:line 12534
   at DryIoc.Factory.GetExpressionOrDefault(Request request) in /_/src/DryIoc/Container.cs:line 10969
   at DryIoc.WrappersSupport.GetArrayExpression(Request request) in /_/src/DryIoc/Container.cs:line 5152
   at DryIoc.WrappersSupport.<>c.<BuildSupportedWrappers>b__9_0(Request r) in /_/src/DryIoc/Container.cs:line 4992
   at DryIoc.ExpressionFactory.CreateExpressionOrDefault(Request request) in /_/src/DryIoc/Container.cs:line 12482
   at DryIoc.Factory.GetExpressionOrDefault(Request request) in /_/src/DryIoc/Container.cs:line 10969
   at DryIoc.ReflectionFactory.CreateExpressionOrDefault(Request request) in /_/src/DryIoc/Container.cs:line 12013
   at DryIoc.Factory.GetExpressionOrDefault(Request request) in /_/src/DryIoc/Container.cs:line 10969
   at DryIoc.ReflectionFactory.CreateExpressionOrDefault(Request request) in /_/src/DryIoc/Container.cs:line 12013
   at DryIoc.Factory.GetExpressionOrDefault(Request request) in /_/src/DryIoc/Container.cs:line 10969
   at DryIoc.ReflectionFactory.CreateExpressionOrDefault(Request request) in /_/src/DryIoc/Container.cs:line 12013
   at DryIoc.Factory.GetExpressionOrDefault(Request request) in /_/src/DryIoc/Container.cs:line 10969
   at DryIoc.ReflectionFactory.CreateExpressionOrDefault(Request request) in /_/src/DryIoc/Container.cs:line 12013
   at DryIoc.Factory.GetExpressionOrDefault(Request request) in /_/src/DryIoc/Container.cs:line 10969
   at DryIoc.WrappersSupport.GetFuncOrActionExpressionOrDefault(Request request, Factory serviceFactory) in /_/src/DryIoc/Container.cs:line 5267
   at DryIoc.WrapperExpressionFactory.CreateExpressionWithWrappedFactory(Request request, Factory serviceFactory) in /_/src/DryIoc/Container.cs:line 12534
   at DryIoc.Factory.GetExpressionOrDefault(Request request) in /_/src/DryIoc/Container.cs:line 10969
   at DryIoc.WrappersSupport.GetKeyValuePairExpressionOrDefault(Request request, Factory serviceFactory) in /_/src/DryIoc/Container.cs:line 5339
   at DryIoc.WrapperExpressionFactory.CreateExpressionWithWrappedFactory(Request request, Factory serviceFactory) in /_/src/DryIoc/Container.cs:line 12534
   at DryIoc.Factory.GetExpressionOrDefault(Request request) in /_/src/DryIoc/Container.cs:line 10969
   at DryIoc.WrappersSupport.GetArrayExpression(Request request) in /_/src/DryIoc/Container.cs:line 5152
   at DryIoc.WrappersSupport.<>c.<BuildSupportedWrappers>b__9_0(Request r) in /_/src/DryIoc/Container.cs:line 4992
   at DryIoc.ExpressionFactory.CreateExpressionOrDefault(Request request) in /_/src/DryIoc/Container.cs:line 12482
   at DryIoc.Factory.GetExpressionOrDefault(Request request) in /_/src/DryIoc/Container.cs:line 10969
   at DryIoc.ReflectionFactory.CreateExpressionOrDefault(Request request) in /_/src/DryIoc/Container.cs:line 12013
   at DryIoc.Factory.GetExpressionOrDefault(Request request) in /_/src/DryIoc/Container.cs:line 10969
   at DryIoc.ReflectionFactory.CreateExpressionOrDefault(Request request) in /_/src/DryIoc/Container.cs:line 12013
   at DryIoc.Factory.GetExpressionOrDefault(Request request) in /_/src/DryIoc/Container.cs:line 10969
   at DryIoc.Container.ResolveAndCacheKeyed(Int32 serviceTypeHash, Type serviceType, Object serviceKey, IfUnresolved ifUnresolved, Object scopeName, Type requiredServiceType, Request preResolveParent, Object[] args) in /_/src/DryIoc/Container.cs:line 538
   at DryIoc.Container.DryIoc.IResolver.Resolve(Type serviceType, Object serviceKey, IfUnresolved ifUnresolved, Type requiredServiceType, Request preResolveParent, Object[] args) in /_/src/DryIoc/Container.cs:line 466
   at DryIoc.Resolver.Resolve(IResolver resolver, Type serviceType, Object[] args, IfUnresolved ifUnresolved, Type requiredServiceType, Object serviceKey) in /_/src/DryIoc/Container.cs:line 8609
   at My.Framework.Maui.Navigation.Implementations.NavigationService.ResolveView(Type viewModelType, INavigation pageNavigation) in C:\Projects\My.Framework.Maui\Navigation\Implementations\NavigationService.cs:line 245
   at My.Framework.Maui.Navigation.Implementations.NavigationService.PushAsync(Type viewModelType, IEnumerable`1 parameters, ModalType modal, Boolean animated) in C:\Projects\My.Framework.Maui\Navigation\Implementations\NavigationService.cs:line 36

I didn't change anything on the containers rules or such. Is there a new rule or parameter that's now set by default, that I must explicitly disable to get this to work again?

@dadhi
Copy link
Owner

dadhi commented Jun 13, 2024

Hi @cytoph
It is too deep of the stack trace. Could you illustrate with the smaller example?

@dadhi
Copy link
Owner

dadhi commented Jun 13, 2024

Also, from your description:

because the container tries to pass this service instance to every other (undelying) service that is resolved during this call, and not just that, it forces it to be present in every constructor

This behavior is intended and supposed to be working in the v5 and v5.4.

But I am not sure how this behavior leads to the specific exception Error.UnableToResolveFromRegisteredServices
So the smaller example is needed.

@cytoph
Copy link
Author

cytoph commented Jun 18, 2024

I tried to reproduce this with a smaller example, but I'm not able to. All I know is when my project references DryIoc.Microsoft.DependencyInjection 6.0.2 (DryIoc.dll 5.0.2) it works, with DryIoc.Microsoft.DependencyInjection 6.2.0 (DryIoc.dll 5.4.0) it doesn't. I'm still working on an example, but I'm not sure, if I'll be able to provide it.

@dadhi
Copy link
Owner

dadhi commented Jun 25, 2024

@cytoph Could you check if the same issue happens for the latest preview 6.0.0-preview-07?

@dadhi
Copy link
Owner

dadhi commented Aug 29, 2024

@cytoph Did you check with DryIoc v6 preview?

@jods4
Copy link

jods4 commented Feb 27, 2025

For what it's worth, I need this feature (pass explicit values to service ctors) so I did a few tests.
I got identical results from 5.4.3 and 6.0.0-preview-08 (on .net 8).
The simple test below seems to indicate that args is used to fill in ctor dependencies:

  • Across the whole request graph (deep);
  • args can go unused (so is optional);
  • It can be an unregistered service;
  • If it's a registered service than it is used instead of the container version of that service.

@dadhi I was looking for docs on this topic, but I couldn't find any. Is this mentionned somewhere in wiki?

My quick tests:

using DryIoc;

var container = new Container();
container.Register<Dependency>();
container.Register<Service>();
container.Register<Service2>();
container.Register<DeepService>();

// This fails because ManualArg cannot be injected into Service
Console.WriteLine("Inject Service(dep)");
Console.WriteLine("===================");
try
{
    _ = container.Resolve<Service>();
    Console.WriteLine("Should have failed?!");
}
catch (ContainerException ex)
{
    Console.WriteLine(ex.Message);
}

Console.WriteLine();
Console.WriteLine("Inject Service(dep, arg)");
Console.WriteLine("========================");
// This works by passing the `object[] args` parameters down into Service ctor.
var svc = container.Resolve<Service>([new ManualArg()]);
Console.WriteLine(svc.Dependency.Name);
Console.WriteLine(svc.Arg.Name);

Console.WriteLine();
Console.WriteLine("Inject Service2(dep)");
Console.WriteLine("====================");
// This still works, even though arguments ManualArg() turns out to not be required.
var svc2 = container.Resolve<Service2>([new ManualArg()]);
Console.WriteLine(svc2.Dependency.Name);

Console.WriteLine();
Console.WriteLine("Inject DeepService(arg)");
Console.WriteLine("=======================");
// This demonstrates that ManualArg() will be used deep in the resolution graph, too.
var deep = container.Resolve<DeepService>([new ManualArg()]);
Console.WriteLine(deep.Dependency.Name);

Console.WriteLine();
Console.WriteLine("Inject DeepService(local_svc)");
Console.WriteLine("=============================");
// This demonstrates that registered services are also replaced by provided arguments during resolution
var localSvc = new Service(null!, null!);
var customized = container.Resolve<DeepService>([localSvc]);
Console.WriteLine(ReferenceEquals(customized.Service, localSvc));

class Dependency
{
    public string Name => "Dependency";
}

class ManualArg
{
    public string Name => "Arg";
}

class Service(Dependency dep, ManualArg arg)
{
    public Dependency Dependency => dep;
    public ManualArg Arg => arg;
}

class Service2(Dependency dep)
{
    public Dependency Dependency => dep;
}

class DeepService(Service svc)
{
    public Service Service => svc;
    public Dependency Dependency => svc.Dependency;
}

@dadhi
Copy link
Owner

dadhi commented Feb 27, 2025

Noted.

@jods4
Copy link

jods4 commented Feb 28, 2025

I did one more test, because I don't want to use Resolve() but inject a factory Func<Arg, Service>.
It seems to work fine, too:

// Added to my code from previous comment:

Console.WriteLine();
Console.WriteLine("Inject factory Func<ManualArg, Service>");
Console.WriteLine("=======================================");
// This demonstrates a factory that accepts an argument
var fn = container.Resolve<Func<ManualArg, Service>>();
svc = fn(new ManualArg());
Console.WriteLine(svc.Dependency.Name);

@dadhi
Copy link
Owner

dadhi commented Mar 3, 2025

@jods4 After quick check, I did not find the documentation for the args as well.
My miss. I will include the corresponding section into the docs (or will accept the PR for it).

Though you test cases display the design as intended:

  • args are passed down across the whole request graph (deep).
  • args can go unused (so is optional)
  • passed args replace both unregistered and registered service with the matching types

Here is an open issue/feature, adding more control on what services are replaced with args: #222

@jods4
Copy link

jods4 commented Mar 3, 2025

@dadhi No worry!
Please also add a point to indicate that args work in conjunction with factory, e.g. Func<Arg1, Arg2, Service>.
This is not obvious and useful when avoiding the service locator anti-pattern!

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

3 participants