简单注入器 属性 注入

Simple Injector Property Injection

如何使用 Simple Injector 执行 属性 注入。

您使用 Ninject 执行的操作如下所示:

[Inject]
public IUnitOfWork UnitOfWork { get; set; }

如何使用 Simple Injector 执行与此等效的操作。我尝试在网上找到解决方案,但没有成功。

为什么要使用属性注入?

我想使用 属性 注入在我的基本控制器中设置工作单元,以便它将创建一个新的工作单元 OnActionExecuting 并提交更改 OnResultExecuted。这也意味着我不必为通过构造函数创建的每个新控制器传入 UoW。

需要创建以下内容:

首先创建属性class

[System.AttributeUsage(System.AttributeTargets.Property]
public class Inject : Attribute
{
}

然后创建自定义 属性 行为

class PropertySelectionBehavior<TAttribute> : IPropertySelectionBehavior
where TAttribute : Attribute
{
    public bool SelectProperty(Type type, PropertyInfo prop)
    {
        return prop.GetCustomAttributes(typeof(TAttribute)).Any();
    }
}

最后告诉容器使用自定义行为

container.Options.PropertySelectionBehavior = new PropertySelectionBehavior<Inject>();

剩下要做的就是用属性

装饰 属性
[Inject]
public IUnitOfWork UnitOfWork { get; set; }

另一种选择是使用RegisterInitializer方法:

container.RegisterInitializer<BaseControllerType>(controller =>
{
   controller.UnitOfWork = container.GetInstance<IUnitOfWork>();
}

它将所有配置保存在您的组合根中,并且不会用各种属性污染您的代码库。

更新:(如约)

虽然这是对您问题的直接回答,但我必须为您提供更好的选择,因为出于多种原因,为此使用基数 class 在 IMO 中不是正确的设计。

  1. 抽象的 classes 可以变成真正的 PITA classes,因为它们倾向于向神 class 发展,其中有各种横切问题
  2. 抽象 class,尤其是与 属性 注入一起使用时,隐藏了所需的依赖项。

重点关注第2点。当你想对一个继承自基础控制器的控制器进行单元测试时,你无法知道这个控制器依赖于IUnitOfWork。你可以通过使用构造函数注入而不是 属性 注入来解决这个问题:

protected abstract class BaseController : Controller
{
    protected readonly IUnitOfWork uoW;
    protected BaseController (IUnitOfWork uoW)
    {
        this.uoW = uoW;
    }

}
public class SomeController : BaseController
{
    public SomeController(IUnitOfWork uoW) : base(uoW) { }
}

虽然这解决了第 2 点,但第 1 点仍然存在。正如您所说,您想要这样做的主要原因是您不想在每个 Action 方法中提交您的更改。当请求完成时,更改必须仅由上下文保存。并且以这种方式思考设计是一件好事,因为 Saving changes 是,或者可以看作是 cross cutting concern and the way you're implementing this is more or less known as AOP

如果涉及到 AOP, especially if you're working with atomic actions in the action methods of your controllers, there is a far better, more SOLID 和更灵活的设计可能会很好地处理这个问题。

我指的是 Command/Handler 模式,该模式在 here (also read this for the query part of your application) 中有详细描述。

使用此模式,您不会注入通用 IUnitOfWork 抽象,而是注入特定需要的 ICommandHandler<TCommand> 抽象。

操作方法将为此特定操作触发负责的命令处理程序。所有命令处理程序都可以简单地由单个 open-generic SaveChangesCommandHandlerDecorator、'ValidationDecorator'、'CheckPermissionsDecorator' 等装饰...

一个简单的例子:

public class MoveCustomerCommand
{
    public int CustomerId;
    public Address NewAddress;
}

public class MoveCustomerCommandHandler : ICommandHandler<MoveCustomerCommand>
{
    public void Handle(MoveCustomerCommand command)
    {
        // retrieve customer from database
        // change address
    }
}

public class SaveChangesCommandHandlerDecorator<TCommand> : ICommandHandler<TCommand>
{
    private readonly ICommandHandler<TCommand> decoratee;
    private readonly DbContext db;

    public SaveChangesCommandHandlerDecorator(
           ICommandHandler<TCommand> decoratee, DbContext db)
    {
        this.decoratee = decoratee;
        this.db = db;
    }

    public void Handle(TCommand command)
    {
        this.decoratee.Handle(command);
        this.db.SaveChanges();
    }
}

// Register as 
container.Register(typeof(ICommandHandler<>), new []{Assembly.GetExecutingAssembly() });
container.RegisterDecorator(typeof(ICommandHandler<>), 
                       typeof(SaveChangesCommandHandlerDecorator<>));

// And use in controller as
public ActionResult MoveCustomer(int customerId, Address address)
{
     var command = new MoveCustomerCommand 
                   { CustomerId = customerId, Address = address };
     this.commandHandler.Handle(command);

     return View(new ResultModel());
}

这可以让您的控制器保持干净并让它做它必须做的事情,即成为业务逻辑(在本例中为 commandhandler 实现)和视图之间的层。