在项目之间共享Microsoft.Extensions.DependencyInjection.ServiceProvider配置

时间:2018-06-28 16:17:35

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

我有一个包含以下项目的解决方案

  • Acme.Core
  • Acme.Domain
  • Acme。存储库
  • Acme.Services
  • Acme.Web

过去,我在完整框架项目中使用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实例的另一种方法。

我的问题:

  1. 如何传递当前ServiceProvider的引用?
  2. 是否有更好的设计来实现我在多个库中共享Microsoft.Extensions.DependencyInjection的配置的目标?

2 个答案:

答案 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)

为了在多个项目中共享配置,可以将配置放入共享程序集中,然后在其中注册(而不解析)它们。许多依赖项注入库都提供了该功能。例如

从总体上讲,您将创建一个方法来接收DI容器并将您的注册添加到该容器中。如果您的DI框架不提供钩子,则需要您自己手动调用该方法,但是一般概念不会改变。

将注册拆分为模块,可以轻松地将相似的功能集进行分组,同时保持将不同功能集合并到不同项目中的灵活性。当然,您可以创建一个共享的程序集,该程序集为所有项目注册所有依赖项的并集,但这将带来不必要的负担,并导致实现过程的可重用性降低。

Steven指出的关键点是配置容器并让其注入依赖项,而不是从内到外寻找依赖项。