• 【微软技术栈】C#.NET 中使用依赖注入


    本文内容

    1. 先决条件
    2. 创建新的控制台应用程序
    3. 添加接口
    4. 添加默认实现
    5. 添加需要 DI 的服务
    6. 为 DI 注册服务
    7. 结束语

    本文介绍如何在 .NET 中使用依赖注入 (DI)。 借助 Microsoft 扩展,可通过添加服务并在 IServiceCollection 中配置这些服务来管理 DI。 IHost 接口会公开 IServiceProvider 实例,它充当所有已注册的服务的容器。

    本文介绍如何执行下列操作:

    • 创建一个使用依赖注入的 .NET 控制台应用
    • 生成和配置通用主机
    • 编写多个接口及相应的实现
    • 为 DI 使用服务生存期和范围设定

    1、先决条件

    • .NET Core 3.1 SDK 或更高版本。
    • 熟悉如何创建新的 .NET 应用程序以及如何安装 NuGet 包。

    2、创建新的控制台应用程序

    通过 dotnet new 命令或 IDE 的“新建项目”向导,新建一个名为 ConsoleDI 的 .NET 控制台应用程序 Example 。 将 NuGet 包 Microsoft.Extensions.Hosting 添加到项目。

    新的控制台应用项目文件应如下所示:

    1. <Project Sdk="Microsoft.NET.Sdk">
    2. <PropertyGroup>
    3. <OutputType>Exe</OutputType>
    4. <TargetFramework>net7.0</TargetFramework>
    5. <Nullable>enable</Nullable>
    6. <ImplicitUsings>true</ImplicitUsings>
    7. <RootNamespace>ConsoleDI.Example</RootNamespace>
    8. </PropertyGroup>
    9. <ItemGroup>
    10. <PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" />
    11. </ItemGroup>
    12. </Project>

     重要

    在此示例中,需要 NuGet 包 Microsoft.Extensions.Hosting 来生成和运行应用。 某些元包可能包含 Microsoft.Extensions.Hosting 包,在这种情况下,不需要显式包引用。

    3、添加接口

    在此示例应用中,你将了解依赖项注入如何处理服务生存期。 你将创建多个表示不同服务生存期的接口。 将以下接口添加到项目根目录:

    IReportServiceLifetime.cs

    1. using Microsoft.Extensions.DependencyInjection;
    2. namespace ConsoleDI.Example;
    3. public interface IReportServiceLifetime
    4. {
    5. Guid Id { get; }
    6. ServiceLifetime Lifetime { get; }
    7. }

    IReportServiceLifetime 接口定义了以下项:

    • 表示服务的唯一标识符的 Guid Id 属性。
    • 表示服务生存期的 ServiceLifetime 属性。

    IExampleTransientService.cs

    1. using Microsoft.Extensions.DependencyInjection;
    2. namespace ConsoleDI.Example;
    3. public interface IExampleTransientService : IReportServiceLifetime
    4. {
    5. ServiceLifetime IReportServiceLifetime.Lifetime => ServiceLifetime.Transient;
    6. }

    IExampleScopedService.cs

    1. using Microsoft.Extensions.DependencyInjection;
    2. namespace ConsoleDI.Example;
    3. public interface IExampleScopedService : IReportServiceLifetime
    4. {
    5. ServiceLifetime IReportServiceLifetime.Lifetime => ServiceLifetime.Scoped;
    6. }

    IExampleSingletonService.cs

    1. using Microsoft.Extensions.DependencyInjection;
    2. namespace ConsoleDI.Example;
    3. public interface IExampleSingletonService : IReportServiceLifetime
    4. {
    5. ServiceLifetime IReportServiceLifetime.Lifetime => ServiceLifetime.Singleton;
    6. }

    IReportServiceLifetime 的所有子接口均使用默认值显式实现 IReportServiceLifetime.Lifetime。 例如,IExampleTransientService 使用 ServiceLifetime.Transient 值显式实现 IReportServiceLifetime.Lifetime

    4、添加默认实现

    该示例实现使用 Guid.NewGuid() 的结果初始化其 Id 属性。 将各种服务的下列默认实现类添加到项目根目录:

    ExampleTransientService.cs

    1. namespace ConsoleDI.Example;
    2. internal sealed class ExampleTransientService : IExampleTransientService
    3. {
    4. Guid IReportServiceLifetime.Id { get; } = Guid.NewGuid();
    5. }

    ExampleScopedService.cs

    1. namespace ConsoleDI.Example;
    2. internal sealed class ExampleScopedService : IExampleScopedService
    3. {
    4. Guid IReportServiceLifetime.Id { get; } = Guid.NewGuid();
    5. }

    ExampleSingletonService.cs

    1. namespace ConsoleDI.Example;
    2. internal sealed class ExampleSingletonService : IExampleSingletonService
    3. {
    4. Guid IReportServiceLifetime.Id { get; } = Guid.NewGuid();
    5. }

    每个实现都定义为 internal sealed 并实现其相应的接口。 例如,ExampleSingletonService 会实现 IExampleSingletonService

    5、添加需要 DI 的服务

    添加下列服务生存期报告器类,它作为服务添加到控制台应用:

    ServiceLifetimeReporter.cs

    1. namespace ConsoleDI.Example;
    2. internal sealed class ServiceLifetimeReporter
    3. {
    4. private readonly IExampleTransientService _transientService;
    5. private readonly IExampleScopedService _scopedService;
    6. private readonly IExampleSingletonService _singletonService;
    7. public ServiceLifetimeReporter(
    8. IExampleTransientService transientService,
    9. IExampleScopedService scopedService,
    10. IExampleSingletonService singletonService) =>
    11. (_transientService, _scopedService, _singletonService) =
    12. (transientService, scopedService, singletonService);
    13. public void ReportServiceLifetimeDetails(string lifetimeDetails)
    14. {
    15. Console.WriteLine(lifetimeDetails);
    16. LogService(_transientService, "Always different");
    17. LogService(_scopedService, "Changes only with lifetime");
    18. LogService(_singletonService, "Always the same");
    19. }
    20. private static void LogService<T>(T service, string message)
    21. where T : IReportServiceLifetime =>
    22. Console.WriteLine(
    23. $" {typeof(T).Name}: {service.Id} ({message})");
    24. }

    ServiceLifetimeReporter 会定义一个构造函数,该函数需要上述每一个服务接口(即 IExampleTransientServiceIExampleScopedService 和 IExampleSingletonService)。 对象会公开一个方法,使用者可通过该方法使用给定的 lifetimeDetails 参数报告服务。 被调用时,ReportServiceLifetimeDetails 方法会使用服务生存期消息记录每个服务的唯一标识符。 日志消息有助于直观呈现服务生存期。

    6、为 DI 注册服务

    使用以下代码更新 Program.cs:

    1. using Microsoft.Extensions.DependencyInjection;
    2. using Microsoft.Extensions.Hosting;
    3. using ConsoleDI.Example;
    4. HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
    5. builder.Services.AddTransient<IExampleTransientService, ExampleTransientService>();
    6. builder.Services.AddScoped<IExampleScopedService, ExampleScopedService>();
    7. builder.Services.AddSingleton<IExampleSingletonService, ExampleSingletonService>();
    8. builder.Services.AddTransient<ServiceLifetimeReporter>();
    9. using IHost host = builder.Build();
    10. ExemplifyServiceLifetime(host.Services, "Lifetime 1");
    11. ExemplifyServiceLifetime(host.Services, "Lifetime 2");
    12. await host.RunAsync();
    13. static void ExemplifyServiceLifetime(IServiceProvider hostProvider, string lifetime)
    14. {
    15. using IServiceScope serviceScope = hostProvider.CreateScope();
    16. IServiceProvider provider = serviceScope.ServiceProvider;
    17. ServiceLifetimeReporter logger = provider.GetRequiredService<ServiceLifetimeReporter>();
    18. logger.ReportServiceLifetimeDetails(
    19. $"{lifetime}: Call 1 to provider.GetRequiredService()");
    20. Console.WriteLine("...");
    21. logger = provider.GetRequiredService<ServiceLifetimeReporter>();
    22. logger.ReportServiceLifetimeDetails(
    23. $"{lifetime}: Call 2 to provider.GetRequiredService()");
    24. Console.WriteLine();
    25. }

    每个 services.Add{LIFETIME}<{SERVICE}> 扩展方法添加(并可能配置)服务。 我们建议应用遵循此约定。 将扩展方法置于 Microsoft.Extensions.DependencyInjection 命名空间中以封装服务注册的组。 还包括用于 DI 扩展方法的命名空间部分 Microsoft.Extensions.DependencyInjection

    • 允许在不添加其他 using 块的情况下在 IntelliSense 中显示它们。
    • 在通常会调用这些扩展方法的 Program 或 Startup 类中,避免出现过多的 using 语句。

    应用会执行以下操作:

    7、结束语

    在此示例应用中,你创建了多个接口和相应的实现。 其中每个服务都唯一标识并与 ServiceLifetime 配对。 示例应用演示了如何针对接口注册服务实现,以及如何在没有支持接口的情况下注册纯类。 然后,示例应用演示了如何在运行时解析定义为构造函数参数的依赖项。

    运行该应用时,它会显示如下所示的输出:

    1. // Sample output:
    2. // Lifetime 1: Call 1 to provider.GetRequiredService<ServiceLifetimeReporter>()
    3. // IExampleTransientService: d08a27fa-87d2-4a06-98d7-2773af886125 (Always different)
    4. // IExampleScopedService: 402c83c9-b4ed-4be1-b78c-86be1b1d908d (Changes only with lifetime)
    5. // IExampleSingletonService: a61f1ff4-0b14-4508-bd41-21d852484a7b (Always the same)
    6. // ...
    7. // Lifetime 1: Call 2 to provider.GetRequiredService<ServiceLifetimeReporter>()
    8. // IExampleTransientService: b43d68fb-2c7b-4a9b-8f02-fc507c164326 (Always different)
    9. // IExampleScopedService: 402c83c9-b4ed-4be1-b78c-86be1b1d908d (Changes only with lifetime)
    10. // IExampleSingletonService: a61f1ff4-0b14-4508-bd41-21d852484a7b (Always the same)
    11. //
    12. // Lifetime 2: Call 1 to provider.GetRequiredService<ServiceLifetimeReporter>()
    13. // IExampleTransientService: f3856b59-ab3f-4bbd-876f-7bab0013d392 (Always different)
    14. // IExampleScopedService: bba80089-1157-4041-936d-e96d81dd9d1c (Changes only with lifetime)
    15. // IExampleSingletonService: a61f1ff4-0b14-4508-bd41-21d852484a7b (Always the same)
    16. // ...
    17. // Lifetime 2: Call 2 to provider.GetRequiredService<ServiceLifetimeReporter>()
    18. // IExampleTransientService: a8015c6a-08cd-4799-9ec3-2f2af9cbbfd2 (Always different)
    19. // IExampleScopedService: bba80089-1157-4041-936d-e96d81dd9d1c (Changes only with lifetime)
    20. // IExampleSingletonService: a61f1ff4-0b14-4508-bd41-21d852484a7b (Always the same)

    在应用输出中,可看到:

    • Transient 服务总是不同的,每次检索服务时,都会创建一个新实例。
    • Scoped 服务只会随着新范围而改变,但在一个范围中是相同的实例。
    • Singleton 服务总是相同的,新实例仅被创建一次。
  • 相关阅读:
    推动智行生态融合!Flyme 迎来大动作,魅族与星纪时代布局初显
    【图像分类】2022-CMT CVPR
    Dubbo的整体框架和主要模块
    Web of Science怎么用有哪些功能
    antv G6 开发踩坑记录
    Prompts(一)
    【算法】道路与航线(保姆级题解)
    exit(0),exit(1),exit(EXIT_SUCCESS),exit(EXIT_FAILURE)
    面试官:说一说CyclicBarrier的妙用!我:这个没用过
    Java图书管理系统实训报告
  • 原文地址:https://blog.csdn.net/m0_51887793/article/details/134256209