欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

[Asp.Net Core] Blazor Server Side 项目实践 - 切换页面时保留状态

程序员文章站 2022-03-02 13:41:00
前言: 这是 项目实践系列 , 算是中高级系列博文, 用于为项目开发过程中不好解决的问题提出解决方案的. 不属于入门级系列. 解释起来也比较跳跃, 只讲重点. 因为有网友的项目需求, 所以提前把这些解决方案做出来并分享. 问题: Blazor自己是携带一个简单的路由功能的, 当切换Url的时候, 整 ......

前言:

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

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

 

问题:

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 

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

 

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