作者: 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 和导航状态帮助程序。 |
自定义服务提供程序不会自动提供表中列出的默认服务。 如果使用自定义服务提供程序并且需要表中所示的任何服务,请将所需服务添加到新的服务提供程序中。
在Program.cs的 Main
方法中配置应用服务集合的服务。 在下面的示例中,为 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(); } }
创建新应用后,请检查 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>
复杂服务可能需要其他服务。 在前面的示例中,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) { ... } }
构造函数注入的先决条件:
在 ASP.NET Core 应用中,作用域内服务通常作用于当前请求。 请求完成后,DI 系统将释放任何作用域内或暂时性的服务。 在 Blazor Server apps 中,请求范围将在客户端连接期间持续,这可能会导致暂时性和作用域内服务的运行时间比预期要长得多。
若要将服务范围限定于组件的生存期,可以使用 OwningComponentBase
和 OwningComponentBase<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>
备注
使用 @inject
或 InjectAttribute
注入到组件中的服务不会在组件的作用域中创建,并绑定到请求范围。