五、.NET 依赖注入

企业应用可能面临的一个大问题是将不同的元素连接在一起并管理它们的生命周期的复杂性。为了解决这个问题,我们使用控制反转 ( IoC )原理,该原理建议消除对象之间的依赖性。通过委派控制流,IoC 使程序可扩展并增加了模块化。事件、回调委托、观察者模式和依赖注入 ( DI )是实现 IoC 的一些方法。

在本章中,我们将了解以下内容:

  • 什么是 DI?
  • ASP.NET Core 5 中的直接投资
  • 管理应用服务
  • 使用第三方容器

到本章结束时,您将对直接投资有一个很好的了解,充分利用直接投资.NET 5 应用,ASP.NET Core 5 中提供的范围类型,以及如何在项目中利用它们。

技术要求

对微软的基本了解.NET 是必需的。

本章使用的代码可以在https://github . com/PacktPublishing/Enterprise-Application-Development-with-C-Sharp-9-and-找到.NET-5/树/主/章节 05/

什么是 DI?

DI 是一种对象接收它所依赖的对象的技术。DI 模式实现了在 第 1 章设计和架构企业应用中作为固体设计原则的一部分所涵盖的 DI 原则。随着 DI 的使用,代码将变得更加可维护、可读、可测试和可扩展。

DI 是帮助实现更好的可维护代码的最著名的方法之一。

DI 涉及三个实体,如图图 5.1 :

Figure 5.1 – DI relationship

图 5.1–直接投资关系

注入器创建服务的实例,并将其注入到客户端对象中。客户端依赖注入的服务来执行其操作。例如,在我们将要构建的企业应用中,IOrderRepository负责Order实体上的 CRUD 操作。IOrderRepository将由运行时实例化并注入OrderController

IoC 容器,也称被称为 DI 容器,是实现自动 DI 的框架。在图 5.1 中,称为喷油器。负责创建或引用依赖关系、注入客户端

现在我们已经了解了什么是 DI,让我们了解一下 DI 的类型。

DI 的类型

有多种方法可以将服务注入依赖项。根据服务注入客户端对象的方式,DI 分为三种类型:

  • Constructor injection: Dependencies are injected through a constructor while instantiating the dependent. In the following example, the IWeatherProvider dependency is injected through the constructor parameter:

    cs public class WeatherService { private readonly IweatherProvider weatherProvider; public WeatherService(IWeatherProvider weatherProvider) => this.weatherProvider = weatherProvider; public WeatherForecast GetForeCast(string location) => this.weatherProvider. GetForecastOfLocation (location); }

    在前面的例子中,WeatherService依赖于IWeatherProvider,它是通过构造函数参数注入的。

  • Setter 注入:在 setter 注入的情况下,依赖者暴露一个 Setter 方法或者属性,注入者使用这个属性注入依赖者。在本例中,初始化WeatherService时不设置IWeatherProvider相关性。通过WeatherProvider属性岗位对象初始化

    ```cs
    public class WeatherService2
    {
    private IWeatherProvider _weatherProvider;
    public IWeatherProvider WeatherProvider
    {
    get => _weatherProvider == null ?
    throw new
    InvalidOperationException(
    "WeatherService is not
    initialized")
    _weatherProvider; set => _weatherProvider = value; } public WeatherForecast GetForeCast(string location) => this.WeatherProvider. GetForecastOfLocation(location); } ```

    设置 * Method injection: Another way to inject dependency is by passing it as a method parameter. In this example, the IWeatherProvider dependency is injected as a method parameter wherever required.

    在下面的代码片段中,IWeatherProvider服务通过GetForecast方法注入到WeatherService中:

    cs public class WeatherService { public WeatherForecast GetForecast( string location, IWeatherProvider weatherProvider) { if(weatherProvider == null) { throw new ArgumentNullException( nameof(weatherProvider)); } return weatherProvider. GetForecastOfLocation (location); } }

当类有依赖关系时,如果没有依赖关系,功能就无法工作,那么我们使用构造函数注入。此外,当类中的多个函数使用依赖关系时,我们使用构造函数注入。当类实例化后依赖关系可能改变时,我们使用属性注入。当依赖项的实现随着每次调用而改变时,使用方法注入。

在大多数情况下,构造函数注入将用于干净且解耦的代码。但是根据需要,我们还将利用方法和属性注入技术。

我们现在已经学习了 DI 的概念。让我们深入研究一下.NET 5 提供了实现 DI 的方法。

ASP.NET Core 5 的 DI

在本节中,我们将详细了解 ASP.NET Core 5 支持的直接投资。

.NET 5 自带内置的 IoC 容器,简化了 DI。这是附带的Microsoft.Extensions.DependencyInjection NuGet 包。ASP.NET Core 5 框架本身在很大程度上依赖于此。为了支持 DI,容器需要支持对对象/服务的三个基本操作:

  • 注册:容器应该有注册依赖项的规定。这将有助于将正确的类型映射到类,以便它可以创建正确的依赖关系实例。
  • 解析:容器应该通过创建依赖对象并将其注入依赖实例来解析依赖。IoC 容器通过传入所有必需的依赖项来管理注册对象的创建。
  • 处理:容器负责管理通过它创建的依赖项的生存期。

英寸 NET 5,术语服务指的是由容器管理的依赖关系。提供的服务.NET 5 Framework 被称为框架服务,例如、IConfigurationILoggerFactoryIWebHostEnvironment等等。开发人员为支持应用功能而创建的服务称为应用服务

为了启动应用,ASP.NET Core 5 框架注入了一些依赖项,这些依赖项被称为框架服务。当您创建一个 ASP.NET Core 5 web 应用时,Startup类被注入了所需的框架服务。当您创建一个 ASP.NET Core 5 网络项目时,您将看到如下定义的Startup类:

    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }
        public IConfiguration Configuration { get; }
        public void ConfigureServices(IserviceCollection
            services)
        {
            services.AddScoped<IWeatherProvider,
                WeatherProvider>();
        services.AddControllersWithViews();
        }
    }

在前面的例子中,Startup类通过构造函数注入被注入IConfigurationIConfiguration是一个框架服务,IoC 容器知道如何实例化,并通过构造函数注入来注入。IServiceCollectionIApplicationBuilderIWebHostEnvironment通过方法注射进行注射。

应用服务是由开发人员注入容器的那些服务。应用服务将通过IServiceCollectionConfigureService方法内注册。从前面的代码中可以看出,IWeatherProvider应用服务是通过服务注册的。

在本节中,我们已经了解了什么是框架服务和应用服务。在下一节中,我们将了解这些服务的生命周期以及如何管理它们。

注意

参考 第 10 章创建 ASP.NET Core 5 Web API ,了解更多Startup类中注入的框架服务。

了解服务生存期

当你注册一个有指定生存期的服务时,容器会根据指定的生存期自动处置对象。有三种类型的生存期可供 Microsoft DI 容器使用。T 嘿如下:

  • 瞬态:在这个生存期内,每次从服务容器请求对象时都会创建该对象。将此生存期用于无状态的轻量级服务。如果创建服务很耗时,这可能不是正确的范围,因为这会增加延迟。AddTransient扩展方法用于注册此寿命:

    cs public static IServiceCollection AddTransient(this IServiceCollection services, Type serviceType);

  • Singleton:Singleton 生存期允许容器在每个应用生命周期中只创建一个对象。每次请求时,我们都会得到相同的对象。当对服务有第一个请求时,或者当在注册时直接提供实现实例时,创建对象。当ServiceProvider在应用关闭时被处理掉时,注册为单例的服务被处理掉。AddSingleton扩展方法用于注册此生:

    cs public static IServiceCollection AddSingleton(this IServiceCollection services, Type serviceType);

  • 作用域:通过使用这个生存期,服务将只在客户端请求作用域中创建一次。这在 ASP.NET Core 5 中特别有用,在那里,每个 HTTP 请求创建一个对象实例。像实体框架核心DbContext这样的服务是用作用域生存期注册的。AddScoped扩展方法用于注册作用域生存期范围:

    cs public static IServiceCollection AddScoped(this IServiceCollection services, Type serviceType);

在应用开发中,需要明智地选择生存期类型。一个服务不应该依赖于一个寿命比它自己的寿命短的服务。例如,注册为单例的服务不应该依赖于注册为临时的服务。下表显示了哪些生存期可以安全地依赖于哪些其他生存期范围:

Table 5.1

表 5.1

作为开发人员,您不需要担心范围验证。当环境设置为开发时,内置范围验证在启动期间在 ASP.NET Core 5 中完成。在配置错误的情况下,应用启动时会抛出InvalidOperationException。这可以通过在注册ServiceProvider时启用ValidateScopes选项来明确打开,如下代码所示。在以下代码中,在创建宿主构建器时,ValidateScopes被设置为true以打开范围验证:

public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
            .UseDefaultServiceProvider(opt => {
 opt.ValidateScopes = true; })
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                }); 

让我们创建一个 ASP.NET Core 5 web 应用来了解服务生命周期。我们将创建不同的服务,并将它们注册到单一的、有作用域的和短暂的生存期作用域中,并观察它们的行为:

  1. 创建一个新的 ASP.NET Core web 应用,命名为DISampleWeb
  2. Create a new project folder with the name Services and add three classes: ScopedService, SingletonService, and TransientService. Add the following code (all these services will be the same without any real code in them; we just register them with different lifetime scopes as per their name):

    注意

    在下面的示例中,接口和类在一个文件中定义。这纯粹是为了演示。理想情况下,接口和类将在两个不同的类中定义。

    • ScopedService.cs:这个类将注册到作用域生存期范围:

      cs public interface IScopedService { } public class ScopedService : IScopedService { }

    • SingletonService.cs:该类将注册到单体生存期范围:

      cs public interface ISingletonService { } public class SingletonService : ISingletonService { }

    • TransientService.cs:这个类将在

      cs public interface ITransientService { } public class TransientService : ITransientService { }

      瞬态寿命范围内注册 3. 现在,用ConfigureServices方法在startup.cs注册这些服务,如下所示:

    cs public void ConfigureServices(IserviceCollection services) { //Register as Scoped services.AddScoped<IScopedService, ScopedService>(); //Register as Singleton services.AddSingleton<ISingletonService, SingletonService>(); //Register as Transient services.AddTransient<ITransientService, TransientService>(); services.AddControllersWithViews(); }

  3. Now, add the HomeViewModel model class under the Models folder, which will be used to show the data retrieved from the services registered previously:

    cs public class HomeViewModel { public int Singleton { get; set; } public int Scoped { get; set; } public int Scoped2 { get; internal set; } public int Transient { get; set; } public int Transient2 { get; internal set; } }

    由于我们在 ASP.NET Core 5 IoC 容器中注册了ScopedServiceSingletonServiceTransientService,我们将通过构造函数注入获得这些服务。

  4. Now, we will add code to get these services in HomeController and Views to show the data retrieved from these objects on the home page. Modify the home controller to get two instances of ScopedService and TransientService and set ViewModel with the Hash code of the service object:

    Figure 5.2 – Solution structure

    图 5.2–解决方案结构

    注意

    GetHashCode方法返回对象的哈希码。这将因实例而异。

  5. 修改HomeController的构造函数,接受注册的服务,定义私有字段引用服务实例:

    cs private readonly ILogger<HomeController> _logger; private readonly IScopedService scopedService; private readonly IscopedService scopedService2; private readonly IsingletonService singletonService; private readonly ItransientService transientService; private readonly ItransientService transientService2; public HomeController(ILogger<HomeController> logger, IScopedService scopedService, IScopedService scopedService2, ISingletonService singletonService, ITransientService transientService, ITransientService transientService2) { _logger = logger; this.scopedService = scopedService; this.scopedService2 = scopedService2; this.singletonService = singletonService; this.transientService = transientService; this.transientService2 = transientService2; }

  6. 现在,修改在HomeController下的Index方法来设置HomeViewModel :

    cs public IActionResult Index() { var viewModel = new HomeViewModel { Scoped = scopedService.GetHashCode(), Scoped2 = scopedService2. GetHashCode(), Singleton = singletonService. GetHashCode(), Transient = transientService. GetHashCode(), Transient2 = transientService2. GetHashCode(), }; return View(viewModel); }

  7. 现在,修改~/Views/Home文件夹下的Index.cshtml,如图所示,在页面上显示HomeViewModel:

    cs @model HomeViewModel @{ ViewData["Title"] = "Home Page"; } <h2 class="text-success">Singleton.</h2> <p> <strong>ID:</strong> <code>@Model.Singleton </code> </p> <h2 class="text-success">Scoped instance 1</h2> <p> <strong>ID:</strong> <code>@Model.Scoped</code> </p> <h2 class="text-success">Scoped instance 2</h2> <p> <strong>ID:</strong> <code>@Model.Scoped2</code> </p> <h2 class="text-success">Transient instance 1</h2> <p> <strong>ID:</strong> <code>@Model.Transient</code> </p> <h2 class="text-success">Transient instance 2</h2> <p> <strong>ID:</strong> <code>@Model.Transient2</code> </p>

  8. Now, run the application. You will see an output as follows:

    Figure 5.3 – Sample output on first run

    图 5.3–首次运行时的样本输出

    如果我们观察输出,为两个ScopedService实例显示的标识是相同的。这是因为每个请求范围只为IScopedService创建了一个对象。

    对于这两种服务,临时服务的标识是不同的。正如我们所了解到的,这是因为对 IoC 容器的每个请求都会创建一个新的实例。

  9. 现在,再次刷新页面。我们看到如下输出:

Figure 5.4 – Sample output on second run

图 5.4–第二次运行的样本输出

如果我们比较一下图 5.3图 5.4 中的输出,我们可以注意到SingletonService的 ID 没有变化。这是因为在应用的每个生命周期中,只为单例对象创建一个对象。到目前为止,我们已经看到了如何基于注册来管理服务生存期。了解何时处置对象也很重要。在下一节中,我们将了解服务的处置。

服务处置

正如我们在本章前面所学的,物体的处理是国际奥委会容器的责任。容器在那些实现IDisposable的服务上调用Dispose方法。由容器创建的服务永远不应该由开发人员显式处置。

考虑下面的代码片段,其中SingletonService实例在单例范围内注册:

//Register as Singleton
services.AddSingleton(new DisposableSingletonService());

在前面的代码中,我们创建了一个对象SingletonService,并将其注册到 IoC 容器中。服务实例不是由容器创建的。在这种情况下,IoC 容器不会处置对象。开发商有责任处置它。当IHostApplicationLifetime触发ApplicationStopping事件时,我们可以处理对象,如下代码所示:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IHostApplicationLifetime applicationLifetime)
{
 applicationLifetime.ApplicationStopping.Register(() => {
 _disposableSingletonService.Dispose();
 });
}

在前面的代码中,运行时将IHostApplicationLifetime注入到Startup类中的Configure方法中。该界面允许消费者收到ApplicationStartedApplicationStoppedApplicationStopping应用生命周期事件的通知。为了处置单例对象,我们将通过注册到ApplicationStopping生存期事件来调用处置方法。

注意

请参考以下微软文档,了解更多关于 DI 指南的信息:

https://docs . Microsoft . com/en-us/dotnet/core/extensions/dependency-injection-guidelines

到目前为止,我们已经研究了服务生存期以及它们在中的处理方式.NET 5。在下一节中,我们将学习管理应用服务。

管理应用服务

在 ASP.NET Core 5 中,当MvcMiddleware收到请求时,路由用于选择控制器和动作方式。IControllerActivator创建控制器的实例,并从 DI 容器加载构造函数参数。

在上一节了解服务生存期中,我们看到了应用服务是如何注册的,以及它们的生存期是如何管理的。在这个例子中,服务是通过构造函数注入的,这就是构造函数注入。在本节中,我们将看到如何实现方法注入,以及如何通过不同的方式在 ASP.NET Core 5 IoC 容器中注册和访问应用服务。

通过方法注入访问注册服务

在前面的小节中,我们看到了如何将依赖服务注入到控制器构造函数中,并将引用存储在用于调用依赖的方法/应用编程接口的本地字段中。

有时,我们不希望依赖服务在控制器的所有操作中都可用。在这种情况下,可以通过方法注入来注入服务。这是通过使参数具有FromService属性来完成的,如下例所示:

public IActionResult Index([FromServices] ISingletonService singletonService2)
{
}

在下一节中,我们将看到同一服务类型的多个实例的注册以及如何访问它们。

注册多个实例

如果一个以上的实现注册了相同的服务类型,最后一次注册将优先于所有以前的注册。考虑以下服务注册,其中IWeatherForeCastService服务注册有两个实现:WeatherForeCastServiceWeatherForeCastServiceV2:

services.AddScoped<IWeatherForcastService, WeatherForcastService>();
services.AddScoped<IWeatherForcastService, WeatherForcastServiceV2>();

现在,当控制器发出请求IWeatherForeCastService实例时,WeatherForeCastServiceV2实例将被服务:

public WeatherForecastController(ILogger<WeatherForecastController> logger, IWeatherForcastService weatherForcastService)
        {
            _logger = logger;
            this.weatherForcastService = weatherForcastService;
        }

在前面的例子中,WeatherForecastV2的注册可能会覆盖WeatherForecastService的先前注册。然而,ASP.NET Core 5 国际奥委会集装箱将拥有IWeatherForeCastService的所有注册。要获取所有注册,获取服务为IEnumerable:

public WeatherForecastController(ILogger<WeatherForecastController> logger, IEnumerable<IWeatherForcastService> weatherForcastService)
{
            _logger = logger;
            this.weatherForcastService = weatherForcastService;
}

这在场景中可能很有用,例如执行规则引擎,在这里我们希望在处理请求之前运行所有规则。该组规则将通过Startup调用中的ConfigureServices方法进行配置。所以,在未来,当有一个规则的增加或一个现有规则的删除时,变化将只在ConfigureServices发生。

TryAdd 的使用

在本节中,我们将了解如何避免意外覆盖已经注册的服务。

TryAdd扩展方法仅在同一服务不存在注册时注册服务。TryAdd扩展方法适用于所有寿命范围(TyrAddScopedTryAddSingletonTryAddTransient)。

通过如下代码所示的服务注册,当有IweatherForecastService请求时,IoC Container 服务WeatherForecastService,而不是WeatherForecastServiceV2:

services.AddScoped<IWeatherForcastService, WeatherForcastService>();
services.TryAddScoped<IWeatherForcastService, WeatherForcastServiceV2>();

为了克服重复注册可能产生的副作用,总是建议使用TryAdd扩展方法来注册服务。

现在,让我们看看如何替换已经注册的服务。

替换现有注册

ASP.NET Core 5 国际奥委会容器提供了一种替代现有注册的方法。在以下示例中,IWeatherForeCastService最初在WeatherForeCastService注册。然后替换为WeatherForeCastServiceV2:

services.TryAddScoped<IWeatherForcastService, WeatherForcastService>();
            services.Replace(ServiceDescriptor.Scoped<IWeatherForcastService, WeatherForcastServiceV2>());

WeatherForeCastServiceV2Replace实例一样,实现服务于WeatherForeCastController的构造器。在下面的代码片段中,不同于中的注册多个实例部分,我们将在weatherForcastService构造函数变量中只看到一个对象:

public WeatherForecastController(ILogger<WeatherForecastController> logger, IEnumerable<IWeatherForcastService> weatherForcastService)
{
            _logger = logger;
            this.weatherForcastService = weatherForcastService;
}

在下一节中,我们将看到如何删除已注册的服务。

删除现有注册

如果您希望删除现有的注册,您可以使用 ASP.NET Core 5 国际奥委会容器提供的Remove扩展方法。您可以使用RemoveAll方法删除与服务相关的所有注册,如以下代码片段所示:

//Removes the first registration of IWeatherForcastService           services.Remove(ServiceDescriptor.Scoped<IWeatherForcastService, WeatherForcastService>());

在前面的代码片段中,Remove方法从容器中移除了WeatherForeCastService实现的注册:

services.RemoveAll<IWeatherForcastService>();

到目前为止,我们已经看到了如何使用复杂的服务。但是当涉及到泛型开放类型时,很难注册每个泛型构造类型。在下一节中,我们将学习如何处理通用开放类型服务。

注意

要了解更多关于泛型类型的信息,您可以参考以下网站:

https://docs . Microsoft . com/en-us/dotnet/csharp/language-reference/language-specification/type

注册泛型

本节将带您了解使用 DI 处理泛型类型服务的。

在泛型类型的情况下,为正在使用的每种实现类型注册服务是没有意义的。ASP.NET Core 5 IoC 容器提供了一种简化泛型类型注册的方法。框架本身已经提供的一种类型是ILogger:

services.TryAdd(ServiceDescriptor.Singleton(typeof(ILogger<>), typeof(Logger<>))); 

注意

您可以访问https://github . com/ASPnet/Logging/blob/master/src/Microsoft 查阅。Extensions.Logging/LoggingServiceCollectionExtensions.cs

使用泛型的另一个用例是数据访问层使用的泛型存储库模式。

有了我们所有的注册,ConfigureServices方法会变大,不再可读。下一部分将帮助您了解如何解决这个问题。

代码可读性的扩展方法

为了使代码看起来更易读,ASP.NET Core 5 框架中遵循的模式是创建一个带有服务注册逻辑分组的扩展方法。下面的代码尝试使用扩展方法对通知相关服务进行分组和注册。一般的做法是使用Microsoft.Extensions.DependencyInjection命名空间来定义服务注册扩展方法。这将使开发人员仅通过使用Microsoft.Extensions.DependencyInjection名称空间就可以使用与 DI 相关的所有功能。

在下面的代码片段中,通知相关服务注册到了AddNotificationServices:

namespace Microsoft.Extensions.DependencyInjection
{
    public static class NotificationServicesServiceCollectionExtension
    {
        public static IServiceCollection AddNotificationServices(this IServiceCollection services)
        {
            services.TryAddScoped<INotificationService, EmailNotificationService>();
 services.TryAddScoped<INotificationService, SMSNotificationService>();
 return services;
        }
    }
}

现在扩展方法已经创建,我们可以使用AddNotificationServices方法在ConfigureServices下注册通知服务。这将使ConfigureServices变得更加易读:

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddNotificationServices();
        }

我们已经看到了如何将服务注入控制器和其他类。在下一节中,我们将学习如何将服务注入视图。

剃刀页中的 DI

MVC 中视图的目的是显示数据。大多数情况下,视图中显示的数据是从控制器传递的。考虑到关注点分离原则,建议从控制器传递所有需要的数据。但是可能有些情况下,我们希望从本地化和遥测服务等页面查看特定服务。使用 Razor 视图支持的 DI,我们可以将这样的服务注入到视图中。

要了解如何将服务注入视图,让我们修改在前面几章中创建的DISampleWeb应用。如果设置了飞行标志,我们将修改DISampleWeb应用以在主页上显示附加内容。将如下代码片段所示的isFlightOn配置添加到appsettings.json:

{
  "AllowedHosts": "*",
 "isFlightOn": "true"
}

现在,修改Home下的索引视图,显示Flight下的内容,如下面的代码片段所示:

@using Microsoft.Extensions.Configuration
@inject IConfiguration Configuration
@{
   string isFlightOn = Configuration["isFlightOn"];
   if (string.Equals(isFlightOn, "true", StringComparison.OrdinalIgnoreCase))
   {
       <h1>
        <strong>Flight content</strong>
       </h1> 
   }
}

这里,提供读取配置文件功能的IConfiguration服务使用@inject关键字注入到 Razor 视图中。注入的配置服务用于获取配置并根据设置显示附加内容。我们可以使用@inject关键字将任何在IServiceCollection注册的服务注入到 Razor 视图中。

到目前为止,我们已经看到了如何利用?NET 5 内置的 IoC 容器。在下一节中,我们将了解如何利用第三方容器。

使用第三方容器

虽然内置容器对于我们的大多数场景来说已经足够了.NET 5 提供了一种与第三方容器集成的方法,如果需要,可以利用这些容器。

让我们仔细看看框架是如何连接服务的。当Startup类在Program.csHostBuilder注册时.NET 框架使用反射来识别和调用ConfigureConfigureServices方法。

以下是 ASP.NET Core 5 中StartupLoader类的一个片段:

var configureMethod = FindConfigureDelegate(startupType, environmentName);
var servicesMethod = FindConfigureServicesDelegate(startupType, environmentName);
var configureContainerMethod = FindConfigureContainerDelegate(startupType, environmentName);

从前面的代码中,我们可以看到,前两个方法FindConfigureDelegateFindConfigureServicesDelegate是寻找ConfigureConfigureServices的方法。

最后一行是ConfigureContainer。我们可以在Startup类中定义ConfigureContainer方法,将服务配置到第三方容器中。

以下是 ASP.NET Core 5 可用的一些流行的 DI 框架:

尽管这些框架之间有一些不同,但这些框架中通常都有功能对等。决定框架选择的主要是开发人员的专业知识。

在下一节中,让我们看看如何利用 Autofac IoC 容器。

Autofac IoC 容器

Autofac 是开发者社区中最受欢迎的 IoC 容器之一。像任何其他 IoC 容器一样,它管理类之间的依赖关系,以便应用在复杂性和规模增长时易于更改。让我们通过创建一个小应用来学习如何使用自动交流:

  1. 创建一个新的 ASP。通过选择应用编程接口模板并将其命名为AutofacSample,NET Core web 应用。
  2. Add the Autofac.Extensions.DependencyInjection NuGet package reference to the AutofacSample project:

    Figure 5.5 – Add the Autofac.Extensions.DependencyInjection NuGet package

    图 5.5–添加自动空调。扩展。依赖注入获取包

  3. 我们需要向HostBuilder注册AutofacServiceProviderFactory,这样运行时将使用 Autofac IoC 容器。在Program.cs中,注册 Autofac 服务提供商工厂,如以下代码片段所示:

    cs public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .UseServiceProviderFactory(new AutofacServiceProviderFactory()) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); });

  4. 让我们继续为我们的项目添加一个简单的天气预报服务。新建一个名为Service的解决方案文件夹,添加IWeatherProviderWeatherProvider界面,如下。WeatherProvider班生成第二天的随机预报:

    cs public interface IWeatherProvider { IEnumerable<WeatherForecast> GetForecast(); } public class WeatherProvider : IWeatherProvider { private static readonly string[] Summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" }; public IEnumerable<WeatherForecast> GetForecast() { var rng = new Random(); return Enumerable.Range(1, 1).Select(index => new WeatherForecast { Date = DateTime.Now.AddDays(index), TemperatureC = rng.Next(-20, 55), Summary = Summaries[rng.Next(Summaries.Length)] }).ToArray(); } }

  5. 现在,让我们用 Autofac 容器注册IWeatherProvider服务。将ConfigureContainer方法添加到Startup.cs中的Startup类中,以注册IWeatherProvider,实现:

    cs public void ConfigureContainer(ContainerBuilder builder) { builder.RegisterType<WeatherProvider>() .As<IWeatherProvider>(); }

  6. To get the IweatherForecast service injected into the WeatherForecastController controller, add a constructor argument as shown in the following code. Also, modify the Get action to make use of the IWeatherProvider services:

    cs public class WeatherForecastController : ControllerBase { private readonly ILogger<WeatherForecastController> _logger; private readonly IWeatherProvider weatherProvider; public WeatherForecastController( ILogger<WeatherForecastController> logger, IWeatherProvider weatherProvider) { _logger = logger; this.weatherProvider = weatherProvider; } [HttpGet] public IEnumerable<WeatherForecast> Get() { return weatherProvider.GetForecast(); } }

    现在,当您运行项目时,您将在浏览器中看到如下输出:

Figure 5.6 – Final output for the container

图 5.6–容器的最终输出

在前面的示例中,我们已经看到使用第三方 Autofac IoC 容器来代替提供的默认容器.NET 5。

总结

本章向您介绍了 DI 的概念,它有助于编写松散耦合、更易测试和更易读的代码。本章介绍了 DI 的类型以及 ASP.NET Core 5 如何支持它们。我们还看到了如何使用不同类型的注册来管理对象生存期。本章还向您介绍了一些流行的第三方 IoC 容器,供您进一步探索。我们将使用本章中学习的概念来构建我们的电子商务应用。在 第 15 章测试中,我们也将看到 DI 如何帮助测试性。

正如 第 1 章设计和架构企业应用所建议的,在关注点分离/单一责任架构部分,我们将始终尝试通过接口注册服务。这将有助于在不改变客户端实现的情况下随时改变具体的实现。

在下一章中,我们将学习如何配置.NET 5,并了解不同的配置,同时学习如何建立一个自定义的。

问题

  1. Which of the following is not a framework service?

    a.IConfiguration

    b.IApplicationBuilder

    c.IWeatherService

    d.IWebHostEnvironment

  2. True or false: DI is one of the mechanisms to achieve IoC?

    a.真实的

    b.错误的

  3. True or false: An injected service can depend on a service that has a shorter life span than its own?

    a.真实的

    b.错误的

  4. Which of the following is not a valid lifetime scope of ASP.NET Core 5 IoC Container?

    a.审视

    b.一个

    c.短暂的

    d.动态的