前言:

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

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

问题:

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. Java工程师常用Linux命令

    本文所列的Linux常用命令包含:文件相关(目录操作,内容查看,查找与比较,压缩与解压),进程管理,网络操作,系统管理,性能监测与优化,Java常用工具多个方面概述. 文件目录基本操作 ls 命令用来 ...

  2. JSP学习笔记(三)

    jsp与javabean 编写javabean 创建与使用bean 获取和修改bean的属性 javabean是一种java类,是通过封装属性和方法成为具有某种功能或处理某个业务的对象,简称bean, ...

  3. Go语言库系列之aurora

    背景介绍 今天跟大家推荐一款可以给终端输出上色的工具--aurora. 极速上手 准备工作 初始化项目 go mod init aurora 演示项目结构 . ├── go.mod ├── go.su ...

  4. Python itchat库(1)

    一.实验环境 在cmd中输入以下命令,完成微信的API包itchat的安装. pip install itchat 注意这里有个问题,因为电脑里既有Python3.6,又有anaconda.所以一定要 ...

  5. Android进阶AIDL使用自定义类型

    原文首发于微信公众号:jzman-blog,欢迎关注交流! 上篇文章中主要介绍从 AIDL 的使用方式以及 Android 开发中不同进程之间的通信,遗留的问题是如何在 AIDL 中使用自定义类型,具 ...

  6. CSS3新增的选择器

    1. 层次选择器 子元素选择器: 只选择子元素    父选择器 > 子选择器 后面紧相邻的兄弟元素:  选择器1 + 选择器2 后面所有的兄弟元素:   选择器1 ~ 选择器2 2.属性选择器  ...

  7. MQTT协议实现Android中的消息收发

    前言 MQTT(Message Queuing Telemetry Transport,消息队列遥测传输),基于发布/订阅范式的消息协议,是一种极其简单和轻量级的消息协议,专为受限设备和低带宽.高延迟 ...

  8. MFC 工具栏ToolBar

    一.创建工具栏 1.在MFC工程,找到“资源视图”界面,右键添加资源,选择Toolbar,点击新建: 2.修改工具条属性: 3.添加工具: 新建ToolBar后,会自动生成一个工具,编辑ID后,工具条 ...

  9. SSM项目依赖,spring5.1.9

    SSM项目依赖,spring5.1.9 简单的SSM项目,未加AOP等等操作 Spring三包bean core content Mybatis 五个mybatis mybatis-spring jd ...

  10. vue 动态添加body背景图片

    <script> export default {   data () {     return {      bodyBgImage: 'url(' + require('../asse ...