前言:

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

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

问题:

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. Array.forEach原理,仿造一个类似功能

    Array.forEach原理,仿造一个类似功能 array.forEach // 设一个arr数组 let arr = [12,45,78,165,68,124]; let sum = 0; // ...

  2. [codevs2370]小机房的树<LCA>

    题目链接:http://codevs.cn/problem/2370/ 这题我还是做了比较久了,因为有人告诉我这是用tarjan离线做 好吧算我是蒟蒻,真心不懂tarjan怎么做,最后还是用倍增做的 ...

  3. vue中keepAlive的用法[返回页面不刷新]

    本文转载于時間蒼白了誓言_49b9 使用vue单页开发项目时遇到一个问题:在列表页进入详情页,按返回键返回列表页时页面刷新了,用户体验非常差啊!!!查阅了一下相关问题,使用解决这个问题,下面是我的使用 ...

  4. ajax2.0之文件上传加跨域

    express_server.js const express=require('express'); //主体 const body=require('body-parser'); //接收普通PO ...

  5. jmeter发送Query String Parameters格式参数报错

    当发起一次GET请求时,参数会以url string的形式进行传递.即?后的字符串则为其请求参数,并以&作为分隔符 当参数为json格式时,这时需要勾选编码,否则会报错

  6. APScheduler轻量级定时任务框架

    目录 一.APScheduler简介 支持的后端存储作业 集成的Python框架 二.APScheduler下载安装 三.APScheduler组件 各组件简介 调度器 作业存储器 执行器 触发器 四 ...

  7. C语言把浮点数转换为字符串

    目录 1.把float/double格式化输出到字符串 2.注意事项 3.版权声明 1.把float/double格式化输出到字符串 标准的C语言提供了atof函数把字符串转double,但是没有提供 ...

  8. 【PHP】流程控制

    一.      顺序结构 a)    PHP的脚本从上到下,从左往右的执行顺序就是顺序结构! 二.      分支结构 a)    单一的分支结构 If ( 条件表达式 ) { 程序体: } 执行过程 ...

  9. 关于web数据库的相关知识点的操作

    主要是怎样建立与数据库的连接.对于框架是固定的: 下面是对于数据库的连接操作: package com.DBU; //数据库连接 import java.sql.Connection; import ...

  10. django自定义404和500页面

    from django.contrib import admin from django.urls import path urlpatterns = [ path('admin/', admin.s ...