我已阅读问题Ioc/DI - Why do I have to reference all layers/assemblies in entry application?
因此,在Asp.Net MVC5解决方案中,组合根位于MVC5项目中(并且负责所有注册的DependencyInjection程序集没有意义)。
在这张图片中,我不清楚以下哪种方法更好。
方法1
具体实现是public class ...
,所有注册子句都集中在组合根中(例如,在CompositionRoot文件夹下的一个或多个文件中)。 MVC5项目必须引用所有程序集,提供至少一个要绑定的具体实现。没有库引用DI库。 MVC项目可以包含没有缺点的接口。
方法2
具体实施是internal class ...
。每个图书馆都会公开DI' local'配置处理程序例如
public class DependencyInjectionConfig {
public static void Configure(Container container) {
//here registration of assembly-provided implementations
//...
}
}
这是注册自己的实现。组合根通过调用所有Configure()
方法触发注册,每个项目只需一个。然后,MVC5项目必须引用所有程序集,提供至少一个要绑定的具体实现。库必须引用DI库。在这种情况下,MVC5项目不能包含接口(否则会有循环引用):需要一个ServiceLayer程序集来保存要绑定的公共接口。
方法3
与方法2相同,但是通过程序集反射动态发现本地配置模块(按照惯例?)。所以MVC5项目还没有引用库。 MVC项目可以包含接口,可以由库引用。图书馆必须引用DI库。
这里的最佳做法是什么?还有其他更好的可能吗?
*编辑(2016-12-22)* 感谢收到的答案,我发表了this github project,描述了我迄今为止找到的最佳解决方案。
*编辑(2018-09-09)* This answer提供了一个有趣的选项。
答案 0 :(得分:3)
我通常喜欢将这些类型的东西封装到每个项目中。例如,我可能有以下内容。 (这是一个非常简单的示例,我将在此示例中使用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 for Dapper,因为每个项目都封装了它自己的实现和注册。
如果我正在添加单元测试并且有一个InMemoryPersonRepository,我将如何为我的InMemoryPersonRepository换出PersonRepository?
假设我们忽略任何业务逻辑层并让MVC Controller直接访问我们的数据访问器,我的代码可能如下所示:
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 Might的测试看起来像:
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));
}
答案 1 :(得分:1)
你发现了一个真正的问题。 (可以说这是一个很好的问题。)如果入境申请A
引用B
,B
引用C
,B
和/或{{ 1}}需要一些DI注册,这使得C
(您的条目应用程序)负责了解A
和B
的详细信息以注册所有依赖项。
解决方案是使用一个单独的程序集来处理C
和B
的所有注册。 C
引用了该文件,它提供了A
需要使用A
和B
的所有容器配置。
好处是
C
对A
和B
的了解不比C
,A
和B
都不得与Unity或Windsor这样的特定DI框架绑定。 Here's an example。这是一个最适合DI容器的事件总线类。但是为了使用它,你不必知道它需要注册的所有依赖项。所以对于温莎我创建了一个C
。你只需致电
DomainEventFacility
并且所有依赖项都已注册。您注册的唯一事项是您的事件处理程序。
然后,如果我想使用与Unity不同的DI容器相同的事件总线库,我可以创建一些类似的程序集来处理Unity的相同配置。