Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can one use an existing instance to select a type to create in an IoC container

this is probably just a newbie question, but I have the following:

public class FooSettings {}
public class BarSettings {}
public class DohSettings {}
// There might be many more settings types...

public interface IProcessor { ... }

public class FooProcessor
    : IProcessor
{
     public FooProcessor(FooSettings) { ... }
}

public class BarProcessor
    : IProcessor
{
     public BarProcessor(BarSettings) { ... }
}

public class DohProcessor
    : IProcessor
{
     public DohProcessor(DohSettings) { ... }
}

// There might be many more processor types with matching settings...

public interface IProcessorConsumer {}

public class ProcessorConsumer 
    : IProcessorConsumer
{
     public ProcessorConsumer(IProcessor processor) { ... }
}

An instance of either FooSettings or BarSettings is provided from an external source i.e.:

object settings = GetSettings();

And now I would like to resolve ProcessorConsumer based on injecting the existing instance of settings e.g.:

container.RegisterAssemblyTypes(...); // Or similar
container.Inject(settings);
var consumer = container.Resolve<IProcessorConsumer>();

That is if an instance of FooSettings is provided then a FooProcessor is created and injected into the ProcessorConsumer which is then the instance resolved.

I haven't been able to figure out how to do this in either StructureMap, Ninject nor Autofac... probably because I am a newbie when it comes to IoC containers. So answers for all of these or other containers so they can be compared would be highly appreciated.

UPDATE: I am looking for a solution which easily allows for new settings and processors to be added. Also there will be a one-to-on mapping from settings type to processor type. But which also allows for other instances/services to be injected in a given processor type, based on its constructor parameters. I.e. some processor might need a IResourceProvider service or similar. Just an example here.

Ideally, I would like something like

container.For<IProcessor>.InjectConstructorParameter(settings)

or similar. Thereby, guiding the IoC container to use the processor type matching the injected constructor parameter instance.

like image 789
nietras Avatar asked Jun 17 '11 13:06

nietras


2 Answers

You don't want dependency injection for this. You want a factory (which, of course, you can build using your container). The factory would know how to take, say, an IProcessorSettings and return the appropriate IProcessor. In short, you can build a factory that uses the concrete type of an object that implements IProcessorSettings and the container to resolve an instance of the appropriate type.

like image 154
jason Avatar answered Sep 24 '22 19:09

jason


I think what you are looking for is the ForObject() method in StructureMap. It can close an open generic type based on a given object instance. The key change you need to make to your design is to introduce the generic type:

public interface IProcessor { }
public interface IProcessor<TSettings> : IProcessor{}

All of the important stuff is still declared on IProcessor, the generic IProcessor<TSettings> is really just a marker interface. Each of your processors will then implement the generic interface, to declare which settings type they expect:

public class FooProcessor : IProcessor<FooSettings>
{
     public FooProcessor(FooSettings settings) {  }
}

public class BarProcessor : IProcessor<BarSettings>
{
     public BarProcessor(BarSettings settings) {  }
}

public class DohProcessor : IProcessor<DohSettings>
{
     public DohProcessor(DohSettings settings) {  }
}

Now, given an instance of a settings object, you can retrieve the correct IProcessor:

IProcessor processor = container.ForObject(settings).
  GetClosedTypeOf(typeof(IProcessor<>)).
  As<IProcessor>();

Now you can tell StructureMap to use this logic whenever it resolves an IProcessor:

var container = new Container(x =>
{
    x.Scan(scan =>
    {
        scan.TheCallingAssembly();
        scan.WithDefaultConventions();
        scan.ConnectImplementationsToTypesClosing(typeof(IProcessor<>));
    });

    x.For<IProcessor>().Use(context =>
    {
        // Get the settings object somehow - I'll assume an ISettingsSource
        var settings = context.GetInstance<ISettingsSource>().GetSettings();
        // Need access to full container, since context interface does not expose ForObject
        var me = context.GetInstance<IContainer>();
        // Get the correct IProcessor based on the settings object
        return me.ForObject(settings).
            GetClosedTypeOf(typeof (IProcessor<>)).
            As<IProcessor>();
    });

});
like image 27
Joshua Flanagan Avatar answered Sep 25 '22 19:09

Joshua Flanagan