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

7

6

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.

Leroi answered 17/6, 2011 at 13:3 Comment(2)
Should public Foo(FooSettings) { ... } be public FooProcessor(FooSettings) { } and public Foo(BarSettings) { ... } be public BarProcessor(BarSettings) { }?Timbal
Possible duplicate question - #6373622Tracheitis
T
1

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>();
    });

});
Tend answered 24/6, 2011 at 15:56 Comment(2)
Yeah, ok that would work, but also adds friction. Having to specify a marker with the settings type for me sounds like unnecessary friction. However, I guess this has some benefits in that the dependency is stated in the interface of the type in question.Leroi
You are trying to tie two types together - the settings type and the processor type. So you are inevitably going to need some way of declaring that relationship. You could probably come up with a custom convention that scanned the IProcessor implementations and checked their constructor parameters for settings type, and then store that information for later use by the container - but I'm betting that would be a lot more work than the marker interface.Tend
T
6

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.

Timbal answered 17/6, 2011 at 13:11 Comment(3)
Well, that just seems like a lot of work for something that should be possible. Could you elaborate on why I do not want dependency injection for this? Also I would imagine one could say something like: container.For<IProcessor>.InjectDependency(settings) to be more specific.Leroi
+1 on the factory. The problem here is that there's really no way for the IoC container to know which type of processor to create. This isn't its job in this situation. The factory would take care of this for you based on conditions that are appropriate for making this determination.Grider
Could any of you give an example of how this would be done exactly? I am not sure I understand...Leroi
T
1

StructureMap containers expose the Model property which allows you to query for the instances it contains.

var container = new Container(x =>
{
    x.For<IProcessorConsumer>().Use<ProcessorConsumer>();
    x.For<IProcessor>().Use(context =>
    {
        var model = context.GetInstance<IContainer>().Model;
        if (model.PluginTypes.Any(t => typeof(FooSettings).Equals(t.PluginType)))
        {
            return context.GetInstance<FooProcessor>();
        }
        return context.GetInstance<BarProcessor>();
    });
});
Tend answered 18/6, 2011 at 23:56 Comment(4)
My answer shows the mechanics of how to do what the question asked. However, I would not consider this normal usage of an IoC container. You may want to ask another question that describes your situation and solicit design advice. For example, explain in what situations you would have a FooSettings vs. a BarSettings?Tend
First thanks for the answer, well approach seems coupled and would make have to change this if new settings were introduced e.g. OtherSettings. Basically, I want to create an object graph based on using existing injected instances that are for example loaded from disk i.e. settings. But where settings usually map to a single specific type.Leroi
What I'm trying to get at is that you probably don't want to base the decision on the presence of a settings type in the container - but rather whatever logic is happening earlier to determine which settings to inject. When and does your application know whether to create a FooSettings instead of a BarSettings?Tend
Joshua and I are hunting for the same thing (see my suggested answer - esp the final comments). The entity that knows whether to create a FooSettings or a BarSettings or a xxxSettings is the entity that should be responsible for constructing the correct types. Look at the "Creator" pattern in the GRASP patterns to see what we mean. en.wikipedia.org/wiki/GRASP_(object-oriented_design)#CreatorMomentarily
L
1

In Autofac given:

public class AcceptsTypeConstructorFinder
    : IConstructorFinder
{
    private readonly Type m_typeToAccept;
    public AcceptsTypeConstructorFinder(Type typeToAccept)
    {
        if (typeToAccept == null) { throw new ArgumentNullException("typeToAccept"); }
        m_typeToAccept = typeToAccept;
    }

    public IEnumerable<ConstructorInfo> FindConstructors(Type targetType)
    {
        return targetType.GetConstructors()
            .Where(constructorInfo => constructorInfo.GetParameters()
                .Select(parameterInfo => parameterInfo.ParameterType)
                .Contains(m_typeToAccept));
    }
}

the following works:

        // Load
        var settings = new BarSettings();
        var expectedProcessorType = typeof(BarProcessor);

        // Register
        var constructorFinder = new AcceptsTypeConstructorFinder(settings.GetType());
        var builder = new ContainerBuilder();
        var assembly = Assembly.GetExecutingAssembly();

        builder.RegisterInstance(settings);

        builder.RegisterAssemblyTypes(assembly)
               .Where(type => type.IsAssignableTo<IProcessor>() && constructorFinder.FindConstructors(type).Any())
               .As<IProcessor>();

        builder.RegisterAssemblyTypes(assembly)
               .As<IProcessorConsumer>();

        using (var container = builder.Build())
        {
            // Resolve
            var processorConsumer = container.Resolve<IProcessorConsumer>();

            Assert.IsInstanceOfType(processorConsumer, typeof(ProcessorConsumer));
            Assert.IsInstanceOfType(processorConsumer.Processor, expectedProcessorType);

            // Run
            // TODO
        }

However, I find this to be rather cumbersome and was hoping for something more built into an IoC container.

Leroi answered 22/6, 2011 at 15:55 Comment(1)
I think you're not going to find anything more built into an IoC container, because that's just not what they were designed for. What IoC container does is that you ask for a type and it resolves all necessary dependencies. What you want is to provide a dependency and let it create some type that depends on it. That's the exact opposite of what IoC container normally does.Nimble
T
1

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>();
    });

});
Tend answered 24/6, 2011 at 15:56 Comment(2)
Yeah, ok that would work, but also adds friction. Having to specify a marker with the settings type for me sounds like unnecessary friction. However, I guess this has some benefits in that the dependency is stated in the interface of the type in question.Leroi
You are trying to tie two types together - the settings type and the processor type. So you are inevitably going to need some way of declaring that relationship. You could probably come up with a custom convention that scanned the IProcessor implementations and checked their constructor parameters for settings type, and then store that information for later use by the container - but I'm betting that would be a lot more work than the marker interface.Tend
T
0

Now, I'm not saying this is the right way to do this. However, it may be another option if you are using Autofac. This assumes that you are happy for the registration delegate to call GetSettings at the point you try and resolve the IProcessorConsumer. If you are able to do that, you can do what you want in the registration, as below:

var cb = new ConatainerBuilder();
cb.Register(c => 
{
    var settings = GetSettings();
    if(settings is FooSettings)
        return new FooProcessor((FooSettings)settings);
    else if(settings is BarSettings)
        return new BarProcessor((BarSettings)settings);
    else
       throw new NotImplementedException("Hmmm. Got some new fangled settings.");

}).As<IProcessor>();

//Also need to register IProcessorConsumer

Note: This code may be wrong as I can't try it right now.

Tymes answered 18/6, 2011 at 18:7 Comment(1)
This would work, but then you could basically just write a factory for processor and inject the processor instance into the container, but I do not want to have a "switch" factory for processors and would like to allow some processors to require more services etc. I guess I would like the settings instance to function as a selector for the processor, but having to create a factory that seems redundant.Leroi
M
0

Here is as close as you can get to a proper factory method. But there are some issues. First, here's the code; then we'll talk.

public class FooSettings
{
    public int FooNumber { get; set; }
    public string FooString { get; set; }
}

public class BarSettings
{
    public int BarNumber { get; set; }
    public string BarString { get; set; }
}


public interface IProcessor
{
    void Process();
}

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

    public void Process() { }
}

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

    public void Process() { }
}


public interface IProcessorFactory
{
    IProcessor GetProcessor(object settings);
}

public interface IProcessorConsumer { }

public class ProcessorConsumer : IProcessorConsumer
{
    private IProcessorFactory _processorFactory;
    private object _settings;

    public ProcessorConsumer(IProcessorFactory processorFactory, object settings)
    {
        _processorFactory = processorFactory;
        _settings = settings;
    }


    public void MyLogic()
    {
        IProcessor processor = _processorFactory.GetProcessor(_settings);

        processor.Process();
    }
}


public class ExampleProcessorFactory : IProcessorFactory
{
    public IProcessor GetProcessor(object settings)
    {
        IProcessor p = null;

        if (settings is BarSettings)
        {
            p = new BarProcessor(settings as BarSettings);
        }
        else if (settings is FooSettings)
        {
            p = new FooProcessor(settings as FooSettings);
        }

        return p;
    }
}

So what's the issue? It's the different types of settings that you're giving to your factory method. Sometimes it's FooSettings and sometimes it's BarSettings. Later, it might be xxxSettings. Each new type will force a recompilation. If you had a common Settings class, this would not be the case.

The other issue? Your consumer gets passed the factory and the settings and uses that to get the correct processor. If you have someone passing these to your consumer, just have that entity call GetProcessor on the Factory and pass the resulting IProcessor to the consumer.

Momentarily answered 23/6, 2011 at 19:45 Comment(1)
A common settings class is an option and is something we use in some of the cases that I am trying to figure out how to solve using an IoC container. However, I am looking for a solution with no hard coded if statements. Also I do not want a factory in the ProcessorConsumer. The processor might be time consuming to create for example, so it should be created by the IoC and injected. Also it is a poor solution and you might as well just write the if statement when setting up the IoC container.Leroi
F
0

I think the issue is that you haven't actually specified in any structured way how to resolve a processor from a settings instance. How about making the settings object return the processor? That seems to be the right place to put this information:

public interface ISettings
{
   IProcessor GetProcessor();
}

Each implementation must resolve its own processor implementation:

public class FooSettings : ISettings
{
   //this is where you are linking the settings type to its processor type
   public IProcessor GetProcessor() { return new FooProcessor(this); }
}

And any code needing a processor gets it from the settings object, which you can reference from the consumer constructor:

var consumer = new ProcessorConsumer(Settings.GetProcessor());
Fiore answered 27/6, 2011 at 17:15 Comment(3)
It was probably obvious from my response, but I don't think an IoC container is the way to resolve the issue (not that it couldn't be done, but it puts the information a long way away from the concerned types).Fiore
I think I specified it very clealy in the constructors of the given processor. Why would I tie the settings to a given processor when instead it is the processor that depends on the settings?Leroi
Your specification requires knowing the answer before you ask it. You need to be able to locate the processor from the settings, not the other way around.Fiore

© 2022 - 2024 — McMap. All rights reserved.