Razor 组件主要与 UI 表示有关。生成 UI 所涉及的部分工作通常涉及与数据存储进行通信,可能是通过 Web 服务。可能需要记录组件中的操作和事件。数据访问和日志记录不是 Razor 组件的主要关注点。执行日志记录或提取数据的代码不属于 UI 组件。在 Razor 组件中包含此类代码会违反单一原则。
调用 Web 服务或记录操作的代码应编写在单独的类(或多个类)中。这些类通常称为服务。这样做可以满足单一责任主体,但你仍然需要某种方式使这些服务可用于 Razor 组件。
您可以简单地在组件中实例化服务类:
@code{
DataAccessService service; = new DataAccessService();
List<Contact> contacts;
protected void OnInitialized()
{
service = new DataAccessService();
contacts = service.GetContacts();
}
...
}
然而,这种方法会导致紧密耦合。Razor 组件与数据访问服务的特定实现紧密耦合。由于组件与其服务之间关系的性质,它使组件难以进行单元测试:服务实现被硬编码到组件中。如果要在组件上运行单元测试,则需要找到一种方法,将类替换为实际上不与数据库或 Web 服务通信的假类或模拟类。现在想象一下,如果这个问题扩展到数十个或数百个组件。DataAccessService
依赖注入提供了解决此问题的方法。首先,使用抽象来表示服务。最常见的是,这种抽象采用接口的形式。然后,组件代码引用抽象而不是特定的实现:
@code{
IDataAccessService service;
List<Contact> contacts;
protected void OnInitialized()
{
contacts = service.GetContacts();
}
...
}
上面的片段有两个问题没有得到解答:
IDataAccessService
是如何变成 DataAccessService
的?要回答第一个问题,我们需要看一下服务注册。
服务注册涉及到将具体实现映射到抽象。这是通过向 ServiceCollection
添加条目来实现的, ServiceCollection
是 ServiceDescriptor
对象的中央注册表,表示服务类型、其实现和服务的生存期。
注册通常发生在应用程序的 Program
类中的 Main
方法中,其中应用程序的 ServiceCollection
可以通过 WebAssemblyHostBuilder
的 Services
属性访问:
public class Program
{
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("app");
builder.Services.AddSingleton<IDataAccessService, DataAccessService>();
}
}
在这个实例中, DataAccessService
类被注册为在注入 IDataAccessService
类型时使用的实现。它被注册为单例,这意味着在应用程序的生命周期内只有一个实例可用。
为了回答第二个悬而未决的问题,依赖注入系统负责在引用抽象时提供指定类型的实例,并管理其生存期。
服务是通过注射提供的,注射以不同的方式完成,具体取决于消费者。
@inject命令
@inject
指令用于使Razor组件可以使用服务。它后面是要注入的类型,以及该类型的实例:
@inject IDataAccessService service
...
@code{
List<Contact> contacts;
protected void OnInitialized()
{
contacts = service.GetContacts();
}
...
}
作为Razor组件的代码隐藏类的类-那些从 ComponentBase
派生或实现 IComponent
的类-不支持构造函数注入。通过将这些类添加为属性并使用 InjectAttribute
装饰它们,可以为这些类提供服务:
public class MyComponent : ComponentBase
{
[Inject] IDataAccessService service { get; set; }
protected List<Contact> Contacts { get; set; }
protected override void OnInitialized()
{
contacts = service.GetContacts();
}
}
非组件相关类支持标准构造函数注入:
public class MyService
{
private readonly IDataAccessService _service;
public MyService(IDataAccessService service) => _service = service;
public void SomeMethod()
{
var x = _service.GetContacts();
}
}
默认情况下会注册许多实用程序服务:
Service 服务 | Lifetime | Description 描述 |
---|---|---|
HttpClient | Singleton | 用于发出HTTP请求并接收其响应。WebAssembly版本使用Fetch API。 |
NavigationManager | Singleton | 包含使用URI和导航状态的帮助程序。 |
IJSRuntime | Singleton | 表示调度JavaScript调用的JavaScript运行时的实例。 |
生命周期
可以使用以下三种生存期作用域之一注册服务:单一实例、作用域和瞬态。
IDisposable
或维护状态的服务。