zoukankan      html  css  js  c++  java
  • [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>(0);
                builder.AddAttribute(1, "Layout", layoutType);
                builder.AddAttribute(2, "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(0, pagetype);
                    foreach (KeyValuePair<string, object> routeValue in routeValues)
                    {
                        builder.AddAttribute(1, 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 

    暂时没时间做视频写博客.  后面补上. 

    视频杂音的确多, 求推荐一个麦克风..

  • 相关阅读:
    [maven 问题]java.lang.NoSuchMethodError: javax.ws.rs.core.Application.getProperties()Ljava/util/Map
    [Java基础]Java异常捕获
    [JVM]常用JVM工具使用
    [JVM]一次线上频繁GC的问题解决
    [Java 基础]ResultSet 指定field映射到Pojo对象的Map
    [JVM]使用JMeter做压力测试
    [Linux命令]zip
    [Mysql]分组取最新一条
    [zookeeper_kafka]zookeeper和Kafka安装
    网络带宽是什么?
  • 原文地址:https://www.cnblogs.com/zhgangxuan/p/d001_keeppagestate.html
Copyright © 2011-2022 走看看