我们使用 Blazor(服务器端)和 .NET 5 来实现新的解决方案,该解决方案需要使用会话来消除完全相同数据的数据库往返。
我们目前正在使用推荐的 ProtectedBrowserStorage 模型,但当我们尝试存储相对较大的数据集时,遇到了浏览器的 25MB 限制。因此,当我们处理需要最终用户查看和操作的数千条记录时,这可能并不理想。我们正在努力为最终用户优化性能。这是一个连接的桌面场景。
是否有更好的方法来处理 Blazor 中的大型会话变量,不受客户端浏览器的限制?
提前致谢。
消除完全相同数据到数据库的往返。
您可以使用缓存。这允许在会话/用户之间共享数据(可能存在风险,请小心)。它还会减少 I/O。
缓存位于服务器端,您的应用程序也在那里运行。所以一般会比较快。但尚不清楚您的内存限制(每个用户)是什么。
您可以使用所有主流浏览器都支持的 IndexedDb。
我在 PWA 中使用此对象存储数据库,但您可以将其与 Blazor 服务器端应用程序或 Blazor WASM 客户端应用程序一起使用。
IndexedDB 的限制对于您以及大多数情况下都不是问题。
浏览器管理数据,您只需决定如何刷新它们的策略。
从这个库开始https://github.com/amuste/DnetIndexedDb并尝试一些示例。
根据您的需要,您可以通过几种不同的方法来执行此操作:
级联参数:一个很好的基础知识视频 - https://www.youtube.com/watch?v=ZmDMKp1Q8kA
AppState 方法:一个很好的视频,涵盖了两种不同的方法:https://www.youtube.com/watch?v=BB4lK2kfKf0
这两个问题的主要问题是它们仍在会话中,并且会在刷新或新选项卡时消失。
我所知道的唯一其他解决方案是使用 Redis,这将保留数据,只要您想保留它,但它不会在 blazor 中。 精彩视频:https://www.youtube.com/watch?v=UrQWii_kfIE
这取决于您有多少并发用户以及您的系统资源。
单例服务跨会话持续存在,您可以使用任何您想要的数据结构。
首先,如果您使用 Blazor Server,则不希望在浏览器存储中使用大型会话 blob。每次访问“会话”(服务器端)时,它都会从客户端浏览器获取对象,将对象传输回服务器,服务器将处理和渲染,将其发送回用户。
无论数据大小如何,我都不会推荐这种方法来管理服务器端频繁访问的状态变量,因为每次访问都会产生返回服务器的延迟。
我发现最适合我的方法是拥有一个自定义状态提供程序组件 - 例如 CustomStatePovider。
不涉及所有细节,但基本上 CustomStateProvider 公开了一个字典,您可以在其中存储所有状态变量(服务器端)。
包含一个 Flush() 方法,允许您有选择地将 KVP 持久保存到您选择的存储类型(ProtectedBrowserStorage、ProtectedLocalStorage、ProtectedSessionStorage - 根据需要实施,示例显示 ProtectedSessionStorage)有关此内容的更多详细信息,请参阅 here。
包含一个 Get() 方法,该方法将尝试从字典中加载(如果存在),否则从您选择的持久存储层加载(当然会更新字典)。
注意:您需要使 Get() 异步,但在此示例中尚未这样做
自定义状态提供者:
@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
@inject ProtectedSessionStorage ProtectedSessionStore
@if (_hasLoaded)
{
<CascadingValue Value="@this">
@ChildContent
</CascadingValue>
}
@code {
[Parameter]
public RenderFragment ChildContent { get; set; }
private bool _hasLoaded;
public Dictionary<string, object> InMemoryState { get; set; } = new();
protected override async Task OnInitializedAsync()
{
//This will be called if the page refreshes / first load
//Can load anything you want from your persisted state if required
_hasLoaded = true;
}
public async Task Flush(string key)
{
await ProtectedSessionStore.SetAsync(key, InMemoryState);
}
public T Get<T>(string key, bool errorOnNotFound = false)
{
if (InMemoryState.ContainsKey(key))
{
return (T)InMemoryState[key];
}
else
{
var persistedValue = GetPersistedStateAsync<T>(key, errorOnNotFound).GetAwaiter().GetResult();
InMemoryState[key] = persistedValue;
return persistedValue;
}
}
private async Task<T> GetPersistedStateAsync<T>(string key, bool errorOnNotFound = false)
{
var val = await ProtectedSessionStore.GetAsync<T>(key);
if (val.Success)
{
return val.Value;
}
else
{
if (errorOnNotFound)
{
throw new Exception($"Storage key not found: {key}");
}
else
{
//or just default
return default(T);
}
}
}
}
我通常有两个组件根(一个用于页面,一个用于组件),所有其他组件都继承自它们。在每个中为 CustomStateProvider 实现 CascadingParameter。所有页面和所有组件现在都可以访问自定义状态提供程序。
PageBase(与组件库类似 - 未显示)
@inherits LayoutComponentBase
@Body
@code {
[CascadingParameter]
public CustomStateProvider CustomStateProvider { get; set; }
}
然后将您的路由器包装在自定义状态提供程序中,您就可以上路了。
<CustomStateProvider>
<Router AppAssembly="@typeof(App).Assembly">
<Found Context="routeData">
<AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
</Found>
<NotFound>
<PageTitle>Not found</PageTitle>
<LayoutView Layout="@typeof(LayoutBase)">
//Add your default 404 message
</LayoutView>
</NotFound>
</Router>
</CustomStateProvider>
@code {
}