依赖关系注入

ASP.NET Core Blazor 依赖关系注入

作者: Rainer Stropek

重要

Blazor WebAssembly 为预览版状态

ASP.NET Core 3.0 支持 Blazor Server 。 Blazor WebAssembly 在 ASP.NET Core 3.1 中为预览版。

Blazor 支持依赖关系注入(DI) 应用可以通过将内置服务注入组件来使用这些服务。 应用还可以定义和注册自定义服务,并通过 DI 使其在整个应用中可用。

DI 是一种用于访问在中心位置配置的服务的技术。 在 Blazor 应用中,这种方法可用于:

  • 跨多个组件(称为单独服务)共享服务类的单个实例。
  • 使用引用抽象将组件与具体服务类分离。 例如,请考虑用于访问应用中的数据的接口 IDataAccess 接口由具体 DataAccess 类实现并在应用服务容器中注册为服务。 当组件使用 DI 接收 IDataAccess 实现时,组件不与具体类型耦合。 可以交换实现,这可能是单元测试中的模拟实现。

默认服务

默认服务会自动添加到应用的服务集合。

服务 生存期 描述
HttpClient 单一实例 提供用于发送 HTTP 请求以及从 URI 所标识资源接收 HTTP 响应的方法。

Blazor WebAssembly 应用程序中 HttpClient 的实例使用浏览器在后台处理 HTTP 流量。

默认情况下,Blazor 服务器应用不包括配置为服务的 HttpClient 向 Blazor 服务器应用提供 HttpClient

有关更多信息,请参见从 ASP.NET Core 调用 web API Blazor
IJSRuntime Singleton (Blazor WebAssembly)
作用域(Blazor 服务器)
表示在其中调度 JavaScript 调用的 JavaScript 运行时的实例。 有关更多信息,请参见<xref:blazor/javascript-interop>。
NavigationManager Singleton (Blazor WebAssembly)
作用域(Blazor 服务器)
包含用于处理 Uri 和导航状态的帮助器。 有关详细信息,请参阅URI 和导航状态帮助程序。

自定义服务提供程序不会自动提供表中列出的默认服务。 如果使用自定义服务提供程序并且需要表中所示的任何服务,请将所需服务添加到新的服务提供程序中。

向应用程序添加服务

Blazor WebAssembly

Program.csMain 方法中配置应用服务集合的服务。 在下面的示例中,为 IMyDependency注册了 MyDependency 实现:

public class Program
{
    public static async Task Main(string[] args)
    {
        var builder = WebAssemblyHostBuilder.CreateDefault(args);
        builder.Services.AddSingleton<IMyDependency, MyDependency>();
        builder.RootComponents.Add<App>("app");

        await builder.Build().RunAsync();
    }
}

构建主机后,在呈现任何组件之前,可以从根 DI 范围访问服务。 这可用于在呈现内容之前运行初始化逻辑:

public class Program
{
    public static async Task Main(string[] args)
    {
        var builder = WebAssemblyHostBuilder.CreateDefault(args);
        builder.Services.AddSingleton<WeatherService>();
        builder.RootComponents.Add<App>("app");

        var host = builder.Build();

        var weatherService = host.Services.GetRequiredService<WeatherService>();
        await weatherService.InitializeWeatherAsync();

        await host.RunAsync();
    }
}

主机还提供应用的中央配置实例。 基于前面的示例,天气服务的 URL 将从默认配置源(例如appsettings)传递到 InitializeWeatherAsync

public class Program
{
    public static async Task Main(string[] args)
    {
        var builder = WebAssemblyHostBuilder.CreateDefault(args);
        builder.Services.AddSingleton<WeatherService>();
        builder.RootComponents.Add<App>("app");

        var host = builder.Build();

        var weatherService = host.Services.GetRequiredService<WeatherService>();
        await weatherService.InitializeWeatherAsync(
            host.Configuration["WeatherServiceUrl"]);

        await host.RunAsync();
    }
}

Blazor 服务器

创建新应用后,请检查 Startup.ConfigureServices 方法:

public void ConfigureServices(IServiceCollection services)
{
    // Add custom services here
}

ConfigureServices 方法传递 IServiceCollection,它是服务描述符对象(ServiceDescriptor)的列表。 通过向服务集合提供服务描述符来添加服务。 下面的示例演示了 IDataAccess 接口的概念及其具体实现 DataAccess

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<IDataAccess, DataAccess>();
}

服务生存期

可以将服务配置为下表中所示的生存期。

生存期 描述
Scoped Blazor WebAssembly 应用当前没有 DI 作用域的概念。 Scoped注册的服务的行为类似于 Singleton 服务。 但是,Blazor Server 宿主模型支持 Scoped 生存期。 在 Blazor Server apps 中,作用域内服务注册的范围为连接 出于此原因,使用作用域内服务的目的是应该作用于当前用户的服务,即使当前目的是在浏览器中运行客户端。
Singleton DI 创建服务的单个实例 需要 Singleton 服务的所有组件均可接收同一服务的实例。
Transient 每当组件从服务容器获取 Transient 服务的实例时,它都会接收服务的新实例

DI 系统基于 ASP.NET Core 中的 DI 系统。 有关更多信息,请参见在 ASP.NET Core 依赖注入

在组件中请求服务

将服务添加到服务集合后,使用@插入Razor 指令将服务注入到组件。 @inject 有两个参数:

  • 键入要注入的服务的类型 –。
  • 属性 – 接收插入的应用服务的属性的名称。 属性不需要手动创建。 编译器将创建属性。

有关更多信息,请参见在 ASP.NET Core 中将依赖项注入到视图

使用多个 @inject 语句注入不同的服务。

下面的示例说明如何使用 @inject 将实现 Services.IDataAccess 的服务注入组件的属性 DataRepository 请注意代码如何只使用 IDataAccess 抽象:

@page "/customer-list"
@using Services
@inject IDataAccess DataRepository

@if (_customers != null)
{
    <ul>
        @foreach (var customer in _customers)
        {
            <li>@customer.FirstName @customer.LastName</li>
        }
    </ul>
}

@code {
    private IReadOnlyList<Customer> _customers;

    protected override async Task OnInitializedAsync()
    {
        _customers = await DataRepository.GetAllCustomersAsync();
    }
}

在内部,生成的属性(DataRepository)使用 InjectAttribute 属性。 通常不会直接使用此属性。 如果基类对于组件是必需的,并且插入的属性也是基类所必需的,请手动添加 InjectAttribute

public class ComponentBase : IComponent
{
    // DI works even if using the InjectAttribute in a component's base class.
    [Inject]
    protected IDataAccess DataRepository { get; set; }
    ...
}

在派生自基类的组件中,不需要 @inject 指令。 基类的 InjectAttribute 是足够的:

@page "/demo"
@inherits ComponentBase

<h1>Demo Component</h1>

在服务中使用 DI

复杂服务可能需要其他服务。 在前面的示例中,DataAccess 可能需要 HttpClient 的默认服务。 @inject (或 InjectAttribute)不能在服务中使用。 必须改为使用构造函数注入 通过将参数添加到服务的构造函数中,添加了所需的服务。 当 DI 创建服务时,它将在构造函数中识别它所需要的服务,并相应地提供这些服务。

public class DataAccess : IDataAccess
{
    // The constructor receives an HttpClient via dependency
    // injection. HttpClient is a default service.
    public DataAccess(HttpClient client)
    {
        ...
    }
}

构造函数注入的先决条件:

  • 一个构造函数必须存在,其参数可以全部通过 DI 完成。 如果指定默认值,则不允许使用 DI 未涵盖的其他参数。
  • 适用的构造函数必须是公共的。
  • 必须存在一个适用的构造函数。 如果出现多义性,DI 会引发异常。

用于管理 DI 作用域的实用工具基组件类

在 ASP.NET Core 应用中,作用域内服务通常作用于当前请求。 请求完成后,DI 系统将释放任何作用域内或暂时性的服务。 在 Blazor Server apps 中,请求范围将在客户端连接期间持续,这可能会导致暂时性和作用域内服务的运行时间比预期要长得多。

若要将服务范围限定于组件的生存期,可以使用 OwningComponentBaseOwningComponentBase<TService> 基类。 这些基类公开 IServiceProvider 类型的 ScopedServices 属性,该属性可解析范围限制在组件生存期内的服务。 若要创作从 Razor 中的基类继承的组件,请使用 @inherits 指令。

@page "/users"
@attribute [Authorize]
@inherits OwningComponentBase<Data.ApplicationDbContext>

<h1>Users (@Service.Users.Count())</h1>
<ul>
    @foreach (var user in Service.Users)
    {
        <li>@user.UserName</li>
    }
</ul>

备注

使用 @injectInjectAttribute 注入到组件中的服务不会在组件的作用域中创建,并绑定到请求范围。

其他资源