前言:

这是 项目实践系列 , 算是中高级系列博文, 用于为项目开发过程中不好解决的问题提出解决方案的. 不属于入门级系列. 解释起来也比较跳跃, 只讲重点.

因为有网友的项目需求, 所以提前把这些解决方案做出来并分享.

问题:

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 项目实践 - 切换页面时保留状态的更多相关文章

  1. [Asp.Net Core] Blazor Server Side 扩展用途 - 配合CEF来制作带浏览器核心的客户端软件 (二) 可运行版本

    前言 大概3个星期之前立项, 要做一个 CEF+Blazor+WinForms 三合一到同一个进程的客户端模板. 这个东西在五一的时候做出了原型, 然后慢慢修正, 在5天之前就上传到github了. ...

  2. [Asp.Net Core] Blazor Server Side 扩展用途 - 配合CEF来制作客户端浏览器软件

    前言 大家用过微信PC端吧? 这是用浏览器做的. 用过Visual Studio Code吧? 也是用浏览器做的. 听说, 暴雪客户端也包含浏览器核心?? 在客户端启动一个浏览器, 并不是什么难事了. ...

  3. ASP.NET Core Blazor 初探之 Blazor Server

    上周初步对Blazor WebAssembly进行了初步的探索(ASP.NET Core Blazor 初探之 Blazor WebAssembly).这次来看看Blazor Server该怎么玩. ...

  4. 快读《ASP.NET Core技术内幕与项目实战》WebApi3.1:WebApi最佳实践

    本节内容,涉及到6.1-6.6(P155-182),以WebApi说明为主.主要NuGet包:无 一.创建WebApi的最佳实践,综合了RPC和Restful两种风格的特点 1 //定义Person类 ...

  5. ASP.NET Core Blazor Webassembly 之 组件

    关于组件 现在前端几大轮子全面组件化.组件让我们可以对常用的功能进行封装,以便复用.组件这东西对于搞.NET的同学其实并不陌生,以前ASP.NET WebForm的用户控件其实也是一种组件.它封装ht ...

  6. 学习ASP.NET Core Blazor编程系列二——第一个Blazor应用程序(中)

    学习ASP.NET Core Blazor编程系列一--综述 学习ASP.NET Core Blazor编程系列二--第一个Blazor应用程序(上) 四.创建一个Blazor应用程序 1. 第一种创 ...

  7. 022年9月12日 学习ASP.NET Core Blazor编程系列三——实体

    学习ASP.NET Core Blazor编程系列一--综述 学习ASP.NET Core Blazor编程系列二--第一个Blazor应用程序(上) 学习ASP.NET Core Blazor编程系 ...

  8. 学习ASP.NET Core Blazor编程系列四——迁移

    学习ASP.NET Core Blazor编程系列一--综述 学习ASP.NET Core Blazor编程系列二--第一个Blazor应用程序(上) 学习ASP.NET Core Blazor编程系 ...

  9. 学习ASP.NET Core Blazor编程系列六——初始化数据

    学习ASP.NET Core Blazor编程系列一--综述 学习ASP.NET Core Blazor编程系列二--第一个Blazor应用程序(上) 学习ASP.NET Core Blazor编程系 ...

随机推荐

  1. SpringBoot安装与配置

    1.环境准备 1.1.Maven安装配置 Maven项目对象模型(POM),可以通过一小段描述信息来管理项目的构建,报告和文档的项目管理工具软件. 下载Maven可执行文件 cd /usr/local ...

  2. jsp学习笔记day2

    一.jsp基础语法 1.注释 显式注释语法: <!--注释内容-->客户端可以看见 隐式注释语法:客户端不能看见 <% //单行注释 /*多行注释*/ %> 2.Scriptl ...

  3. 模块 jieba结巴分词库 中文分词

    jieba结巴分词库 jieba(结巴)是一个强大的分词库,完美支持中文分词,本文对其基本用法做一个简要总结. 安装jieba pip install jieba 简单用法 结巴分词分为三种模式:精确 ...

  4. elasticsearch异常问题 discovery.seed_hosts, discovery.seed_providers, cluster.initial_master_nodes] must be configured

    本文使用环境  centos7.x   elasticsearch7.6.2   JDK1.8 错误:文件权限不足      [1]: max file descriptors [4096] for ...

  5. 用c#求一元二次方程

    题目:编一个程序,输入a .b.c 的值,求出一元二次方程a*x*x+b*x+c=0的二个实数根. 我的思路: 我们都知道数学中求一元二次方程有很多方法:直接开方法.配方法.公式法.分解因式法等等,在 ...

  6. Q - Queue HDU - 5493(树状树组维护区间前缀和 + 二分找预留空位)

    Q - Queue HDU - 5493 Problem Description NNN people numbered from 1 to NNN are waiting in a bank for ...

  7. python学习笔记--字符串格式化

    字符串和常量 print(r'hello\py\thon') r 代表后面字符不进行转义,原样输出; 表示常量,命名时变量名字大写代表常量.NAME = 'liulixue'; 字符串表示:' ', ...

  8. 逍遥云天 H5外部浏览器直接调起微信——通过url协议 weixin:// 判断是否安装微信及启动微信

    h5分享到微信,h5使用微信支付这些功能,都需要先判断是否安装微信客户端,如果已安装就启动微信,如果没有安装微信,就提示用户前去安装. 我们可以通过访问微信提供的URL协议(weixin://)来实现 ...

  9. java类文件结构笔记

    注:新的博客地址 - https://zhengw-tech.com/archives/ 我们都知道java实现跨平台靠的是虚拟机技术,将源文件编译成与操作系统无关的,只有虚拟机能识别并执行的字节码文 ...

  10. Android | 教你如何开发扫二维码功能

    前言   最近要做一个停车场扫码收费的app,在网上搜了一圈,首先接触到了ZXing,上手试了下,集成过程不复杂,但是感觉效果欠佳,比如距离稍微远点儿就扫不出来了,另外角度对的不好,反光或者光线比较暗 ...