在 ASP.NET Core 中掌握依赖关系注入

作者:微信公众号:【架构师老卢】
10-26 18:2
126

依赖项注入 (DI) 是 ASP.NET Core 中的一项重要功能,使您能够以弯曲的方式管理依赖于每个不同的实用程序的部分。虽然许多开发人员了解基础知识,但卓越的 DI 策略可以广泛增强大型或复杂计划的能力。在本文中,我们将介绍一些高级技术。

第 1 部分:深入了解服务生命周期

ASP.NET Core 支持三种类型的提供程序生存期:单一实例、作用域和瞬态。每个都有自己的用例和对实用程序性能的影响。

  • _单一实例_:在应用程序的生命周期内使用单个实例。适用于无状态服务或缓存。
  • _范围:_根据请求创建新实例。最适合想要操作请求状态但不再全局操作的服务。
  • _Transient_:每次请求提供程序时,都会创建一个新实例。非常适合轻量级、无状态的产品。

_高级提示:将 scoped 产品注入 singleton 产品时要小心。ASP.NET Core 将引发异常,以防你尝试从单一实例中清除范围提供程序。常见的答案是使用 IServiceProvider工厂示例_手动操作范围。

public class MySingletonService
{
    private readonly IServiceProvider _serviceProvider;

    public MySingletonService(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public void DoWork()
    {
        using (var scope = _serviceProvider.CreateScope())
        {
            var scopedService = scope.ServiceProvider.GetRequiredService<IMyScopedService>();
            scopedService.PerformTask();
        }
    }
}

第 2 部分:使用 IServiceProvider 和 IServiceScopeFactory

在某些情况下,您可能希望在服务生命周期内进行更好的管理。_IServiceProvider_ 接口允许以编程方式引入提供程序范围,这在冗长的职责(如历史记录服务)或具有非标准请求生存期的程序中特别有用。

例如,如果你有一个希望修复范围服务的托管服务,则可以使用 _IServiceScopeFactory_:

public class MyBackgroundService : BackgroundService
{
    private readonly IServiceScopeFactory _scopeFactory;

    public MyBackgroundService(IServiceScopeFactory scopeFactory)
    {
        _scopeFactory = scopeFactory;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            using (var scope = _scopeFactory.CreateScope())
            {
                var scopedService = scope.ServiceProvider.GetRequiredService<IMyScopedService>();
                await scopedService.DoWorkAsync();
            }
            await Task.Delay(1000, stoppingToken);
        }
    }
}

此示例确保后台任务的每次迭代都有其自己的范围提供程序的 sparkling 实例。

第 3 部分:使用 IServiceProvider 的条件 DI

在高级情况下,您可能希望根据运行时配置或其他因素有条件地注册产品。将 IServiceProvider 用于运行时 DI 可以为您提供以下柔韧性:

public void ConfigureServices(IServiceCollection services)
{
    if (someCondition)
    {
        services.AddTransient<IMyService, MyServiceA>();
    }
    else
    {
        services.AddTransient<IMyService, MyServiceB>();
    }
}

ASP.NET Core 的 IServiceProvider 允许运行时的良好判断,以完全根据环境或特定情况来决定要注入哪个实现。

第 4 部分:使用 IEnumerable<T 处理多个实现>

使用 _**IEnumerable<T**_ 处理多个实现>

有时,您可能希望注入一个接口的几个实现并动态地使用它们。ASP.NET Core DI 通过 IEnumerable<T> 支持此功能:

public class MyConsumerService
{
    private readonly IEnumerable<IMyService> _services;

    public MyConsumerService(IEnumerable<IMyService> services)
    {
        _services = services;
    }

    public void ExecuteAll()
    {
        foreach (var service in _services)
        {
            service.PerformOperation();
        }
    }
}

这允许您注册多个产品,并在运行时确定要调用哪个产品。

services.AddTransient<IMyService, MyServiceA>();  
services.AddTransient<IMyService, MyServiceB>();

第 5 部分:管理循环依赖关系

循环依赖关系在使用 DI.例如,如果两个产品相互依赖,则可能会导致运行时异常。ASP.NET Core 允许使用构造函数注入或通过应用技术或资产注入等策略,或者通过使用工厂来打破此类循环。

要清除循环依赖关系:

  1. 使用制造单元样本打破循环。
  2. 注入 IServiceProvider 并在运行时解析其中一个产品/服务。
public class ServiceA
{
    private readonly IServiceProvider _serviceProvider;

    public ServiceA(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public void UseServiceB()
    {
        var serviceB = _serviceProvider.GetService<ServiceB>();
        serviceB.PerformOperation();
    }
}

第 6 部分:实现具有多个共享同一实例的接口的类

当一个类别实现多个接口时,可以登录并为特殊接口注入 equal 示例,从而确保每个接口在整个应用程序中共享相同的项。

下面是一个类的单个实例实现多个接口的示例:

public interface IEmailSender  
{  
    void SendEmail(string to, string subject, string body);  
}

public interface ISmsSender  
{  
    void SendSms(string number, string message);  
}

public class NotificationService : IEmailSender, ISmsSender  
{  
    public void SendEmail(string to, string subject, string body)  
    {  
        // Email sending logic  
    }

    public void SendSms(string number, string message)  
    {  
        // SMS sending logic  
    }  
}

要确保在每个接口中共享相同的实例,请尽快登录 elegance 并将两个接口映射到该相等的实例:

services.AddSingleton<NotificationService>(); // Register the class  
services.AddSingleton<IEmailSender>(provider => provider.GetService\<NotificationService>()); // Use the same instance  
services.AddSingleton<ISmsSender>(provider => provider.GetService\<NotificationService>()); // Use the same instance

在这里,_NotificationService_ 注册为单一实例,并且每个 IEmailSenderISmsSender 都解析为使用颁发者的 NotificationService 的等效示例_。GetService<NotificationService>()_ 的

当您将两个接口注入到一个类别中时,可以共享相同的 NotificationService 示例:

public class CommunicationController
{
    private readonly IEmailSender _emailSender;
    private readonly ISmsSender _smsSender;

    public CommunicationController(IEmailSender emailSender, ISmsSender smsSender)
    {
        _emailSender = emailSender;
        _smsSender = smsSender;
    }

    public void NotifyUser(string email, string phoneNumber)
    {
        _emailSender.SendEmail(email, "Subject", "Body");
        _smsSender.SendSms(phoneNumber, "Message");
    }
}

为什么有效

NotificationService 最好实例化一次,因为它是单例注册。

IEmailSenderISmsSender 都因 NotificationService 的同一实例而被解析,从而确保稳定的行为和国家/地区。

第 7 部分:将 AddKeyedTransient 用于多个实现

在 .NET 8 中,ASP.NET Core 引入了 AddKeyed 技术,其中包括 AddKeyedTransientAddKeyedScoped 和 _AddKeyedSingleton_,这些技术允许你注册同一运营商的多个实现,并在运行时根据密钥检索精确的实现。如果您有几个版本的相等 provider 接口,但需要根据上下文动态选择一个版本,则这主要是有益的。

让我们记住一个示例,在该示例中,您根据价格类型获得了 IPaymentProcessor 接口的不同实现,其中包括 CreditCardProcessor 和 _PayPalProcessor_:

public interface IPaymentProcessor  
{  
    void ProcessPayment(decimal amount);  
}

public class CreditCardProcessor : IPaymentProcessor  
{  
    public void ProcessPayment(decimal amount)  
    {  
        // Logic for credit card payment  
        Console.WriteLine($"Processing credit card payment of {amount}");  
    }  
}

public class PayPalProcessor : IPaymentProcessor  
{  
    public void ProcessPayment(decimal amount)  
    {  
        // Logic for PayPal payment  
        Console.WriteLine($"Processing PayPal payment of {amount}");  
    }  
}

使用 _AddKeyedTransient_,您可以使用独占键登录这两个实现:

services.AddKeyedTransient<IPaymentProcessor, CreditCardProcessor>("CreditCard");  
services.AddKeyedTransient<IPaymentProcessor, PayPalProcessor>("PayPal");

要完全基于键解析正确的实现,可以注入 _IKeyedServiceProvider_:

public class PaymentService
{
    private readonly IKeyedServiceProvider _keyedServiceProvider;

    public PaymentService(IKeyedServiceProvider keyedServiceProvider)
    {
        _keyedServiceProvider = keyedServiceProvider;
    }

    public void MakePayment(string paymentType, decimal amount)
    {
        var processor = _keyedServiceProvider.GetRequiredService<IPaymentProcessor>(paymentType);
        processor.ProcessPayment(amount);
    }
}

现在,根据重要的事情(“_CreditCard”或“PayPal_”),可以解决并使用 IPaymentProcessor 的最佳实现。

public class PaymentService
{
    private readonly IKeyedServiceProvider _keyedServiceProvider;

    public PaymentService(IKeyedServiceProvider keyedServiceProvider)
    {
        _keyedServiceProvider = keyedServiceProvider;
    }

    public void MakePayment(string paymentType, decimal amount)
    {
        var processor = _keyedServiceProvider.GetRequiredService<IPaymentProcessor>(paymentType);
        processor.ProcessPayment(amount);
    }
}

当您的软件支持多种策略或行为(例如收费策略、通知渠道)并允许在独一无二的实施之间轻松分离时,这种方法特别有用。

ASP.NET Core 中的高级 DI 技术提供了对实用程序行为的更多控制,从而可以优化整体性能、操纵服务生命周期和干净地实施复杂的架构。通过以某种方式正确处理范围产品、装饰器和提供程序生命周期的专业知识,您可以充分利用 ASP.NET Core 强大的 DI 框架。

相关留言评论
昵称:
邮箱:
阅读排行