[Asp.Net Core] Blazor Server Side 项目实践 - 切换页面时保留状态
前言:
这是 项目实践系列 , 算是中高级系列博文, 用于为项目开发过程中不好解决的问题提出解决方案的. 不属于入门级系列. 解释起来也比较跳跃, 只讲重点.
因为有网友的项目需求, 所以提前把这些解决方案做出来并分享.
问题:
Blazor自己是携带一个简单的路由功能的, 当切换Url的时候, 整个通过把RouteData传递给 App.razor 加载 MainLayout , 实现页面刷新的目的.
如果跳转到另外一个页面, 然后再跳回来的时候, 希望原来页面不刷新, 保留之前的状态 , 例如搜索条件, 那么怎么办?
解决过程:
结合视频, 图文观看效果最好 : https://www.bilibili.com/video/BV1g54y1R7uX/
1. 现在简单说说, 这种情况的源头在哪里.
2. App.razor 文件使用了 RouteView 来实现路由
3. routeData是包含页面类型, 以及页面参数的.
4. 然而默认的实现里, RouteView 是不带状态的
5. MainLayout虽然得到了内容的 RenderFragment ,
6. 然而这个 RenderFragment是由RouteView直接绑定到routeData上面去.
7. 所以MainLayout无法得到不同的RenderFragment来显示不同的内容.
8. 要解决这个问题, 首先第一步就是改造 RouteView
改造 RouteView
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Components.Rendering;
using System.Reflection; namespace Microsoft.AspNetCore.Components //use this namepace so copy/paste this code easier
{ public class KeepPageStateRouteView : RouteView
{
protected override void Render(RenderTreeBuilder builder)
{
var layoutType = RouteData.PageType.GetCustomAttribute<LayoutAttribute>()?.LayoutType ?? DefaultLayout;
builder.OpenComponent<LayoutView>();
builder.AddAttribute(, "Layout", layoutType);
builder.AddAttribute(, "ChildContent", (RenderFragment)CreateBody());
builder.CloseComponent();
} RenderFragment CreateBody()
{
var pagetype = RouteData.PageType;
var routeValues = RouteData.RouteValues; void RenderForLastValue(RenderTreeBuilder builder)
{
//dont reference RouteData again builder.OpenComponent(, pagetype);
foreach (KeyValuePair<string, object> routeValue in routeValues)
{
builder.AddAttribute(, routeValue.Key, routeValue.Value);
}
builder.CloseComponent();
} return RenderForLastValue;
} } }
Blazor自带的RouteView是一个控件. 它每次呈现, 都使用 RouteData 属性, 所以它每次生成的 RenderFragment 都是跟着最后的 RouteData 走, 保存来没用.
改造后的 KeepPageStateRouteView , 使用 CreateBody() 方法, 创建出绑定 pagetype 和 routevalue 的 RenderFragement , 为 MainLayout 打下基础
改造 MainLayout
@inherits LayoutComponentBase
@inject NavigationManager navmgr
@code{
TimeSpan GetUrlMaxLifeSpan(string url)
{
if (url.Contains("/fetchdata")) // Let /fetachdata always refresh
return TimeSpan.Zero;
if (url.Contains("/counter")) // Let /counter expires in 10 seconds
return TimeSpan.FromSeconds(10);
return TimeSpan.FromSeconds(-1); //other pages never expires
}
class PageItem
{
public string Url;
public RenderFragment PageBody;
public DateTime StartTime = DateTime.Now;
public DateTime ActiveTime = DateTime.Now;
public TimeSpan MaxLifeSpan;
}
Dictionary<string, PageItem> bodymap = new Dictionary<string, PageItem>();
int mainRenderCount = 0;
}
<div class="sidebar">
<NavMenu />
</div>
<div class="main">
@{
bool currurlrendered = false;
string currenturl = navmgr.Uri;
PageItem curritem;
if (bodymap.TryGetValue(currenturl, out curritem))
{
curritem.ActiveTime = DateTime.Now;
}
else
{
curritem = new PageItem { Url = currenturl, PageBody = Body };
curritem.MaxLifeSpan = GetUrlMaxLifeSpan(currenturl);
if (curritem.MaxLifeSpan != TimeSpan.Zero)
{
bodymap[navmgr.Uri] = curritem;
}
}
mainRenderCount++;
}
<div class="top-row px-4">
#@mainRenderCount
, CurrentUrl : @currenturl
. PageCount : @bodymap.Count
,
<button @onclick="StateHasChanged">StateHasChanged</button>
</div>
@foreach (PageItem eachitem in bodymap.Values.ToArray())
{
string pageurl = eachitem.Url;
RenderFragment pagebody = eachitem.PageBody;
string divstyle = "display:none";
if (pageurl == currenturl)
{
divstyle = "";
currurlrendered = true;
}
else if (eachitem.MaxLifeSpan.TotalSeconds > 0 && DateTime.Now - eachitem.ActiveTime > eachitem.MaxLifeSpan)
{
bodymap.Remove(eachitem.Url);
continue;
}
<div @key="pageurl" class="content px-4" style="@divstyle">
@pagebody
</div>
}
@if (!currurlrendered)
{
<div class="content px-4">
@Body
</div>
}
</div>
MainLayout 里, 最关键的是 Dictionary<string, PageItem> bodymap = new Dictionary<string, PageItem>();
这个字典, Key 是 Url , 而 PageItem 则储存了这个 Url 的多个信息.
范例使用了 TimeSpan GetUrlMaxLifeSpan(string url) 函数来制定页面的生存时间规则.
如果页面的生存时间是 0 , 表示不加进 bodymap , 每次都要全部刷新.
生存时间不为0 , 就储存到 bodymap 里面去. 然后在
@foreach (PageItem eachitem in bodymap.Values.ToArray())
循环过程中, 把每一个页面 Render 出来.
当前页面, 就显示, 不是当前页面, 则 display:none
没错. 在 Blazor 的 Render 体系里 , 只有输出了, 才有生命. 不输出, 就会被系统释放.
所以, 所有要让它活着的 Page , 都得输出. 哪怕用display:none隐藏它.
看看视频效果吧.
https://www.bilibili.com/video/BV1g54y1R7uX/
最后:
github : https://github.com/BlazorPlus/BlazorDemoKeepPageState
下一个版本: 支持多Tabs
https://github.com/BlazorPlus/BlazorDemoMultiPagesTab
暂时没时间做视频写博客. 后面补上.
视频杂音的确多, 求推荐一个麦克风..
[Asp.Net Core] Blazor Server Side 项目实践 - 切换页面时保留状态的更多相关文章
- [Asp.Net Core] Blazor Server Side 扩展用途 - 配合CEF来制作带浏览器核心的客户端软件 (二) 可运行版本
前言 大概3个星期之前立项, 要做一个 CEF+Blazor+WinForms 三合一到同一个进程的客户端模板. 这个东西在五一的时候做出了原型, 然后慢慢修正, 在5天之前就上传到github了. ...
- [Asp.Net Core] Blazor Server Side 扩展用途 - 配合CEF来制作客户端浏览器软件
前言 大家用过微信PC端吧? 这是用浏览器做的. 用过Visual Studio Code吧? 也是用浏览器做的. 听说, 暴雪客户端也包含浏览器核心?? 在客户端启动一个浏览器, 并不是什么难事了. ...
- ASP.NET Core Blazor 初探之 Blazor Server
上周初步对Blazor WebAssembly进行了初步的探索(ASP.NET Core Blazor 初探之 Blazor WebAssembly).这次来看看Blazor Server该怎么玩. ...
- 快读《ASP.NET Core技术内幕与项目实战》WebApi3.1:WebApi最佳实践
本节内容,涉及到6.1-6.6(P155-182),以WebApi说明为主.主要NuGet包:无 一.创建WebApi的最佳实践,综合了RPC和Restful两种风格的特点 1 //定义Person类 ...
- ASP.NET Core Blazor Webassembly 之 组件
关于组件 现在前端几大轮子全面组件化.组件让我们可以对常用的功能进行封装,以便复用.组件这东西对于搞.NET的同学其实并不陌生,以前ASP.NET WebForm的用户控件其实也是一种组件.它封装ht ...
- 学习ASP.NET Core Blazor编程系列二——第一个Blazor应用程序(中)
学习ASP.NET Core Blazor编程系列一--综述 学习ASP.NET Core Blazor编程系列二--第一个Blazor应用程序(上) 四.创建一个Blazor应用程序 1. 第一种创 ...
- 022年9月12日 学习ASP.NET Core Blazor编程系列三——实体
学习ASP.NET Core Blazor编程系列一--综述 学习ASP.NET Core Blazor编程系列二--第一个Blazor应用程序(上) 学习ASP.NET Core Blazor编程系 ...
- 学习ASP.NET Core Blazor编程系列四——迁移
学习ASP.NET Core Blazor编程系列一--综述 学习ASP.NET Core Blazor编程系列二--第一个Blazor应用程序(上) 学习ASP.NET Core Blazor编程系 ...
- 学习ASP.NET Core Blazor编程系列六——初始化数据
学习ASP.NET Core Blazor编程系列一--综述 学习ASP.NET Core Blazor编程系列二--第一个Blazor应用程序(上) 学习ASP.NET Core Blazor编程系 ...
随机推荐
- 解决Pycharm导入当前项目的.py文件错误
如图所示错误,由左边导航栏可见.py文件存在: 解决办法:右键单击导包错误文件所在目录,选择[Mark Directory as]+[Sources Root] 错误已解决:
- Linux环境下部署项目时的步骤和一些要注意的点
SQL的导出和导入 sql的导出 首先选中要导出的数据库 然后点击左下角的administration选项,进入导出界面. 点击Data Export 然后勾选图中的几个选项即可导出一个sql,如果需 ...
- list容器排除重复单词的程序
#include<iostream> #include<fstream> #include<string> #include<algorithm> #i ...
- Xmind pro Win10系统下安装问题解决与破解
Xmind pro Win10系统下安装问题解决与破解 1.下载安装版本 解压包含文件: xmind-8-update7-windows--安装包 和XMindCrack.jar--激活破解工具 2. ...
- 金三银四科学找工作,用python大数据分析一线城市1000多份岗位招聘需求
文章每周持续更新,各位的「三连」是对我最大的肯定.可以微信搜索公众号「 后端技术学堂 」第一时间阅读(一般比博客早更新一到两篇) 每年的三四月份是招聘高峰,也常被大家称为金三银四黄金招聘期,这时候上一 ...
- Pytest系列(6) - conftest.py的详细讲解
如果你还想从头学起Pytest,可以看看这个系列的文章哦! https://www.cnblogs.com/poloyy/category/1690628.html 什么是conftest.py 可以 ...
- Java基础知识2-Java基本语法
数据类型 1.Java程序的基本组成 关键字:被Java语言赋予特定含义的单词,不能作标识符,如private. 标识符:由数字.字母.$和_组成的字符串,用于引用变量.且首字母不能是数字. 变量:程 ...
- 存储机制 cookie session jwt token
cookieCookie的诞生 由于HTTP协议是无状态的,而服务器端的业务必须是要有状态的.Cookie诞生的最初目的是为了存储web中的状态信息,以方便服务器端使用.比如判断用户是否是第一次访问网 ...
- 下载腾讯视频mp4格式
import time import subprocess import argparse def command(cmd, timeout=60): ''' :param cmd: 执行命令cmd, ...
- linux安装常用软件和查询基本信息
linux安装常用软件和查询基本信息 1. 安装常用软件 [ ...