Search code examples
c#dependency-injectioninversion-of-controlcastle-windsor

Castle Winsdor - Propagating dependency passed via typed factory


I am currently getting familiar with IoC and having difficulties regarding refactoring of an existing library using IoC principles.

Let's say I have three classes: Controller, Handler and Settings and a typed factory for Controller.

  • Controller is an entry point of the library

    public class Controller
    {
        public Settings Settings { get; }
        public Handler  Handler  { get; }
    
        public Controller(Settings settings, Handler handler)
        {
            Settings = settings;
            Handler  = handler;
        }
    }
    
  • Handler is a dependency of Controller

    public class Handler
    {
        public Settings Settings { get; }
    
        public Handler(Settings settings)
        {
            Settings = settings;
        }
    }
    
  • Settings is a class containing some library-wide settings'

    public class Settings
    {
        public int Revision { get; set; }
    }
    
  • IControllerFactory is a typed factory

    public interface IControllerFactory
    {
        Controller Create(Settings settings);
    }
    

I want to initialize a library. For sake of simplicity, all example code is in a single Main method. In real world application, consumer of Controller class does not have access to container.

static void Main(string[] args)
{
    //create container and register components
    var container = new WindsorContainer();
    container.AddFacility<TypedFactoryFacility>();
    container.Register(
        Component.For<Controller>().LifestyleTransient(),
        Component.For<Settings>().LifestyleBoundTo<Controller>(),
        Component.For<Handler>().LifestyleBoundTo<Controller>()
    );
    container.Register(
        Component.For<IControllerFactory>().AsFactory()
    );

    //in real application, factory is a dependency of a library consumer class 
    //which has no access to container
    var controllerFactory = container.Resolve<IControllerFactory>();

    //create Controller instance with Revision setting set to 100
    var settings = new Settings()
    {
        Revision = 100
    };
    var controller = controllerFactory.Create(settings);

    //check revision value for controller and handler
    Console.WriteLine("Controller's setting revision: " + controller.Settings.Revision);         //Controller's setting revision: 100
    Console.WriteLine("Handler's setting revision: "    + controller.Handler.Settings.Revision); //Handler's setting revision: 0
    Console.ReadKey();
}

Running this example outputs the following:

Controller's setting revision: 100
Handler's setting revision: 0

As you can see, Settings instance which is passed as an argument to a factory is correctly passed to Controller constructor but does not propagate to Controller's dependencies (i.e. Handler constructor). I couldn't find any information on whether it is an intended behaviour.


If arguments of typed factories are actually non-propagatable, what approach would you suggest in my situation? Creating custom scope is one, but doesn't suite my needs because it requires access to the container, which as far as I understand is considered a bad practice.

static void Main(string[] args)
{
    //create container and register components
    var container = new WindsorContainer();
    container.AddFacility<TypedFactoryFacility>();
    container.Register(
        Component.For<Controller>().LifestyleScoped(),
        Component.For<Settings>().LifestyleScoped(),
        Component.For<Handler>().LifestyleBoundTo<Controller>()
    );

    //creating scope means passing container around
    using (container.BeginScope()) 
    {
        //create instance of controller
        var settings = container.Resolve<Settings>();
        settings.Revision = 100;
        var controller = container.Resolve<Controller>();

        //check revision value for controller and handler
        Console.WriteLine("Controller's setting revision: " + controller.Settings.Revision);         //Controller's setting revision: 100
        Console.WriteLine("Handler's setting revision: "    + controller.Handler.Settings.Revision); //Handler's setting revision: 100
    }

    Console.ReadKey();
}

Running this example gives a desired result:

Controller's setting revision: 100
Handler's setting revision: 100

Solution

  • Arguments passed to a factory will not be added to the container - they will only be used to satisfy dependencies of the required service. If you wish those arguments to be passed down to other dependencies you must take responsibility for doing this yourself.

    The general rule-of-thumb that referencing the container is bad practice is correct. However, like all rules-of-thumb it has limits.

    (The actual variability of Settings is not clear from your question so I am assuming your implied requirement, that it can vary in an unspecified way, is the key driver here. However, answering your question about "what approach would you suggest" is difficult without properly understanding the real variability of Settings)

    i) With your current design, one way or another you need to augment the behavior of the container to fulfill your requirements as explained and you must, therefore, access the container.

    As you have already highlighted, one solution would be to use custom scopes. This provides great flexibility in controlling object life cycle but does leak the container reference in a fairly dangerous way. To avoid this, you can encapsulate this to prevent leakage of the container, and you will end up with a custom factory and a partner scope class (which may simply be an IDisposable as far as clients are concerned). In this scenario, I regard this as an augmentation of the container's baseline functionality and therefore the rule-of-thumb does not apply.

    ii) Another approach would be to rethink your design a little and introduce some indirection to the referencing of Settings. i.e. instead of Handler and Controller having a dependency on Settings, they could have a dependency on SettingsProvider. SettingsProvider could then be added to the container as a singleton and the required logic about how to access the currently applicable Settings could be managed completely independently of the container.

    (See this excellent explanation of the benefits of factories in Castle Windsor for further details)