.NET8入门:5.ASP.NET Core中的依赖注入(DI)

                在.NET中依赖注入(DI)一种常用的软件设计模式,在类与其依赖关系之间实现控制反转(IOC)。

 依赖注入解决了以下问题:

  • 使用接口或基类将依赖关系实现抽象化。
  • 在服务容器中注册依赖关系。 .NET 提供了一个内置的服务容器 IServiceProvider。 服务通常在应用启动时注册,并追加到 IServiceCollection。 添加所有服务后,可以使用 BuildServiceProvider 创建服务容器。
  • 将服务注入到使用它的类的构造函数中。 框架负责创建依赖关系的实例,并在不再需要时将其释放。

使用示例如下:

1.将依赖对象抽象成接口。

a.该接口中定义了Write(string message)方法。

    public interface IMessageWriter
    {
        void Write(string message);
    }

2.实现依赖对象。

a.实现IMessageWriter接口。

    public class MessageWriter: IMessageWriter
    {
        public void Write(string message)
        {
            Console.WriteLine($"MessageWriter.Write(message: \"{message}\")");
        }
    }

3.实现使用依赖对象的对象。

a.通过构造函数的方式注入IMessageWriter实例。

    public class Worker(IMessageWriter messageWriter) : BackgroundService
    {
        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            while (!stoppingToken.IsCancellationRequested)
            {
                messageWriter.Write($"Worker running at: {DateTimeOffset.Now}");
                await Task.Delay(1_000, stoppingToken);
            }
        }
    }

4.注入依赖对象。

a.添加Worker服务。

b.通过AddSingleton方法注册MessageWriter类型到IMessageWriter接口上。

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

 //添加Worker服务
builder.Services.AddHostedService<Worker>();

 //册使用具体类型 MessageWriter 注册 MessageWriter 服务
 // AddSingleton 方法使用单一实例生存期(应用的生存期)注册服务。
builder.Services.AddSingleton<IMessageWriter, MessageWriter>();

using IHost host = builder.Build();

host.Run();

5.新增一个依赖对象。

    public class LoggingMessageWriter(ILogger<LoggingMessageWriter> logger) : IMessageWriter
    {
        public void Write(string message) =>logger.LogInformation("Info: {Msg}", message);
    }

6.修改注入对象。

a.该步骤同4。

b.builder.Services.AddSingleton<IMessageWriter, LoggingMessageWriter>()。修改该代码后,注入对象从MessageWriter变成了LoggingMessageWriter。

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Services.AddHostedService<Worker>();
//builder.Services.AddSingleton<IMessageWriter, MessageWriter>();
builder.Services.AddSingleton<IMessageWriter, LoggingMessageWriter>();

using IHost host = builder.Build();

host.Run();

服务生存期

关键字生命周期描述实例创建情况AddTransient暂时每次请求时创建服务,结束时会释放掉此服务。Transient 服务总是不同的,每次检索服务时,都会创建一个新实例AddScoped范围每个客户端请求(连接)创建一次服务,请求(连接)结束时释放。Scoped 服务只会随着新范围而改变,但在一个范围中是相同的实例AddSingleton单例首次请求时创建。应用关闭并释放 ServiceProvider 时释放。Singleton 服务总是相同的,新实例仅被创建一次

服务生命周期使用示例如下:

1.创建报表服务生命周期接口

    /// <summary>
    /// 报表服务生命周期接口
    /// </summary>
    public interface IReportServiceLifetime
    {
        Guid Id { get; }

        /// <summary>
        /// 生命周期
        /// </summary>
        ServiceLifetime Lifetime { get; }
    }

2.定义三种生命周期接口

public interface IExampleTransientService : IReportServiceLifetime
{
    ServiceLifetime IReportServiceLifetime.Lifetime => ServiceLifetime.Transient;
}

public interface IExampleScopedService : IReportServiceLifetime
{
    ServiceLifetime IReportServiceLifetime.Lifetime => ServiceLifetime.Scoped;
}

public interface IExampleSingletonService : IReportServiceLifetime
{
    ServiceLifetime IReportServiceLifetime.Lifetime => ServiceLifetime.Singleton;
}

3.实现三种生命周期示例

internal sealed class ExampleTransientService : IExampleTransientService
{
    Guid IReportServiceLifetime.Id { get; } = Guid.NewGuid();
}

internal sealed class ExampleScopedService : IExampleScopedService
{
    Guid IReportServiceLifetime.Id { get; } = Guid.NewGuid();
}

internal sealed class ExampleSingletonService : IExampleSingletonService
{
    Guid IReportServiceLifetime.Id { get; } = Guid.NewGuid();
}

4.创建需要DI的服务

    /// <summary>
    /// 生命周期报告
    /// </summary>
    /// <param name="transientService"></param>
    /// <param name="scopedService"></param>
    /// <param name="singletonService"></param>
    internal sealed class ServiceLifetimeReporter(
        IExampleTransientService transientService,
        IExampleScopedService scopedService,
        IExampleSingletonService singletonService)
    {
        public void ReportServiceLifetimeDetails(string lifetimeDetails)
        {
            Console.WriteLine(lifetimeDetails);

            LogService(transientService, "Always different");
            LogService(scopedService, "Changes only with lifetime");
            LogService(singletonService, "Always the same");
        }

        private static void LogService<T>(T service, string message)where T : IReportServiceLifetime =>
        Console.WriteLine($"    {typeof(T).Name}: {service.Id} ({message})");
    }

5.注册服务

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

//注册瞬时服务
builder.Services.AddTransient<IExampleTransientService, ExampleTransientService>();

//注册范围服务
builder.Services.AddScoped<IExampleScopedService, ExampleScopedService>();

//注册单例服务
builder.Services.AddSingleton<IExampleSingletonService, ExampleSingletonService>();


builder.Services.AddTransient<ServiceLifetimeReporter>();

using IHost host = builder.Build();

ExemplifyServiceLifetime(host.Services, "Lifetime 1");
ExemplifyServiceLifetime(host.Services, "Lifetime 2");

await host.RunAsync();

static void ExemplifyServiceLifetime(IServiceProvider hostProvider, string lifetime)
{
    using IServiceScope serviceScope = hostProvider.CreateScope();
    IServiceProvider provider = serviceScope.ServiceProvider;
    ServiceLifetimeReporter logger = provider.GetRequiredService<ServiceLifetimeReporter>();
    logger.ReportServiceLifetimeDetails(
        $"{lifetime}: Call 1 to provider.GetRequiredService<ServiceLifetimeReporter>()");

    Console.WriteLine("...");

    logger = provider.GetRequiredService<ServiceLifetimeReporter>();
    logger.ReportServiceLifetimeDetails(
        $"{lifetime}: Call 2 to provider.GetRequiredService<ServiceLifetimeReporter>()");

    Console.WriteLine();
}

6.服务生命周期分析

运行结果如图,

其中IExampleTransientService(瞬时服务),在每次被调用的时候GUID都发生了变化,说明访问的不是同一个服务实例,服务每次被调用完后都会释放,并在被调用时新建。

其中IExampleScopedService(范围服务),在Lifetime 1中每次被调用的时候GUID未发生变化,在Lifetime 2中被调用时才发生变化,说明Lifetime 1中访问的是同一个服务实例,在Lifetime 2中访问的是另一个服务实例,范围服务在一次客户端请求结束后会释放,并在下次客户端请求发起时新建。

其中IExampleSingletonService(单例服务)在每次调用时的GUID都未发生变化,说明每次请求都调用的同一个服务示例,该服务会在应用关闭并释放 ServiceProvider 时释放。

键控服务

在.NET8开始支持基于秘钥的服务注册和查找,假设存在实现需要注册多个实现同一个接口的类时,可以通过键控服务来实现,示例如下:

1.使用FromKeyedServices特性来指定秘钥。

    public class Worker([FromKeyedServices("M")] IMessageWriter messageWriter) : BackgroundService
    {
        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            while (!stoppingToken.IsCancellationRequested)
            {
                messageWriter.Write($"Worker running at: {DateTimeOffset.Now}");
                await Task.Delay(1_000, stoppingToken);
            }
        }
    }

2.使用AddKeyedSingleton方法来使用键控服务。

a.FromKeyedServices("M")指向AddKeyedSingleton<T1,T2>("M")

b.FromKeyedServices("L")指向AddKeyedSingleton<T1,T2>("L")

builder.Services.AddHostedService<Worker>();
builder.Services.AddKeyedSingleton<IMessageWriter, MessageWriter>("M");
builder.Services.AddKeyedSingleton<IMessageWriter, LoggingMessageWriter>("L");

DI三大对象

.NET中依赖注入包含三大抽象对象:

    IServiceCollection:为服务描述符集合定义协定

    IServiceProvider:定义用于检索服务对象的机制

    IServiceProvider:描述一种服务,其中包括该服务的类型、实现和生存期

在IServiceCollection中配置和管理服务,注册服务后调用BuildServiceProvider生成IServiceProvider实例,IServiceProvider充当所有已注册服务的容器。

核心实现如下(具体案例请见:依赖关系注入基本信息 - .NET | Microsoft Learn):

using Microsoft.Extensions.DependencyInjection;

// 1. Create the service collection.
var services = new ServiceCollection();

// 2. Register (add and configure) the services.
services.AddSingleton<IConsole>(
    implementationFactory: static _ => new DefaultConsole
    {
        IsEnabled = true
    });
services.AddSingleton<IGreetingService, DefaultGreetingService>();
services.AddSingleton<FarewellService>();

// 3. Build the service provider from the service collection.
var serviceProvider = services.BuildServiceProvider();

// 4. Resolve the services that you need.
var greetingService = serviceProvider.GetRequiredService<IGreetingService>();
var farewellService = serviceProvider.GetRequiredService<FarewellService>();

// 5. Use the services
var greeting = greetingService.Greet("David");
var farewell = farewellService.SayGoodbye("David");

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值