如何在此特定上下文中使用IoC容器(例如:NInject)

时间:2014-03-27 09:21:35

标签: c# ninject ioc-container

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

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

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

ITask界面:

interface ITask
{
    void DoSomething();
}

TaskBase abtract class:

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

注意: 如果你看到我的问题需要一个downvote,请留下一些评论,以帮助我避免将来犯错误

2 个答案:

答案 0 :(得分:2)

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

  1. 分开的数据和行为;目前,您的任务包含DoSomething方法,同时它们还包含需要执行的数据。
  2. 相关,将运行时数据注入组件的构造函数。
  3. 如果从行为中提取数据,您将获得以下内容:

    // 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<TTaskData>接口:

    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<T>接口。

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

    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

答案 1 :(得分:1)

1)使用像这样的命名属性

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

然后在NinjectModule

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

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

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