【问题标题】:How to use IoC container (ex: NInject) in this particular context如何在此特定上下文中使用 IoC 容器(例如:NInject)
【发布时间】:2014-03-27 09:21:34
【问题描述】:

我正在创建一个后台任务控制器,如下所示:

public class TaskController
{
    private TaskBase task;
    public TaskController(ITask task)
    {
        this.task = task;
    }

    public void DoSomething()
    {
        task.DoSomething();
    }
}

ITask接口:

interface ITask
{
    void DoSomething();
}

TaskBase抽象类:

public abtract class TaskBase : ITask
{
    \\some common fields/properties/methods   

    public void DoSomething()
    {
        \\perform action here
    }
}

Task 实现:

public class Task1 : TaskBase
{
    public Task1(string arg, int arg1)
    {
    }        
}

public class Task2 : TaskBase
{
    public Task2(bool arg, double arg)
    {
    }
}

这是一个如何使用它的示例:

public void DoTask(string arg, int arg1)
{
    Task1 task = new Task1(arg, arg1);
    TaskController controller = new TaskController(task);
    controller.DoSomething();
}

如您所见,我在这种方法中使用手动注入。现在我想改用像 NInject 这样的 IoC,但是在做了一些研究之后,我仍然有两件事困扰。

1. How can I tell the binding which concrete task to use in particular context?
2. How to pass dynamic arguments (`arg` and `arg1` on above example) to `Bind<T>` method

注意: 如果您认为我的问题值得一票否决,请发表评论,以帮助我避免将来犯错误

【问题讨论】:

    标签: c# ninject ioc-container


    【解决方案1】:

    您遇到的问题是由您的设计引起的。如果你改变你的设计,问题就会消失。您应该做几件事:

    1. 分离数据和行为;目前,您的任务包含一个DoSomething 方法,同时它们还包含它们需要执行的数据。
    2. 相关的,将运行时数据注入组件的构造函数。

    如果您从行为中提取数据,您将获得以下信息:

    // Definition of the data of Task1
    public class Task1Data
    {
        public string Arg;
        public int Arg1;
    }
    
    // The behavior of Task1
    public class Task1 : ITask<Task1Data> {
        public void Handle(TTask1Data data) {
            // here the behavior of this task.
        }
    }
    

    这里每个任务都实现了通用的ITask&lt;TTaskData&gt;接口:

    public interface ITask<TTaskData>
    {
        Handle(TTaskData data);
    }
    

    有了这个设计,我们现在可以按如下方式使用它:

    private ITask<Task1Data> task1;
    
    public Consumer(ITask<Task1Data> task1) {
        this.task1 = task1;
    }
    
    public void DoTask(string arg, int arg1)
    {
        task1.Handle(new Task1Data { Arg = arg, Arg1 = arg1 });
    }
    

    我们注册我们的任务如下:

    kernel.Bind<ITask<Task1Data>>().To<Task1>();
    kernel.Bind<ITask<Task2Data>>().To<Task2>();
    kernel.Bind<ITask<Task3Data>>().To<Task3>();
    

    虽然我对 Ninject 不是很有经验,但我确信有一种方法可以将这些注册转换为方便的单行。

    这种设计有很多优点。例如,它使添加横切关注点变得更加容易。例如,您可以创建一个通用装饰器,将每个任务包装在事务中,如下所示:

    public class TransactionTaskDecorator<T> : ITask<T> {
        private readonly ITask<T> decoratee;
        public TransactionTaskDecorator(ITask<T> decoratee) {
            this.decoratee = decoratee;
        }
    
        public void Handle(T data) {
            using (var scope = new TransactionScope()) {
                this.decoratee.Handle(data);
                scope.Complete();
            }
        }
    }
    

    这样的装饰器可以在消费者不需要知道任何事情的情况下应用,因为它只依赖于ITask&lt;T&gt; 接口。

    您还可以添加一个装饰器,允许在后台线程中执行任务:

    public class BackgroundTaskDecorator<T> : ITask<T> {
        private readonly Func<ITask<T>> decorateeFactory;
        private readonly ILogger logger;
        public TransactionTaskDecorator(Func<ITask<T>> decorateeFactory, ILogger logger) {
            this.decorateeFactory = decorateeFactory;
            this.logger = logger;
        }
    
        public void Handle(T data) {
            Task.Factory.StartNew(() =>
            {
                try {
                    // We're running on a different thread, so we must create the task here.
                    var decoratee = this.decorateeFactory.Invoke();
                    decoratee.Handle(data);
                } catch (Exception ex) {
                    this.logger.Log(ex);
                }
            }
        }
    }
    

    您可以通过here了解更多有关此设计的信息。

    【讨论】:

    • 还没有尝试过,但从外观上我可以看出这解决了我的两个问题。非常感谢史蒂文先生
    【解决方案2】:

    1) 像这样使用命名属性

     public TaskController([Named("MyName")] ITask task)
    

    然后在 NinjectModule 中

    Bind<ITask>().To<Task1>().Named("MyName");
    

    2)认为你可以使用与上面相同的方法

    https://github.com/ninject/ninject/wiki/Contextual-Binding

    【讨论】:

    • 这将限制我使用ITask 的一个混凝土和一个TaskController。如果我有多个Task,我必须创建更多控制器,例如:public TaskController([Named("MyName")] ITask task), public TaskController([Named("MyName1")] ITask task) 并绑定Bind&lt;ITask&gt;().To&lt;Task1&gt;().Named("MyName"); Bind&lt;ITask&gt;().To&lt;Task2&gt;().Named("MyName1");,这不是一个好方法
    • 如果您的目的实际上是在不同时间向同一个控制器注入不同的任务,那么您只需将该逻辑放入容器中。即 if(somecontex) 绑定 x; else 绑定 y;
    • 您的意思是用Factory 类包装容器?
    • 您会获得指定绑定的 ninject 模块。所以是的,有一个工厂可以根据上下文返回一个模块。
    猜你喜欢
    • 1970-01-01
    • 2013-08-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-23
    相关资源
    最近更新 更多