.NET DI 与运行时实现解析器

.NET DI with runtime implementation resolvers

我有一个涉及 DI 的奇怪案例,特别是在运行时从同一服务中解析实现。我知道我可以注入服务提供者,但这似乎违反了 dependency inversion principle.

此外,如果这最终成为一个 architectural/design 问题,我们深表歉意;我最近从 .NET Framework 开发转向了 DI,但仍然熟悉 DI 的局限性。 请注意,出于显而易见的原因,我已经简化和更改了业务上下文,因此请记住 hierarchy/structure 是重要的部分...对于这个问题,我决定使用在线零售商的经典示例。

项目 Overview/Example:

core library (.NET Class Library)
  - IRetailerService: public service consumed by client apps
    └ IOrderService: facade/aggregate services injected into ^
      ├ IInventoryManager: internal components injected into facade/aggregate services as well as other components
      ├ IProductRespository
      └ IPriceEstimator

Aggregate/Façade Services

public class RetailerService : IRetailerService
{
    private readonly IOrderService _orderService;

    public OrderService( IOrderService orderService, ... ) { //... set injected components }
    
    async Task IRetailerService.Execute( Guid id )
    {
        await _orderService.Get( id );
    }
    async Task IRetailerService.Execute( Guid id, User user )
    {
        await _orderService.Get( id, user );
    }
}
internal class OrderService : IOrderService
{
    public OrderService( IInventoryManager inventoryManager, IProductRespository productRepo, ... ) { }

    async Task<object> IOrderService.Get( Guid id )
    {
        //... do stuff with the injected components
        await _inventoryManager.Execute( ...args );
        await _productRepo.Execute( ...args );
    }
    async Task<object> IOrderService.Get( Guid id, User user ) { }
}

问题:

假设我想记录 IOrderService.Get( Guid id, User user ),但仅当提供了 User 覆盖时 - 这包括在注入的组件(InventoryManager、IProductRepository 等)中记录.) 还有.

目前我能看到的唯一解决方案是:

  1. 向此层次结构添加一个附加层并使用具有作用域生命周期的命名注册来确定是否传递了 null 与日志记录实现。
  2. 将服务提供者注入面向 public 的服务 IRetailerService,并以某种方式传递正确的实现。

我认为我理想的解决方案是某种类型的 decorator/middleware 来控制它...我只给出了核心库代码;但在引用该库的解决方案中还有一个 WebApi 项目。任何 ideas/guidance 将不胜感激。

您可以在运行时在 IOrderService.Get 方法中解决您的依赖关系,以便每个方法都有自己的依赖关系。尽管如此,这并不能完全解决您的问题。嵌套依赖项 IInventoryManager inventoryManager, IProductRespository productRepo, ... 也应该能够启用日志记录。

因此您可以使用:

internal class OrderService : IOrderService
{
    public OrderService( IServiceProvider serviceProvider) { }

    async Task<object> IOrderService.Get( Guid id )
    {
        var inventoryManager = (IInventoryManager)serviceProvider.GetService(typeof(IInventoryManager));
        inventoryManager.Logging = false;
        var productRepo = (IProductRespository)serviceProvider.GetService(typeof(IProductRespository));
        productRepo.Logging = false;
        //... do stuff with the injected components
        await inventoryManager.Execute( ...args );
        await productRepo.Execute( ...args );
    }
    async Task<object> IOrderService.Get( Guid id, User user ) {
        var inventoryManager = (IInventoryManager)serviceProvider.GetService(typeof(IInventoryManager));
        inventoryManager.Logging = false;
        var productRepo = (IProductRespository)serviceProvider.GetService(typeof(IProductRespository));
        productRepo.Logging = true;
        //... do stuff with the injected components
        await inventoryManager.Execute( ...args );
        await productRepo.Execute( ...args );
    }
}

您还可以为工厂/建造者提供一个启用日志记录的参数。 但在任何情况下,因为您希望在嵌套的 classes 中从同一个根 class 开始有不同的行为,这可能很复杂。

另一种选择是提供 IOrderService 的 2 个实现,一个包含日志记录,另一个不包含。但我不确定这是否对您有帮助,因为您可能有充分的理由为该方法提供重载而不是将它们拆分为单独的服务。这并不能解决嵌套注入的问题。

最后一个选项可能是使用单例 LoggingOptions class。 每个依赖项都依赖于此 class,并且因为这是一个单例,所以每次您输入重载时都将其设置为 true,因此所有 classes 都会被告知您的记录意图。然而,这在很大程度上取决于您的架构。如果几乎同时调用这两种方法,这可能会破坏嵌套依赖项日志记录行为或随时中断日志记录。

看看这个 这可能会有所帮助。通过考虑这个问题,您可以为每个依赖项(包括嵌套依赖项)提供一个工厂,它将在每次调用重载方法时设置日志记录行为。

我建议使用工厂来创建订单服务,以及任何需要记录器的下游依赖项。这是一个完整的示例:

void Main()
{
    var serviceProvider = new ServiceCollection()
        .AddScoped<IRetailerService, RetailerService>()
        .AddScoped<IInventoryManager, InventoryManager>()
        .AddScoped<IOrderServiceFactory, OrderServiceFactory>()
        .BuildServiceProvider();

    var retailerService = serviceProvider.GetRequiredService<IRetailerService>();

    Console.WriteLine("Running without user");
    retailerService.Execute(Guid.NewGuid());
    
    Console.WriteLine("Running with user");
    retailerService.Execute(Guid.NewGuid(), new User());
}

public enum OrderMode
{
    WithUser,

    WithoutUser
}

public interface IOrderServiceFactory
{
    IOrderService Get(OrderMode mode);
}

public class OrderServiceFactory : IOrderServiceFactory
{
    private readonly IServiceProvider _provider;

    public OrderServiceFactory(IServiceProvider provider)
    {
        _provider = provider;
    }

    public IOrderService Get(OrderMode mode)
    {
        // Create the right sort of order service - resolve dependencies either by new-ing them up (if they need the 
        // logger) or by asking the service provider (if they don't need the logger).
        return mode switch
        {
            OrderMode.WithUser => new OrderService(new UserLogger(), _provider.GetRequiredService<IInventoryManager>()),
            OrderMode.WithoutUser => new OrderService(new NullLogger(), _provider.GetRequiredService<IInventoryManager>())
        };
    }
}

public interface IRetailerService
{
    Task Execute(Guid id);

    Task Execute(Guid id, User user);
}

public interface IOrderService
{
    Task Get(Guid id);
    Task Get(Guid id, User user);
}

public class User { }

public class RetailerService : IRetailerService
{
    private readonly IOrderServiceFactory _orderServiceFactory;

    public RetailerService(
        IOrderServiceFactory orderServiceFactory)
    {
        _orderServiceFactory = orderServiceFactory;
    }

    async Task IRetailerService.Execute(Guid id)
    {
        var orderService = _orderServiceFactory.Get(OrderMode.WithoutUser);
        await orderService.Get(id);
    }

    async Task IRetailerService.Execute(Guid id, User user)
    {
        var orderService = _orderServiceFactory.Get(OrderMode.WithUser);
        await orderService.Get(id, user);
    }
}

public interface ISpecialLogger
{
    public void Log(string message);
}

public class UserLogger : ISpecialLogger
{
    public void Log(string message)
    {
        Console.WriteLine(message);
    }
}

public class NullLogger : ISpecialLogger
{
    public void Log(string message)
    {
        // Do nothing.
    }
}

public interface IInventoryManager { }

public class InventoryManager : IInventoryManager { }

internal class OrderService : IOrderService
{

    private readonly ISpecialLogger _logger;

    public OrderService(ISpecialLogger logger, IInventoryManager inventoryManager)
    {
        _logger = logger;

    }

    public async Task Get(Guid id)
    {
        _logger.Log("This is the 'id-only' method");
    }

    public async Task Get(Guid id, User user)
    {
        _logger.Log("This is the 'id-and-user' method");
    }
}

使用这个,你得到以下输出:

Running without user
Running with user
This is the 'id-and-user' method

工厂让您可以完全控制下游组件的生成方式,因此您可以随心所欲地复杂化。