[英]Where dependency-injection registrations have to be put?
我已经阅读了问题Ioc/DI - 为什么我必须引用应用程序入口点中的所有层/程序集?
因此,在 Asp.Net MVC5 解决方案中,组合根位于 MVC5 项目中(并且拥有负责所有注册的 DependencyInjection 程序集没有意义)。
在这张图片中,我不清楚以下哪种方法更好。
方法一
具体实现是public class ...
并且所有注册子句都集中在组合根中(例如,在 CompositionRoot 文件夹下的一个或多个文件中)。 MVC5 项目必须引用所有提供至少一个要绑定的具体实现的程序集。 没有库引用 DI 库。 MVC 项目可以包含要绑定的接口,没有任何缺点。
方法二
具体实现是internal class ...
。 每个库都公开一个 DI“本地”配置处理程序。 例如
public class DependencyInjectionConfig {
public static void Configure(Container container) {
//here registration of assembly-provided implementations
//...
}
}
这取决于注册自己的实现。 组合根通过调用所有Configure()
方法来触发注册,每个项目只调用一个方法。 然后 MVC5 项目必须引用所有程序集,提供至少一个要绑定的具体实现。 库必须引用 DI 库。 在这种情况下,MVC5 项目不能包含接口(否则会出现循环引用):需要一个 ServiceLayer 程序集来保存要绑定的公共接口。
方法三
与方法 2 相同,但本地配置模块是通过程序集反射动态发现的(按惯例?)。 所以MVC5项目没有引用库。 MVC 项目可以包含接口并且可以被库引用。 库必须引用 DI 库。
这里的最佳做法是什么? 还有其他更好的可能吗?
编辑 1 (2016-12-22)感谢收到的答案,我发布了这个 github 项目,描述了我迄今为止找到的最佳解决方案。
编辑 2 (2018-09-09) 这个答案提供了一个有趣的选项。
EDIT 3 (2020-12-29)最后,我想出了一个完整的解决方案,以 WebApi 应用程序模板的形式打包。 我在 GitHub HERE上发布了这个解决方案。 这种方法不仅可以清楚地了解 DI 规则必须放在哪里,而且还建议根据 SOLID 原则和 CQRS 模式设置应用程序。 该项目的提交历史已被构建为具有教育目的。
我通常喜欢将这些类型的东西封装到每个项目中。 例如,我可能有以下内容。 (这是一个极其简化的示例,我将在此示例中使用AutoFac ,但我想所有 DI 框架都具有以下内容)。
仅用于 POCO 和接口的公共区域。
// MyProject.Data.csproj
namespace MyProject.Data
{
public Interface IPersonRepository
{
Person Get();
}
public class Person
{
}
}
存储库和数据访问的实现
// MyProject.Data.EF.csproj
// This project uses EF to implement that data
namespace MyProject.Data.EF
{
// internal, because I don't want anyone to actually create this class
internal class PersonRepository : IPersonRepository
{
Person Get()
{ // implementation }
}
public class Registration : Autofac.Module
{
protected override void Load(ContainerBuilder builder)
{
builder.Register<PersonRepository>()
.As<IPersonRepository>()
.IntancePerLifetimeScope();
}
}
}
消费者
// MyPrject.Web.UI.csproj
// This project requires an IPersonRepository
namespace MyProject.Web.UI
{
// Asp.Net MVC Example
internal class IoCConfig
{
public static void Start()
{
var builder = new ContainerBuilder();
var assemblies = BuildManager.GetReferencedAssemblies()
.Cast<Assembly>();
builder.RegisterAssemblyModules(assemblies);
}
}
}
所以依赖关系看起来像:
MyProject.Data.csproj
- None
MyProject.Data.EF.csproj
- MyProject.Data
MyProject.Web.UI.csproj
- MyProject.Data
- MyProject.Data.EF
在此设置中,Web.UI 无法了解已注册的内容或原因。 它只知道 EF 项目有实现但无法访问它们。
我可以非常轻松地将 EF 删除为 Dapper,因为每个项目都封装了它自己的实现和注册。
如果我添加单元测试并有一个 InMemoryPersonRepository,我将如何将 PersonRepository 换成我的 InMemoryPersonRepository?
假设我们忽略任何业务逻辑层并让 MVC 控制器直接访问我们的数据访问器,我的代码可能如下所示:
public class MyController
{
private readonly IPersonRepository _repo;
public MyController(IPersonRepository repo)
{
_repo = repo;
}
public IActionResult Index()
{
var person = _repo.Get();
var model = Map<PersonVM>(person);
return View(model);
}
}
然后使用 nSubstitute 的测试可能看起来像:
public class MyControllerTests
{
public void Index_Executed_ReturnsObjectWithSameId
{
// Assign
var repo = Substitute.For<IPersonRepository>();
var expectedId = 1;
repo.Get().Returns(new Person { Id = expected });
var controller = new MyController(repo);
// Act
var result = controller.Index() as ActionResult<PersonVM>;
// Assert
Assert.That(expectedId, Is.EqualTo(result.Value.Id));
}
你发现了一个真正的问题。 (可以说这是一个很好的问题。)如果报名申请A
引用B
, B
引用C
,并且B
和/或C
需要一些 DI 注册,这使得A
(您的报名申请)负责了解足够的细节B
和C
来注册所有的依赖。
解决方案是使用一个单独的程序集来处理组合B
和C
所有注册。 A
引用了它,它提供了A
需要使用B
和C
所有容器配置。
好处是
A
对B
和C
了解并不多于它应该知道的A
、 B
和C
都不必绑定到一个特定的 DI 框架,如 Unity 或 Windsor。 这是一个例子。 这是一个最适用于 DI 容器的事件总线类。 但是为了使用它,您不必了解它需要注册的所有依赖项。 所以对于 Windsor 我创建了一个DomainEventFacility
。 你只要打电话
_container.AddFacility<DomainEventFacility>();
并且所有依赖项都已注册。 您唯一注册的是您的事件处理程序。
然后,如果我想将相同的事件总线库与不同的 DI 容器(如 Unity)一起使用,我可以创建一些类似的程序集来处理 Unity 的相同配置。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.