如何在这个特定的上下文中使用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
注意:如果你认为我的问题值得否定,请留下你的评论,以帮助我避免在未来犯错误
你的问题是由你的设计引起的。如果你改变你的设计,问题就会消失。以下是你应该做的几件事:
- 分离数据和行为;目前,你的任务包含一个
DoSomething
方法,而他们也包含他们需要执行的数据。 - 并且,将运行时数据注入到组件的构造函数中。
如果您从行为中提取数据,您将得到以下内容:
// 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