到单例服务Blazor服务器端的双向数据绑定



我一直在使用Webassembly在客户端上玩Blazor。但我想我现在会尝试服务器端版本,我有一个简单的想法想尝试一下。

因此,我的理解是Blazor服务器端使用SignalR来"推送"更改,以便客户端重新呈现其页面的一部分。

我想尝试的是数据绑定到像这样的单例服务上的属性:

@page "/counter"
@inject DataService dataService
<h1>Counter</h1>
<p>Current count: @currentCount ok</p>
<p> @dataService.MyProperty  </p>
<p>
@dataService.Id
</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
int currentCount = 0;
void IncrementCount()
{
currentCount++;
dataService.MyProperty += "--o--|";
}

}

Startup.cs:

public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddSingleton<WeatherForecastService>();
services.AddSingleton<DataService>();
}

服务:

namespace bl1.Services
{
public class DataService
{
public DataService()
{
this.Id = System.Guid.NewGuid().ToString();
}
public string Id {get;set;}
public string MyProperty { get; set; }  
}
}

所以我的问题是。为什么,如果我在两个选项卡中打开此页面,当我在另一个选项卡的一个选项卡中更改属性的值时,我不会立即看到用SignalR更新属性MyProperty的值?有什么不应该起作用的原因吗?还是我只是做错了?

我认为在服务器端使用Blazor的好处是,您可以很容易地利用SignalR可用的事实,并在服务器上的值更改时获得实时更新。

我确实从另一个选项卡中的singleton服务中获得了最新的值,但只有在单击按钮之后。

很抱歉,Ashkan之前没有得到更好的答案(我现在刚读到你的问题)。你所尝试的实际上是Blazor做得很好的,你是对的,它是解决此类问题的完美架构。直接使用SignalR,就像上面的答案一样,对于Blazor WASM解决方案来说是正确的,但这并不能解决您的问题,即使用服务器端Blazor。我将提供以下解决方案。

重要的一点是要理解blazor组件不会"轮询"其绑定属性的更改。相反,如果单击某个组件上的某个按钮,该组件将自动重新呈现(到内部树)。然后将对之前的渲染执行diff,服务器端blazor只会在发生更改时向客户端(浏览器)发送更新。

在您的案例中,您有一个组件,它的模型使用了一个注入的singleton。然后,您可以在两个选项卡中打开组件,如预期的那样(考虑到Blazor的体系结构),只有单击按钮的选项卡中的组件正在重新渲染。这是因为没有任何内容指示组件的另一个实例重新渲染(Blazor没有"轮询"属性值的更改)。

您需要做的是指示组件的所有实例重新渲染;您可以通过在每个组件上调用StateHasChanged来实现这一点。

因此,解决方案是将OnInitialized()中的每个组件连接到一个事件,您可以在修改属性值后通过调用Refresh()来调用该事件;然后在Dispose()中取消连接。

您需要将其添加到组件的顶部才能正确清理:

@implements IDisposable

然后添加此代码:

static event Action OnChange;
void Refresh() => InvokeAsync(StateHasChanged);
override protected void OnInitialized() => OnChange += Refresh;
void IDisposable.Dispose() => OnChange -= Refresh;

您可以将我的OnChange事件移动到您的singleton中,而不是将其作为静态事件。

如果您调用"Refresh()",您现在会注意到所有组件都会立即在任何打开的选项卡上重新绘制。我希望这能有所帮助。

请参阅此处的文档

Blazor Server应用程序是在ASP之上构建的。NET核心信号。每个客户端通过一个或多个SignalR连接与服务器通信称为电路。电路是Blazor对SignalR的抽象可以容忍临时网络中断的连接。当Blazor客户端发现SignalR连接断开尝试使用新的SignalR连接重新连接到服务器。

连接到Blazor Server应用程序使用SignalR连接。这是另一个与典型的服务器渲染应用程序相比,这是一个重要的区别。在服务器渲染的应用程序,在多个浏览器屏幕中打开同一应用程序通常不会转化为对服务器在Blazor Server应用程序中,每个浏览器屏幕都需要单独的电路和部件状态的单独实例由服务器管理。

Blazor考虑关闭浏览器选项卡或导航到外部URL是一个优雅的终止。在优美终止的情况下,电路和相关联的资源被立即释放。A.客户端也可能不正常地断开连接,例如由于网络中断。Blazor服务器为允许客户端重新连接的可配置间隔。了解更多信息有关信息,请参阅重新连接到同一服务器部分。

因此,在您的情况下,另一个选项卡中的客户端不会被通知在另一个ConnectionContext中的另一个电路上所做的更改。

在客户端上调用StateHasChanged()应该可以解决问题。

对于您描述的问题,您最好使用简单的SignalR,而不是Blazor服务器端。

只是为D.Taylor的答案添加一点内容:

我相信在大多数情况下,您都希望将OnChange操作转移到服务中。

在你的services.cs中,你应该添加:

public event Action OnChange;
private void NotifyDataChanged() => OnChange?.Invoke();
private int myProperty; //field
public int MyProperty // property
{
get { return myProperty; }
set
{
myProperty= value;
NotifyDataChanged();
}
}

每次更改该值时,它现在都会调用该Action。现在调整D.Taylor在实际的.rarzor文件中所做的操作,在@code{}部分,现在只需将刷新方法绑定到您服务中的Action。。。

void Refresh() => InvokeAsync(StateHasChanged);
override protected void OnInitialized() => MyService.OnChange += Refresh;
void IDisposable.Dispose() => OnChange -= Refresh;

现在应该将更新推送到客户端!

最新更新