[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编程系 ...
随机推荐
- 关于TensorFlow九件你非知不可的事
来源 | Hackernoon 译者 | Revolver 前些天我参加了7 月24 日在美国旧金山举行的Google Cloud Next 2018 大会,其中的一个演讲( What's New w ...
- JavaScript之onclick事件
对于给同一个元素添加两个点击事件时,其中一个是通过js获取元素添加点击事件另一个是通过内联的方法为元素添加事件. 执行之后只会执行通过元素获取的点击事件.而内联式的添加点击事件是不会执行的 还有一个就 ...
- 二、sudo su root 和sudo su -的区别
1.使用sudo su root可以快速切换为root用户,输入密码后输pwd可以看到它还是在主目录下即/home/sdbi 显示为:用户名@主机名:/home/sdbi#即root@linux:/h ...
- 以个人身份加入.NET基金会
.NET 走向开源,MIT许可协议. 微软为了推动.NET开源社区的发展,2014年联合社区成立了.NET基金会. 一年前 .NET 基金会完成第一次全面改选,2014年 .NET基金会的创始成员中有 ...
- 关于HashSet与HashMap
package com.test; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; i ...
- 深入解读ES6系列(三)
ES6字符串 哈喽小伙伴们,爱说'废'话的Z又回来了,欢迎来到Super IT曾的博客时间,上一节说了函数,解构赋值和数组的五大将,这一节我们继续我们知识的海洋,一起奋斗不秃头!不足的欢迎提问留言. ...
- 第一讲:Git分区,配置与日志
前言 曾经听到过这样一句话:不会git就不要敲代码了.细细品味确实有其中的道理,可能是当事人代码被强行覆盖后的叹息吧! 因此,为了避免这种情况,接下来我们就一起来好好学习git的相关知识吧!不怕你不会 ...
- 1032 Sharing (25分)(数组链表)
To store English words, one method is to use linked lists and store a word letter by letter. To save ...
- on duplicate key update 的用法说明(解决批量操作数据,有就更新,没有就新增)mybatis批量操作数据更新和添加
项目用的ORM框架是用springdatajpa来做的,有些批量数据操作的话,用这个效率太低,所以用mybatis自己写sql优化一下. 一般情况,我们肯定是先查询,有就修改,没有就添加,这样的话,单 ...
- BigDecimal 笔记
参数 BigDecimal 类主要有如下几个参数 这几个参数都比较好理解,看过这两个例子基本都能明白,现在总结一下就是 scale // 小数点后位数 precision // 一共位数 intCom ...