我有一个包含以下项目的解决方案
过去,我在完整框架项目中使用Unity for DI。我能够将具体对象注册到可执行项目(Web应用程序,控制台应用程序,测试应用程序)中的接口映射。
我正在尝试使用.NET Core实现相同的方法。我想首先尝试使用Microsoft.Extensions.DependencyInjection库。在ASP.NET Core应用程序中,它运行良好。不幸的是,当我尝试与注册共享/引用该实例到其他项目(例如.NET Standard库)时,遇到了一个问题。
我的想法是将ServiceProvider注入服务的构造函数中:
public class AddressService : BaseService, IAddressService
{
private readonly IServiceProvider _serviceProvider;
public AddressService(IServiceProvider serviceProvider, string userOrProcessName)
{
_serviceProvider = serviceProvider;
}
public IReadOnlyList<IState> GetAllStates()
{
_serviceProvider.GetService<IAddressRepository>();
// other logic removed
}
}
我在Startup.ConfigureServices()中尝试了以下操作:
services.AddTransient<IAddressService>(s => new AddressService(HttpContext.RequestServices, Environment.UserName));
我遇到的问题是我无法在Controller外部引用HttpContext.RequestServices。我还没有找到传递ServiceProvider实例的另一种方法。
我的问题:
答案 0 :(得分:4)
防止向您的应用程序组件中注入IServiceProvider
;导致Service Locator anti-pattern。
相反,您应该仅使用构造函数注入构建应用程序组件。这意味着您的AddressService
应该要求IAddressRepository
作为构造函数参数,而不是IServiceProvider
。例如:
public class AddressService : IAddressService
{
private readonly IAddressRepository repo;
public AddressService(IAddressRepository repo, IUserContext userContext)
{
this.repo = repo;
}
public IReadOnlyList<IState> GetAllStates()
{
// other logic removed
}
}
还尝试防止将素数注入构造函数中。这本身并不是一个坏习惯,但是它确实使对象图的构造变得复杂。相反,可以将值包装到一个类中(以防万一其为配置值),或者将其隐藏在抽象后面(如上所示),以防它是运行时值。
两种做法都可以简化您的应用程序代码和Composition Root。
例如,这将是先前AddressService
重新设计的结果:
services.AddTransient<IAddressRepository, SqlAddressRepository>();
services.AddTransient<IAddressService, AddressService>();
services.AddScoped<IUserContext, UserContext>();
services.AddHttpContextAccessor();
在这里,UserContext
的定义如下:
public class UserContext : IUserContext
{
private readonly IHttpContextAccessor accessor;
public UserContext(IHttpContextAccessor accessor) => this.accessor = accessor;
public string UserName => this.accessor.HttpContext.User.Identity.Name;
}
答案 1 :(得分:1)
为了在多个项目中共享配置,可以将配置放入共享程序集中,然后在其中注册(而不解析)它们。许多依赖项注入库都提供了该功能。例如
在Autofac中,您创建一个模块(https://autofaccn.readthedocs.io/en/latest/configuration/modules.html),该模块需要容器构建器进行配置:
protected override void Load(ContainerBuilder builder) { ... }
SimpleInjector提供了以下软件包:https://simpleinjector.readthedocs.io/en/latest/howto.html#package-registrations
Unity可以支持类似的功能:Can I register my types in modules in Unity like I can in Autofac?
Ninject具有类似的模块功能:What is the intention of Ninject modules?
已为Microsoft.Extensions.DependencyInjection创建了类似的功能:https://github.com/aruss/DotNetCore_ModularApplication
从总体上讲,您将创建一个方法来接收DI容器并将您的注册添加到该容器中。如果您的DI框架不提供钩子,则需要您自己手动调用该方法,但是一般概念不会改变。
将注册拆分为模块,可以轻松地将相似的功能集进行分组,同时保持将不同功能集合并到不同项目中的灵活性。当然,您可以创建一个共享的程序集,该程序集为所有项目注册所有依赖项的并集,但这将带来不必要的负担,并导致实现过程的可重用性降低。
Steven指出的关键点是配置容器并让其注入依赖项,而不是从内到外寻找依赖项。