如何使用现有实例选择要在IoC容器中创建的类型

时间:2011-06-17 13:03:17

标签: c# ioc-container ninject structuremap autofac

这可能只是一个新手问题,但我有以下内容:

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) { ... }
}

FooSettings或BarSettings的一个实例是从外部源提供的,例如:

object settings = GetSettings();

现在我想基于注入现有的设置实例来解决ProcessorConsumer,例如:

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

即如果提供了FooSettings的实例,则创建FooProcessor并将其注入ProcessorConsumer,然后解析实例。

我无法弄清楚如何在StructureMap,Ninject和Autofac中做到这一点......可能是因为我是IoC容器的新手。因此,所有这些或其他容器的答案都可以进行比较,我们将非常感激。

更新:我正在寻找一种可以轻松添加新设置和处理器的解决方案。此外,还有从设置类型到处理器类型的一对一映射。但是,它还允许基于其构造函数参数在给定的处理器类型中注入其他实例/服务。即某些处理器可能需要IResourceProvider服务或类似服务。这里只是一个例子。

理想情况下,我想要像

这样的东西
container.For<IProcessor>.InjectConstructorParameter(settings)

或类似的。因此,指导IoC容器使用与注入的构造函数参数实例匹配的处理器类型。

7 个答案:

答案 0 :(得分:6)

您不希望依赖注入。你想要一个工厂(当然,你可以用你的容器建造)。工厂会知道如何采用IProcessorSettings并返回相应的IProcessor。简而言之,您可以构建一个工厂,该工厂使用实现IProcessorSettings的对象的具体类型和容器来解析相应类型的实例。

答案 1 :(得分:1)

StructureMap容器​​公开Model属性,该属性允许您查询它包含的实例。

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

答案 2 :(得分:1)

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

以下作品:

        // 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
        }

但是,我觉得这很麻烦,并且希望在IoC容器中内置更多内容。

答案 3 :(得分:1)

我认为你要找的是StructureMap中的ForObject()方法。它可以基于给定的对象实例关闭开放的泛型类型。您需要对设计进行的关键更改是引入泛型类型:

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

所有重要内容仍然在IProcessor上声明,通用IProcessor<TSettings>实际上只是一个标记界面。然后,每个处理器将实现通用接口,以声明它们期望的设置类型:

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) {  }
}

现在,给定设置对象的实例,您可以检索正确的IProcessor

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

现在,只要解析了IProcessor

,就可以告诉StructureMap使用此逻辑
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>();
    });

});

答案 4 :(得分:0)

现在,我不是说这是正确的方式。但是,如果您使用的是Autofac,则可能是另一种选择。这假设您很高兴注册代表在您尝试并解析GetSettings 时致电IProcessorConsumer 。如果你能够做到这一点,你可以在注册时做你想做的事情,如下所示:

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

注意:此代码可能有误,因为我现在无法尝试。

答案 5 :(得分:0)

这里尽可能接近正确的工厂方法。但是有一些问题。首先,这是代码;然后我们会谈谈。

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

那么问题是什么?这是您为工厂方法提供的不同类型的设置。有时它是FooSettings,有时它是BarSettings。后来,它可能是xxxSettings。每种新类型都会强制重新编译。如果你有一个共同的设置类,那就不是这种情况了。

另一个问题?您的消费者通过工厂和设置并使用它来获得正确的处理器。如果有人将这些传递给您的消费者,只需让该实体在Factory上调用GetProcessor并将生成的IProcessor传递给使用者。

答案 6 :(得分:0)

我认为问题在于您实际上没有以任何结构化方式指定如何从设置实例中解析处理器。如何让设置对象返回处理器?这似乎是提供这些信息的正确位置:

public interface ISettings
{
   IProcessor GetProcessor();
}

每个实现都必须解决自己的处理器实现:

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

任何需要处理器的代码都会从设置对象中获取它,您可以从使用者构造函数中引用它:

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