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

本文关键字:容器 IoC 例如 NInject 上下文 | 更新日期: 2023-09-27 17:54:27

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

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

注意:如果你认为我的问题值得否定,请留下你的评论,以帮助我避免在未来犯错误

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

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

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

你可以在这里了解更多关于这个设计

1)使用如下命名属性

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

然后在

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

我认为你可以使用和上面一样的方法

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