简体   繁体   English

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

[英]Where dependency-injection registrations have to be put?

I've read the question Ioc/DI - Why do I have to reference all layers/assemblies in application's entry point?我已经阅读了问题Ioc/DI - 为什么我必须引用应用程序入口点中的所有层/程序集?

So, in a Asp.Net MVC5 solution, the composition root is in the MVC5 project (and having a DependencyInjection assembly in charge of all the registrations does not make sense).因此,在 Asp.Net MVC5 解决方案中,组合根位于 MVC5 项目中(并且拥有负责所有注册的 DependencyInjection 程序集没有意义)。

Within this picture, it is not clear to me what is the better approach among the following.在这张图片中,我不清楚以下哪种方法更好。

Approach 1方法一

The concrete implementations are public class ... and all registrations clauses are centralized within the composition root (eg in one or more files under a CompositionRoot folder).具体实现是public class ...并且所有注册子句都集中在组合根中(例如,在 CompositionRoot 文件夹下的一个或多个文件中)。 MVC5 project must reference all the assemblies providing at least one concrete implementation to be bound. MVC5 项目必须引用所有提供至少一个要绑定的具体实现的程序集。 No library references the DI library.没有库引用 DI 库。 MVC project can contain interfaces to be bound with no drawbacks. MVC 项目可以包含要绑定的接口,没有任何缺点。

Approach 2方法二

The concrete implementations are internal class ... .具体实现是internal class ... Each library exposes a DI 'local' configuration handler.每个库都公开一个 DI“本地”配置处理程序。 For example例如

public class DependencyInjectionConfig {
    public static void Configure(Container container) {
        //here registration of assembly-provided implementations
        //...
    }
}

which is up to register its own implementations.这取决于注册自己的实现。 The composition root triggers registrations by calling all the Configure() methods, just one for each project.组合根通过调用所有Configure()方法来触发注册,每个项目只调用一个方法。 MVC5 project must then reference all the assemblies providing at least one concrete implementation to be bound.然后 MVC5 项目必须引用所有程序集,提供至少一个要绑定的具体实现。 Libraries must reference the DI library.必须引用 DI 库。 In this case, the MVC5 project cannot contain interfaces (otherwise there would be a circular reference): a ServiceLayer assembly would be needed to hold public interfaces to be bound.在这种情况下,MVC5 项目不能包含接口(否则会出现循环引用):需要一个 ServiceLayer 程序集来保存要绑定的公共接口。

Approach 3方法三

Same as Approach 2, but local configuration modules are discovered dynamically through assembly reflection (by convention?).与方法 2 相同,但本地配置模块是通过程序集反射动态发现的(按惯例?)。 So MVC5 project has not to reference libraries.所以MVC5项目没有引用库。 MVC project can contain interfaces and can be referenced by libraries. MVC 项目可以包含接口并且可以被库引用。 Libraries must reference the DI library.必须引用 DI 库。

What is the best practice here?这里的最佳做法是什么? Is there some other better possibility?还有其他更好的可能吗?

EDIT 1 (2016-12-22) Thanks to received answers, I published this github project describing the best solution I found so far.编辑 1 (2016-12-22)感谢收到的答案,我发布了这个 github 项目,描述了我迄今为止找到的最佳解决方案。

EDIT 2 (2018-09-09) This answer provides an interesting option.编辑 2 (2018-09-09) 这个答案提供了一个有趣的选项。

EDIT 3 (2020-12-29) Finally, I came up with a complete solution, packaged in the form of a WebApi application template. EDIT 3 (2020-12-29)最后,我想出了一个完整的解决方案,以 WebApi 应用程序模板的形式打包。 I published this solution on GitHub HERE .我在 GitHub HERE上发布了这个解决方案。 This approach, not only gives a clear understanding about where DI rules have to be put, but also suggests to setup the application according to SOLID principles and CQRS pattern.这种方法不仅可以清楚地了解 DI 规则必须放在哪里,而且还建议根据 SOLID 原则和 CQRS 模式设置应用程序。 The commit history of this project has been structured to have educational purposes.该项目的提交历史已被构建为具有教育目的。

I typically like to encapsulate these types of things into each project.我通常喜欢将这些类型的东西封装到每个项目中。 So for example I might have the following.例如,我可能有以下内容。 (This is an extremely simplified example, and I'll use the AutoFac in this example, but I'd imagine all DI frameworks have something like the following). (这是一个极其简化的示例,我将在此示例中使用AutoFac ,但我想所有 DI 框架都具有以下内容)。

Common area for just POCOs and Interfaces.仅用于 POCO 和接口的公共区域。

// MyProject.Data.csproj
namespace MyProject.Data
{
  public Interface IPersonRepository
  {
    Person Get();
  }

  public class Person
  {
  }
}

Implementation of Repositories and Data Access存储库和数据访问的实现

// 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();
    }
  }
}

Consumer消费者

// 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);
    }
  }
}

So the Dependencies look like:所以依赖关系看起来像:

MyProject.Data.csproj 
- None

MyProject.Data.EF.csproj 
- MyProject.Data

MyProject.Web.UI.csproj 
- MyProject.Data
- MyProject.Data.EF

In this setup, the Web.UI cannot know anything about what is registered nor for what reason.在此设置中,Web.UI 无法了解已注册的内容或原因。 It only knows that the EF project has implementations but can't access them.它只知道 EF 项目有实现但无法访问它们。

I can drop EF for say Dapper extremely easily as each project encapsulates it's own implementations and registration .我可以非常轻松地将 EF 删除为 Dapper,因为每个项目都封装了它自己的实现和注册

If I was adding unit tests and had an InMemoryPersonRepository, how would I swap out the PersonRepository for my InMemoryPersonRepository?如果我添加单元测试并有一个 InMemoryPersonRepository,我将如何将 PersonRepository 换成我的 InMemoryPersonRepository?

Assuming we ignore any business logic layer and have an MVC Controller directly access our Data Accessor, my code might look like:假设我们忽略任何业务逻辑层并让 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);
  }
}

Then a test using nSubstitute Might look like:然后使用 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));
}

You've identified a real problem.你发现了一个真正的问题。 (One could say it's a good problem to have.) If entry application A references B , B references C , and B and/or C require some DI registration, that makes A (your entry application) responsible for knowing enough about the details of B and C to register all the dependencies. (可以说这是一个很好的问题。)如果报名申请A引用BB引用C ,并且B和/或C需要一些 DI 注册,这使得A (您的报名申请)负责了解足够的细节BC来注册所有的依赖。

The solution is to have a separate assembly that handles composing all of the registrations for B and C .解决方案是使用一个单独的程序集来处理组合BC所有注册。 A references that, and it provides all of the container configuration that A needs to use B and C . A引用了它,它提供了A需要使用BC所有容器配置。

The benefits are好处是

  • A doesn't know more about B and C than it should ABC了解并不多于它应该知道的
  • Neither A , B , nor C have to be tied to one particular DI framework like Unity or Windsor. ABC都不必绑定到一个特定的 DI 框架,如 Unity 或 Windsor。

Here's an example . 这是一个例子 This is an event bus class that works best with a DI container.这是一个最适用于 DI 容器的事件总线类。 But in order to use it you shouldn't have to know all about the dependencies it needs to register.但是为了使用它,您不必了解它需要注册的所有依赖项。 So for Windsor I created a DomainEventFacility .所以对于 Windsor 我创建了一个DomainEventFacility You just call你只要打电话

_container.AddFacility<DomainEventFacility>();

and all of the dependencies are registered.并且所有依赖项都已注册。 The only thing you register are your event handlers.您唯一注册的是您的事件处理程序。

Then if I want to use the same event bus library with a different DI container like Unity I can just create some similar assembly to handle the same configuration for Unity.然后,如果我想将相同的事件总线库与不同的 DI 容器(如 Unity)一起使用,我可以创建一些类似的程序集来处理 Unity 的相同配置。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM