[英]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.