哪些依赖注入注册必须放在哪里?

时间:2016-04-03 14:12:02

标签: c# asp.net .net asp.net-mvc dependency-injection

我已阅读问题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提供了一个有趣的选项。

2 个答案:

答案 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引用BB引用CB和/或{{ 1}}需要一些DI注册,这使得C(您的条目应用程序)负责了解AB的详细信息以注册所有依赖项。

解决方案是使用一个单独的程序集来处理CB的所有注册。 C引用了该文件,它提供了A需要使用AB的所有容器配置。

好处是

  • CAB的了解不比
  • 更多
  • CAB都不得与Unity或Windsor这样的特定DI框架绑定。

Here's an example。这是一个最适合DI容器的事件总线类。但是为了使用它,你不必知道它需要注册的所有依赖项。所以对于温莎我创建了一个C。你只需致电

DomainEventFacility

并且所有依赖项都已注册。您注册的唯一事项是您的事件处理程序。

然后,如果我想使用与Unity不同的DI容器相同的事件总线库,我可以创建一些类似的程序集来处理Unity的相同配置。