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

Delegate factory with parameter name 'value' throws #1275

Closed
msv2017 opened this issue May 13, 2021 · 2 comments · Fixed by #1276
Closed

Delegate factory with parameter name 'value' throws #1275

msv2017 opened this issue May 13, 2021 · 2 comments · Fixed by #1276
Labels

Comments

@msv2017
Copy link

msv2017 commented May 13, 2021

Describe the Bug

Delegate factory throws on instance activation when it has parameter with name value

Steps to Reproduce

public interface ISomeService { }
public class SomeService : ISomeService { }

public class MyComponent
{
    public ISomeService Service { get; set; }
    public delegate MyComponent Factory(string value);

    public MyComponent(string value)
    {
    }
}

public class ReproTest
{
    [Fact]
    public void Test()
    {
        // Arrange
        var builder = new ContainerBuilder();
        builder.RegisterType<SomeService>().As<ISomeService>();
        builder.RegisterType<MyComponent>().PropertiesAutowired();
        var container = builder.Build();

        // Act
        var factory = container.Resolve<MyComponent.Factory>();

        // Assert
        Assert.NotNull(factory("Autofac is great!"));
    }
}

Expected Behavior

Delegate factory should produce properly activated component with Service property resolved.

Exception with Stack Trace

Autofac.Core.DependencyResolutionException : An exception was thrown while activating UnitTests.MyComponent.
---- System.InvalidCastException : Unable to cast object of type 'System.String' to type 'UnitTests.ISomeService'.

Dependency Versions

Autofac: 6.2.0

Additional Info

What I've tried so far:

  • changing delegate factory parameter name to something else fixed the issue
  • changing parameter type to other type (e.g. string, object) without changing the name still throws

So, to work around the issue, delegate factory should not have parameter with name value.

@tillig
Copy link
Member

tillig commented May 13, 2021

I am able to reproduce the issue using the above code.

The full exception with stack trace is:

Autofac.Core.DependencyResolutionException: An exception was thrown while activating AutofacRepro.UnitTest1+MyComponent.
 ---> System.InvalidCastException: Unable to cast object of type 'System.String' to type 'ISomeService'.
   at Autofac.Core.Activators.Reflection.AutowiringPropertyInjector.CallPropertySetter[TDeclaringType,TValue](Action`2 setter, Object target, Object value)
   at Autofac.Core.Activators.Reflection.AutowiringPropertyInjector.InjectProperties(IComponentContext context, Object instance, IPropertySelector propertySelector, IEnumerable`1 parameters)
   at Autofac.Builder.RegistrationBuilder`3.<>c__DisplayClass41_0.<PropertiesAutowired>b__0(ResolveRequestContext ctxt, Action`1 next)
   at Autofac.Core.Resolving.Middleware.DelegateMiddleware.Execute(ResolveRequestContext context, Action`1 next)
   at Autofac.Core.Resolving.Pipeline.ResolvePipelineBuilder.<>c__DisplayClass14_0.<BuildPipeline>b__1(ResolveRequestContext ctxt)
   at Autofac.Core.Resolving.Middleware.ActivatorErrorHandlingMiddleware.Execute(ResolveRequestContext context, Action`1 next)
   --- End of inner exception stack trace ---
   at Autofac.Core.Resolving.Middleware.ActivatorErrorHandlingMiddleware.Execute(ResolveRequestContext context, Action`1 next)
   at Autofac.Core.Resolving.Pipeline.ResolvePipelineBuilder.<>c__DisplayClass14_0.<BuildPipeline>b__1(ResolveRequestContext ctxt)
   at Autofac.Core.Pipeline.ResolvePipeline.Invoke(ResolveRequestContext ctxt)
   at Autofac.Core.Resolving.Middleware.RegistrationPipelineInvokeMiddleware.Execute(ResolveRequestContext context, Action`1 next)
   at Autofac.Core.Resolving.Pipeline.ResolvePipelineBuilder.<>c__DisplayClass14_0.<BuildPipeline>b__1(ResolveRequestContext ctxt)
   at Autofac.Core.Resolving.Middleware.SharingMiddleware.Execute(ResolveRequestContext context, Action`1 next)
   at Autofac.Core.Resolving.Pipeline.ResolvePipelineBuilder.<>c__DisplayClass14_0.<BuildPipeline>b__1(ResolveRequestContext ctxt)
   at Autofac.Core.Resolving.Pipeline.ResolvePipelineBuilder.<>c__DisplayClass14_0.<BuildPipeline>b__1(ResolveRequestContext ctxt)
   at Autofac.Core.Resolving.Middleware.CircularDependencyDetectorMiddleware.Execute(ResolveRequestContext context, Action`1 next)
   at Autofac.Core.Resolving.Pipeline.ResolvePipelineBuilder.<>c__DisplayClass14_0.<BuildPipeline>b__1(ResolveRequestContext ctxt)
   at Autofac.Core.Pipeline.ResolvePipeline.Invoke(ResolveRequestContext ctxt)
   at Autofac.Core.Resolving.ResolveOperation.GetOrCreateInstance(ISharingLifetimeScope currentOperationScope, ResolveRequest request)
   at Autofac.Core.Resolving.ResolveOperation.ExecuteOperation(ResolveRequest request)
   at Autofac.Core.Resolving.ResolveOperation.Execute(ResolveRequest request)
   at Autofac.Core.Lifetime.LifetimeScope.ResolveComponent(ResolveRequest request)
   at lambda_method(Closure , String )
   at AutofacRepro.UnitTest1.Repro() in /Users/tillig/dev/autofac/_Repro/UnitTest1.cs:line 24

The exception source is actually in the property injector, and, sure enough, if you remove PropertiesAutowired then the name value doesn't matter at all. So there's something weird in the interaction of delegate factories and the property injection thing that is causing an issue.

Which makes a little more sense, as value is a reserved word when it comes to property getter/setter terminology.

@tillig tillig added the bug label May 13, 2021
@tillig
Copy link
Member

tillig commented May 13, 2021

I figured out the issue and laughed out loud because it totally makes sense.

In the AutoWiringPropertyInjector, each property getting autowired goes through some logic where it grabs the list of parameters available and checks to see if they can supply the instance that should be set on the property.

There are, of course, two ways a parameter can match:

  • By type, so the parameter type matches the property type and that's considered a match.
  • By name, which is mostly for constructor matching, but in the case of property setters, the input parameter name in a property setter is value, so... any parameter that's named value will be considered a match for the property setter input.

Basically, the delegate factory is passing in a named parameter value="Autofac is great!" and the property injector is like, "Oh, value is the name of the parameter to this set method, so... it matches!"

The 99.9% case is that parameters getting passed in here are typed parameters, so it's safe to match like this. However, this little thing seems like a special case we'll just have to handle due to the reserved word overlap.

tillig added a commit that referenced this issue May 13, 2021
alistairjevans added a commit that referenced this issue May 14, 2021
Fix #1275: Filter out 'value' named parameters during property injection
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants