How can I use an existing instance to select the type to create in the IoC container

this is probably just a beginners 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) { ... } } 

A FooSettings or BarSettings instance is provided from an external source ie:

 object settings = GetSettings(); 

And now, I would like to enable ProcessorConsumer based on entering an existing settings instance, for example:

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

That is, if an instance of FooSettings is provided, then the FooProcessor is created and entered into the ProcessorConsumer, which is then resolved by the instance.

I was not able to figure out how to do this in StructureMap, Ninject, or Autofac ... perhaps because I am new when it comes to IoC containers. Thus, the answers to all these or those containers, so that they can be compared, were highly appreciated.

UPDATE: I am looking for a solution that easily allows you to add new settings and processors. A unidirectional mapping from the settings type to the processor type will also be displayed. But it also allows you to enter other instances / services in a particular type of processor based on its constructor settings. That is, for some processors, the IResourceProvider service or similar may be required. Here is an example.

Ideally, I would like something like

 container.For<IProcessor>.InjectConstructorParameter(settings) 

or similar. Thus, directing the IoC container to use the processor type corresponding to the inclinated instance of the constructor parameter.

+6
source share
7 answers

I think you are looking for the ForObject() method in StructureMap. It can close an open generic type based on a specific instance of an object. The key change that needs to be made to your design is to introduce a generic type:

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

All important things are still declared on the IProcessor , the general IProcessor<TSettings> is really a token interface. Each of your processors then implements a common interface to declare what type of settings 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 the instance of the settings object, you can get the correct IProcessor :

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

Now you can tell StructureMap to use this logic whenever it allows 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>(); }); }); 
+1
source

You do not need dependency injection for this. You want a factory (which, of course, can be created using your container). factory will know how to take, say, IProcessorSettings and return the corresponding IProcessor . In short, you can build a factory that uses a specific type of object that implements IProcessorSettings , and a container to allow an instance of the corresponding type.

+6
source

StructureMap containers provide a Model property that allows you to query the instances that 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>(); }); }); 
+1
source

In Autofac:

 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 work is carried out:

  // 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 it rather cumbersome and hoped that something else was built into the IoC container.

+1
source

Now I'm not saying that this is the right way to do this. However, this may be another option if you are using Autofac. This assumes that you are happy that the registration delegate will call GetSettings where you are trying and enable IProcessorConsumer . If you are able to do this, you can do what you want in the registration, as shown 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 incorrect as I cannot try it right now.

0
source

This is as close as possible to the correct factory method. But there are some problems. First, here is 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 problem? These are the different types of settings that you provide to your factory method. Sometimes it's FooSettings, and sometimes BarSettings. Later it may be xxxSettings. Each new type will force recompilation. If you have a common class of settings, this is not so.

Another problem? Your consumer gets the factory and settings and uses this to get the right processor. If you have someone passing them to your consumer, just ask this object to call GetProcessor in the factory and pass the received IProcessor to the user.

0
source

I think the problem is that you did not actually indicate in any structured way how to allow the processor from the settings instance. How to force the settings object to return the processor? This seems to be a suitable place to post this information:

 public interface ISettings { IProcessor GetProcessor(); } 

Each implementation must decide 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 that requires a processor gets it from the settings object, which you can reference to the user’s constructor:

 var consumer = new ProcessorConsumer(Settings.GetProcessor()); 
0
source

Source: https://habr.com/ru/post/890755/


All Articles