当前位置: 移动技术网 > IT编程>开发语言>.net > [Asp.Net Core] Blazor Server Side 项目实践 - 切换页面时保留状态

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

2020年04月26日  | 移动技术网IT编程  | 我要评论

雨水我问你粗话版,程毅君,北京赛车平台

前言:

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

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

 

问题:

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 

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

 

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

 

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网