API 控制器是否使用与 Blazor 组件不同的服务实例?
对于作用域服务,答案肯定是是,对于单例服务,答案肯定是yes,但它会变得复杂。但我怀疑这不是您真正的问题,真正的 问题是如何为两者存储用户特定的数据。或者从外部服务修改特定用户的数据
为什么服务不同
创建一个新的控制器实例来处理每个 HTTP 请求。就 DI 而言,HTTP 请求定义了一个范围,因此为每个 HTTP 请求创建一个新的服务实例,并在处理完请求后释放。
这是一件好事,因为这意味着即使发生错误,也会释放昂贵的范围服务(如 DbContext)。对于 DbContext,这提供了开箱即用的 transaction-per-request 语义,无需任何额外代码。
使用 Blazor Server,事情更多复杂。在这种情况下,Blazor Server 为每个“用户电路”定义一个范围,该范围本质上是一个选项卡。这意味着只要标签处于活动状态,范围内的对象就会保持活动状态当导航发生在客户端上时。 MVC 部分的行为方式与 ASP.NET Core MVC/Web API 相同
这类似于桌面应用程序的行为方式,当人们认为像 DbContest 这样的范围服务将被“自动”处置时,可能会导致令人讨厌的意外。当您致电 SaveChangesAsync 时,您可能最终会保留您认为已被丢弃的更改。
因此,即使您尝试在 Blazor Server 中使用范围服务,您最终也可能会在 Blazor 组件中使用长期服务,而在 Web API 控制器中使用短期服务。
实际上,由于使用了不同的作用域,所以两个服务甚至找不到对方。
DI 和 Scope 在 Blazor Server 中的工作原理
这在ASP.NET Core Blazor dependency injection 中有记录。在Service Lifetime 中解释了作用域生命周期的差异:
Blazor 服务器托管模型支持跨 HTTP 请求的 Scoped 生命周期,但不支持客户端上加载的组件之间的 SignalR 连接/电路消息。
应用程序的 Razor 页面或 MVC 部分正常处理范围服务,并在页面或视图之间或从页面或视图导航到组件时在每个 HTTP 请求上重新创建服务。
在客户端上的组件之间导航时不会重建作用域服务,其中与服务器的通信是通过用户电路的 SignalR 连接进行的,而不是通过 HTTP 请求。
在客户端的以下组件场景中,由于为用户创建了新电路,因此重构了作用域服务:
- 用户关闭浏览器窗口。用户打开一个新窗口并导航回应用程序。
- 用户在浏览器窗口中关闭应用程序的最后一个选项卡。用户打开一个新选项卡并导航回应用程序。
- 用户选择浏览器的重新加载/刷新按钮。
在外部调用后通知用户的标签
来自 cmets:
我有一个 Azure 队列无服务器触发器。在该函数中,我解析一个文件,我想将状态发送到我的 blazor 项目。一旦它进入 API 控制器,我想更新 UI。
这很棘手。在这种情况下,Web API 本质上是在充当另一个用户。 Blazor 服务器选项卡需要更新以响应本质上是外部事件的事件。
由于它是 Blazor 服务器,假设没有使用负载平衡,所有用户电路都在处理 API 请求的同一进程中运行。可以在 API 控制器中引发“事件”并让 Blazor 组件监听它。
幸运的是,这正是像 Blazor.EventAggregator 这样的库所实现的。
事件聚合器服务是一个单例,注册于:
public void ConfigureServices(IServiceCollection services)
{
services.AddEventAggregator();
}
假设消息只接受一个文件名和一个状态:
public record FileEvent(string file,string status);
Web API 控制器将充当发布者:
class MyController:ControllerBase
{
private IEventAggregator _eventAggregator { get; set; }
public MyController(private IEventAggregator eventAggregator)
{
_eventAggregator=eventAggregator;
}
...
[HttpPost]
[AllowAnonymous]
public async Task<ActionResult> Post(BlobInfo blobInfo)
{
await _eventAggregator.PublishAsync(new FileEvent(blogInfo.BlobName,"OK");
return Ok();
}
}
关心的 Blazor Server 组件可以注入IEventAggregator,监听事件,指定他们关心的事件并处理它们。
在每个组件的代码隐藏中,服务可以注入:
[Inject]
private IEventAggregator _eventAggregator { get; set; }
该类还需要为它关心的事件实现IHandler<T>接口:
public class MyComponent : ComponentBase, IHandle<FileEvent>
{
...
List<string> _messages=new List<string>;
public async Task HandleAsync(FileEvent message)
{
_messages.Add($"{message.Name} worked");
await InvokeAsync(StateHasChanged());
}
...
}
Razor 代码实际上不需要更改:
@foreach(var value in _messages)
{
<p>@value</p>
}