使用Simple Injector将特定的值注入到特定的构造函数参数中

本文关键字:构造函数 参数 注入 Injector Simple 使用 | 更新日期: 2023-09-27 18:15:02

我有一些依赖关系,可以通过配置文件在字段中修改,我正试图指定一些构造函数参数。

我有一个模块(它将被注册到Simple Injector容器中),构造函数应该总是包含systemId的Guid和设置的字典。它可能包含其他项目的构造函数,如ILogger等:

public interface IMonitor
{
    void Start();
    void Stop();
}
public class DevLinkMonitor : IMonitor
{
    public DevLinkMonitor(Guid systemId, Dictionary<string,string> settings)
    {
        // store the injected parameters
    }
    public void Start() {}
    public void Stop() {}
}

然后我有一个类来管理监视器对象的构造:

_monitorManager.Add(
    Guid.NewGuid(),
    "System A London",
    new Dictionary<string, string>()
    {
        { "IpAddress", "192.168.1.2" },
        { "Password", "password" }
    },
    typeof(DevLinkMonitor));
_monitorManager.Add(
    Guid.NewGuid(),
    "System B Mars",
    new Dictionary<string, string>()
    {
        { "IpAddress", "192.168.100.10" },
        { "Password", "password" }
    },
    typeof(DevLinkMonitor));

在监视器对象的构造中,我想注入特定的Guid ID和特定的设置字典(在CreateAndRun()中完成):

public class MonitorManager
{
    internal class Item
    {
        public readonly string Description;
        public readonly Dictionary<string, string> Settings;
        public readonly Type TypeToCreate;
        public IMonitor Instance { get; set; }
        public Item(string description, Dictionary<string, string> settings, Type typeToCreate)
        {
            Description = description;
            Settings = settings;
            TypeToCreate = typeToCreate;
        }
    }
    private readonly Container _container;
    readonly Dictionary<Guid, Item> _list = new Dictionary<Guid, Item>();
    public MonitorManager(Container container)
    {
        _container = container;
    }
    public void Add(Guid id, string description, Dictionary<string, string> settings, Type typeToCreate)
    {
        if(typeToCreate.GetInterfaces().Contains(typeof(IMonitor)))
           throw new ArgumentException($"{typeToCreate} does not implement {typeof(IMonitor)}", nameof(typeToCreate));
        _list.Add(id, new Item(description, settings, typeToCreate));
    }
    public void CreateAndRun()
    {
        foreach (var item in _list)
        {                
            var id = item.Key; // this is the guid we want to inject on construction
            var value = item.Value;
            var settings = value.Settings; // use this settings dictionary value on injection
            // for each below call, somehow use the values id, and settings
            value.Instance = _container.GetInstance(value.TypeToCreate) as IMonitor;
            if (value.Instance == null)
                throw new InvalidCastException($"Does not implement {typeof(IMonitor)} ");
            value.Instance.Start();
        }
    }
    public void Stop()
    {
        foreach (var value in _list.Select(item => item.Value))
        {
            value.Instance.Stop();
        }
    }
}

是否可以使用Simple Injector?还是有人闻到了代码的味道?

使用Simple Injector将特定的值注入到特定的构造函数参数中

我想要SimpleInjector在创建类型时的灵活性,而不是在构造函数中定义契约的代码气味——正如Steven正确地说的,这应该成为IMonitor的一部分。

我不喜欢在Start()中创建的东西是如果Stop()在Start()之前被调用…插件框架当然应该确保这是不可能的,但尽管如此,我认为把null引用检查放在我将要在类中使用的东西上是很好的做法,这会使代码在某种程度上膨胀。

public void Stop()
{
    // although unlikely, we would fail big time if Stop() called before Start()
    if(_someService != null && _fileService != null)
    {
        _someService .NotifyShutdown();
        _fileService.WriteLogPosition();
    }   
}

所以我选择了一个带有工厂方法的插件架构,在工厂中我传递了一个SimpleInjector容器,它可以用来构造任何它喜欢的类型。

public interface IMonitorFactory
{
    void RegisterContainerAndTypes(Container container);
    IMonitor Create(Guid systemId, Dictionary<string,string> settings);
}
public class DevLinkMonitorFactory : IMonitorFactory
{
    private Container _container;
    public void RegisterContainerAndTypes(Container container)
    {
        _container = container;
        // register all container stuff this plugin uses
        container.Register<IDevLinkTransport, WcfTransport>();
        // we could do other stuff such as register simple injector decorators
        // if we want to shift cross-cutting loggin concerns to a wrapper etc etc
    }
    public IMonitor Create(Guid systemId, Dictionary<string,string> settings)
    {           
        // logger has already been registered by the main framework
        var logger = _container.GetInstance<ILogger>();
        // transport was registered previously
        var transport = _container.GetInstance<IDevLinkTransport>();
        var username = settings["Username"];
        var password = settings["Password"];
        // proxy creation and wire-up dependencies manually for object being created
        return new DevLinkMonitor(systemId, logger, transport, username, password);
    }
}

MonitorManager现在有几个额外的任务来连接初始的ContainerRegistration,然后为每个任务调用Create。当然,工厂不需要使用SimpleInjector,但如果主框架提供插件将使用的服务,则会使工作变得容易得多。

由于对象是通过工厂创建的,并且在构造函数中包含所有必需的参数,因此不需要进行大量的空检查。

public interface IMonitor
{
    void Start();
    void Stop();
}
public class MonitorManager
{
    internal class Item
    {
        public readonly string Description;
        public readonly Dictionary<string, string> Settings;
        public IMonitorFactory Factory { get; set; }
        public IMonitor Instance { get; set; }
        public Item(string description, Dictionary<string, string> settings, IMonitorFactory factory)
        {
            Description = description;
            Settings = settings;
            Factory = factory;
        }
    }
    private readonly Container _container;
    readonly Dictionary<Guid, Item> _list = new Dictionary<Guid, Item>();
    public MonitorManager(Container container)
    {
        _container = container;
    }
    // some external code would call this for each plugin that is found and
    // either loaded dynamically at runtime or a static list at compile time
    public void Add(Guid id, string description, Dictionary<string, string> settings, IMonitorFactory factory)
    {
        _list.Add(id, new Item(description, settings, factory));
        factory.RegisterContainerAndTypes(_container);
    }
    public void CreateAndRun()
    {
        foreach (var item in _list)
        {                
            var id = item.Key; // this is the guid we want to inject on construction
            var value = item.Value;
            var settings = value.Settings; // use this settings dictionary value on injection
            var factory = value.Factory;
            // for each below call, somehow use the values id, and settings
            value.Instance = factory.Create(id, settings);
            value.Instance.Start();
        }
    }
    public void Stop()
    {
        foreach (var value in _list.Select(item => item.Value))
        {
            value.Instance?.Stop();
        }
    }
}