Search code examples
c#inversion-of-controlautofac

C#, Autofac: NamedParameter is not provided in Register<>


I'm trying some autofac features at the moment and are struggling with the NamedParameter.

I created a very simple example, where IFoo is realized by HappyFoo and SadFoo. IFooUser expects an IFoo object in the constructor and uses it to print something to the console.

The code below shows how I'm trying to build a IFooUser object which shall use an IFoo object based on a 'score' value.

        var builder = new ContainerBuilder();

        builder.RegisterType<NormalFooUser>().As<IFooUser>();

        builder.Register<IFoo>((context, parameters) =>
        {
            var score = parameters.Named<int>("score"); // ! Exception !
            if (score >= 100)
            {
                return new HappyFoo();
            } 
            return new SadFoo();
        });

        var container = builder.Build();

        using (var scope = container.BeginLifetimeScope())
        {
            scope.Resolve<IFooUser>(new NamedParameter("score", 90)).useFoo();
        }

Unfortunately when I execute this code, the parameters sequence doesn't hold any values => Exception! When I resolve to IFooeverything works fine:

       scope.Resolve<IFoo>(new NamedParameter("score", 90)).foo(); // Works

But this is not what I want to do. So I'm wondering how I can construct a IFooUser with an IFoo object corresponding to the 'score' with the NamedParameter.

The complete code-sample can be found here as gist

Thanks.


Solution

  • NamedParameter is intended to configure the requested component and not the whole dependency graph, the parameter is not propagated through the whole registration process it is not a context parameter.

    If you want to have a context you can implement such a component which will store some properties.

    // interface 
    interface IContext {
        T Get<T>(String name); 
        void Set<T>(String name, T value); 
    }
    
    // registration 
    builder.RegisterType<FooContext>().As<IContext>();
    builder.Register<IFoo>(c =>
    {
        var score = c.Resolve<IContext>().Get<Int32>("score"); 
        if (score >= 100)
        {
            return new HappyFoo();
        }
        return new SadFoo();
    });
    
    
    // usage 
    using (ILifetimeScope scope = container.BeginLifetimeScope())
    {
        scope.Resolve<IContext>().Set("score", 90); 
        scope.Resolve<IFooUser>().useFoo();
    }
    

    The advantage of this approach is that you can set the context property whenever you want in your current lifetime scope. You can also imagine implementation without Set method which will retrieve value based on whatever you want.

    Another way of doing the same thing is by using a Func<Int32, IFoo> dependency and the TypedAs methods

    builder.Register<IFoo>((c, p) =>
    {
        Int32 score = p.TypedAs<Int32>(); 
        if (score >= 100)
        {
            return new HappyFoo();
        }
        return new SadFoo();
    });
    

    and of course you have to change the IFooUser interface and implementation

    public class NormalFooUser : IFooUser
    {
        private readonly Func<Int32, IFoo> _fooBuilder;
    
        public NormalFooUser(Func<Int32,IFoo> fooBuilder)
        {
            _fooBuilder = fooBuilder;
        }
    
        public void useFoo(Int32 score)
        {
            _fooBuilder(score).foo();
        }
    }