Blazor 机制初探以及什么是前后端分离,还不赶紧上车?

上一篇文章发了一个 BlazAdmin 的尝鲜版基于 Blazui 的 Blazor 后台管理模板 BlazAdmin 正式尝鲜,这一次主要聊聊 Blazor 是如何做到用 C# 来写前端的,传送门:https://www.cnblogs.com/wzxinchen/p/12057171.html

飚车前

需要说明的一点是,因为我深入接触 Blazor 的时间也不是多长,顶多也就半年,所以这篇文章的内容我不能保证 100% 正确,但可以保证大致原理正确

另外,具有以下条件的园友食用这篇文章会更舒服:

  • 了解 Http 请求响应模型及 Http 协议

  • 有足够的微软技术栈 Web 开发经验,例如 MVC、WebApi 等

  • 有按照微软的 Blazor 官方文档进行入门的实战操作,传送门:https://docs.microsoft.com/zh-cn/aspnet/core/blazor/get-started?view=aspnetcore-3.1&tabs=visual-studio

  • 有自己研究过 Blazor 生成的代码

  • 有过 SignalR 或 WebSocket 使用经验

建议结合 AspNetCore 源码看这篇文章,我不能贴出所有源码,源码需要编译过才能看,不然会很麻烦,但编译这事比较难,编译源码比看源码难多了,这儿是一位园友的源码编译教程:https://www.cnblogs.com/ZaraNet/p/12001261.html
天底下没有新鲜事儿,Blazor 看着神奇,其实也没啥黑科技,它跑不掉 Http 协议,也跑不掉 Html

开始发车

Blazor 服务端渲染过程

当您打开一个服务端渲染的 Blazor 应用时:

Copy浏览器服务器建立 WebSocket 连接发送首页 HTML 代码浏览器JS捕获用户输入事件通知服务器发生了该事件服务器 .Net 处理事件发送有变动的 HTML 代码浏览器JS渲染变动的 HTML 代码loop[ 连接未断开 ]浏览器服务器

有以下几点需要注意:

  • WebSocket 连接采用 SignalR 来建立,如果浏览器不支持 WebSocket,SignalR 会采用其他技术建立

  • 浏览器捕获用户输入是使用 Javascript进行捕获的

  • 服务器处理客户端事件完成后,会生成新的 HTML 结构,然后将这个结构与老的结构进行对比,得到有变动的 HTML 代码

  • Blazor 服务端渲染版采用在服务器端维护一个虚拟 DOM 树来实现上述操作

  • “通知服务器发生了该事件”这一步里,从原理上来说类似于 WebForm 的 PostBack 机制,不同点在于,Blazor 只告诉服务器是哪个 DOM 节点发生了什么事件,这个传输量是极小的。

服务端渲染的基本原理就是这样,下面我们详细讨论

Blazor 路由渲染过程

当我们通过 NavigationManager 去改变路由地址时,大概流程如下

Copy服务器启动初始化 Router 组件,Router 内部注册 LocationChanged 事件LocationChanged 事件中根据路由查找对应的组件,默认触发首页组件加入渲染队列一直进行渲染及比对,直到队列中所有的组件全部渲染完将比对的差异结果更新至浏览器等待下一次路由改变,继续触发 LocationChanged 事件

这里的 Router 组件,就是我们经常用到的,看看下面的代码,是不是很熟悉?

Copy<Router AppAssembly="@typeof(Program).Assembly"><Found Context="routeData"><RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" /></Found><NotFound><LayoutView Layout="@typeof(MainLayout)"><p>Sorry, there's nothing at this address.</p></LayoutView></NotFound>
</Router>

Router 组件部分代码

Copypublic class Router : IComponent, IHandleAfterRender, IDisposable
{public void Attach(RenderHandle renderHandle){_logger = LoggerFactory.CreateLogger<Router>();_renderHandle = renderHandle;_baseUri = NavigationManager.BaseUri;_locationAbsolute = NavigationManager.Uri;//注册 LocationChanged 事件NavigationManager.LocationChanged += OnLocationChanged;}private void OnLocationChanged(object sender, LocationChangedEventArgs args){_locationAbsolute = args.Location;if (_renderHandle.IsInitialized && Routes != null){Refresh(args.IsNavigationIntercepted);}}private void Refresh(bool isNavigationIntercepted){var locationPath = NavigationManager.ToBaseRelativePath(_locationAbsolute);locationPath = StringUntilAny(locationPath, _queryOrHashStartChar);var context = new RouteContext(locationPath);Routes.Route(context);..........var routeData = new RouteData(context.Handler,context.Parameters ?? _emptyParametersDictionary);//此处开始渲染,Found 是一个 RenderFragment<RouteData> 委托,是我们在调用的时候指定的那个_renderHandle.Render(Found(routeData));..........}
}

Blazor 组件渲染过程

要开始飚车了,握紧方向盘,不要翻车。
这部分可能会比较难,如果你发现你看不懂的话就先尝试自己写个组件玩玩。
在 Blazor 中,几乎一切皆组件。首先我们得提到一个 Blazor 组件的几个关键方法,部分方法也是它的生命周期

  • OnInitialized、OnInitializedAsync:仅在第一次实例化组件时,才会调用这些方法一次。注意,该方法调用时参数已经设置,但没有渲染。

  • SetParametersAsync:该方法可以让您在设置参数之前做一些事

  • OnParametersSetAsync、OnParametersSet:每一次参数设置完成之后都会调用

  • OnAfterRender、OnAfterRenderAsync:在组件渲染完成之后触发

  • ShouldRender:如果该方法返回 false,则组件在第一次渲染完成后不会执行二次渲染

  • StateHasChanged:强制渲染当前组件,如果 ShouldRender 返回的是 false,则不会强制渲染

  • BuildRenderTree: 该方法一般情况下我们用不到,它的作用是拼接 HTML 代码,由 VS 自动生成的代码去调用它

另有一个关键的结构体 EventCallBack,还有一个关键的委托RenderFragment,它俩非常重要,前者可能见得比较少,后者基本上玩过 Blazor 的园友都知道。

上面提到的关键点,有个印象即可,下面将开始飚车,我们将重点讨论那个流程图中渲染对比的那部分,但将忽略浏览器捕获事件这一步,我不能贴太多的源码,尽可能用流程图表示

主要生命周期过程

Copy开始渲染调用 SetParametersAsync 方法是否首次渲染调用 OnInitialized 方法调用 OnInitializedAsync 方法调用 OnParametersSet 方法调用 StateHasChanged 方法yesno

需要注意的是这个流程中没有 OnAfterRender 方法的调用,这个将在下面讨论

StateHasChanged 方法

这个方法至关重要,就比如上图中最终只到了 StateHasChanged 方法,就没了下文,我们来看看这个方法里面有什么

Copy开始是否首次渲染进入渲染队列开始循环渲染队列的数据触发 OnAfterRender 方法结束ShouldRender 为True?yesnoyesno

至此,我们基本把一个组件的生命周期的那几个方法讨论完了,除了一些异步版本的,逻辑都差不多,没有写进来

渲染队列时都干了啥?

嗯对,这是重点

Copy开始渲染队列队列还有组件?从队列获取组件备份当前 DOM 树及清空调用组件的 RenderFragment 委托获取新的 DOM 树与备份的树对比将对比结果存入列表将列表中的所有对比结果发送至浏览器结束yesno

为了图好看点(好吧现在其实也不好看),我把流程缩短了一点,有以下几点需要注意:

  • 渲染开始之前是将当前树赋值成了旧的树,然后再将当前树清空

  • 组件的 RenderFragment 委托在大多数情况下就是组件的 ChildContent 属性的值,玩过的都知道几乎每个组件都有自己的 ChildContent

  • 同时 RenderFragment 也有可能是 ComponentBase类中的一个私有属性,详见下面的代码。当然也有可能是其他的,限于篇幅,不细说

  • RenderFragment 委托输入的参数就是当前这颗树

  • 如果您在组件中调用了子组件,并且这个子组件还有自己的内容,那么 VS 会生成调用这个组件的代码,并且为这个组件添加 ChildContent 属性,内容就是子组件自己的内容,详见代码

下面是 ComponentBase 的部分代码,上文提到的私有属性就是 _renderFragment,这个私有属性仅在此处被赋值,可以看到这个属性内部调用了 BuildRenderTree 方法

Copy    public abstract class ComponentBase : IComponent, IHandleEvent, IHandleAfterRender{private readonly RenderFragment _renderFragment;/// <summary>/// Constructs an instance of <see cref="ComponentBase"/>./// </summary>public ComponentBase(){_renderFragment = builder =>{_hasPendingQueuedRender = false;_hasNeverRendered = false;BuildRenderTree(builder);};}}

针对最后一点,举个例子
下面是 NavMenu.razor 组件的 Razor 代码

Copy<BMenu><BMenuItem Route="button">Button 按钮</BMenuItem>
</BMenu>

下面是 VS 生成的代码

Copypublic partial class NavMenu : Microsoft.AspNetCore.Components.ComponentBase{protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder){__builder.OpenComponent<BMenu>(1);__builder.AddAttribute(4, "ChildContent", (Microsoft.AspNetCore.Components.RenderFragment)((__builder2) => {__builder2.OpenComponent<BMenuItem>(6);__builder2.AddAttribute(7, "Route", "button");__builder2.AddAttribute(8, "ChildContent", (Microsoft.AspNetCore.Components.RenderFragment)((__builder3) => {__builder3.AddMarkupContent(9, "Button 按钮");}));__builder2.CloseComponent();}}}

可以看到,NavMenu.razor 使用了 BMenu 这个组件,BMenu 又使用了 BMenuItem这个组件,共套了两层,因此生成了两个 ChildContent 的属性,而且属性类型都是 Microsoft.AspNetCore.Components.RenderFragment
到这儿为止,Blazor 的大概机制基本讨论了一半,接下来讨论上个流程图中的对比那一步,看看 Blazor 是如何进行的对比
这里不细说,因为确实太复杂我也没搞清楚,只说个大概流程,需要说明的一点是 Blazor 的对比是基于序列号的,序列号是什么?大家一定注意到上面代码中的 __builder.AddAttribute(4 中的这个 4 了,这个 4 就是序列号,然后每个序列号对应的内容称为帧,简而言之是通过判断每个序列号对应的帧是否一致来对比是否有改动

Copy

流程图总算画完了,大概有以下几点需要注意:

  • 实际的对比过程是很复杂的,流程图是简化了再简化的结果,这篇文章的几个流程图需要结合在一起理解才行

  • 当走到设置新组件的参数这一步时,继续往下其实就是进入了新组件的生命周期流程,这个流程跟上面的生命周期流程是一样的

  • 结合所有流程图来看,如果只是组件本身重新渲染,那么组件本身设置参数的方法不会被触发,必须是它的父组件被渲染,才会触发它自己的设置参数的方法

  • 对比组件参数这一步,流程图比较笼统。我们可以简单的认为,没有组件的参数是不变化的,它的对比流程过于细节,我觉得没必要写进来。

渲染到此结束,下面就来谈谈 Blazor 会让我们遇到的问题

Blazor 的不足

优势我们就不谈了,我们来谈谈一个比较隐藏但又不容易解决的不足,这个不足就是我们一不小心就让我们的 Blazor 应用变得卡,而且还比较不容易解决,这个问题在服务端渲染的应用中尤其严重。

结合第一张流程图,浏览器产生任何事件都会发送到服务器端,想象一下你注册了一个 onmousemove 事件的话,还要不要活了?所以,大规模触发的事件尽量少注册,这里面的网络传输成本是很大的,而且也会给你的服务端造成很大的压力。

Blazor 应用变卡一般有以下几种情况,我们只讨论服务端应用的情况

  • 服务器端已经挂了,这种情况其实浏览器端会完全失去响应,除非你刷新

  • 你的代码有问题或你引用的库的代码有问题,导致进入死循环或循环次数非常多

第一点无所谓,第二点是要命的,至少对于我来说,一旦 Blazui 或 BlazAdmin 出现了卡的情况,会非常头疼,但实际上大多数情况都是第二种中,原因在于:

结合所有流程图来看,Blazor 完成渲染才会发送至浏览器,那么完成渲染的标准就是渲染队列被清空,那如果一直无法清空呢?体现出来就是死循环,或者说发生了一次点击事件结果循环了十次,这明显不科学(你故意的例外),而渲染队列被加入新东西大多数情况下是因为调用了 StateHasChanged 并且 ShuoldRender 返回了 true,或者是因为使用了 EventCallBack,这些代码所在的地方你全都难以调试
因为这些代码不是你的代码,所以你的断点也没处打,目前的 Blazor 不会告诉你到底是哪个组件哪行代码引起的死循环

还欠了点东西

还有一个关键的东西是 EventCallBack,一次写太多了,不想写了
园友如果有兴趣的话可以继续把这个写了
有任何问题可进QQ群交流:74522853

什么是前后端分离?

Blazor 出来的时候一堆人说什么 WebForm 又来了,Silverlight 又来了,还有啥啥乱七八糟的,最让我不能理解的是另一种说法:

前后端分离搞得好好的,微软为什么又要把前后端合在一起?

我不敢瞎说,我找了一篇文章:https://www.jianshu.com/p/bf3fa3ba2a8f
下面是摘抄的内容

1.首先要知道所有的程序都是一数据为基础的,没有数据的程序没有实际意义,程序的本质就是对程序的增删改查。

2.前后端分离就是把数据操作和显示分离出来。前端专注做数据显示,通过文字,图片或者图标等方式让数据形象直观的显示出来。后端专注做数据的操作。前端把数据发给后端,有后端对数据进行修改。

3.后端一般用java,c#等语言,现在的node属于JavaScript也能进行后端操作,此处不意义裂解语言。后端来进行数据库的链接,并对数据进行操作。

4.后端提供接口给前端调用,来触发后端对数据的操作。

基本原理就是这样,可能语言上不准确,思想是没有问题的。

作者:前端developer 链接:https://www.jianshu.com/p/bf3fa3ba2a8f 来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

重点在于第二点,前后端分离就是把数据操作和显示分离出来,Blazor 并没有有非要让你用 .Net 写后端
第三点也说了,前端一般是 JS,那现在把 JS 换成 .Net 并没有什么不一样

相关文章:

  • 基于 Blazui 的 Blazor 后台管理模板 BlazAdmin 正式尝鲜

  • Blazor 版 Bootstrap Admin 通用后台权限管理框架

  • .NET Core Blazor 1-Blazor项目文件分析

  • [Asp.net core 3.1] 通过一个小组件熟悉Blazor服务端组件开发

  • dotnet Blazor 用 C# 控制界面行为

  • 亲自实践Blazor构建桌面应用程序

  • 使用Asp.net Core3&Blazor 的全栈式网站开发体验

原文链接:https://www.cnblogs.com/wzxinchen/p/12082136.html


.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/312562.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

云原生

一、云原生概念的诞生云原生&#xff08;Cloud Native&#xff09;的概念&#xff0c;由来自Pivotal的MattStine于2013年首次提出&#xff0c;被一直延续使用至今。这个概念是Matt Stine根据其多年的架构和咨询经验总结出来的一个思想集合&#xff0c;并得到了社区的不断完善&a…

基于 Kubernetes 的 CICD 基础设施即代码

在上一篇基于 Kubernetes 的基础设施即代码一文中&#xff0c;我概要地介绍了基于 Kubernetes 的 .NET Core 微服务和 CI/CD 动手实践工作坊 使用的基础设施是如何使用代码描述的&#xff0c;以及它的自动化执行过程。如果要查看基于 Kubernetes 的基础设施即代码架构全图&…

Azure Arc:微软是怎么玩多云游戏的?

混合云在竞争性云提供商的基础上提供了来自云提供商的服务&#xff0c;从而使组织能够以不同方式一起使用来自不同供应商的云服务。例如&#xff0c;组织可以使用将数据存储在一个云中存储上的功能&#xff0c;而另一个云服务商则在该应用程序或数据之上运行。因此&#xff0c;…

当我们在谈 .NET Core 跨平台时,我们在谈些什么?--学习笔记

摘要.NET Framework在过去十多年在跨平台上的尝试。.NET Core跨平台的实现有何不同&#xff1f;基于 .NET Standard的平台兼容性是如何实现的&#xff1f;讲师介绍历史枷锁.NET Framework FCL CLR"跨平台"的 .NET Framework完全独立&#xff0c;各自为政复用之殇由…

IdentityServer4学习笔记汇总(实现传送门在底部)

前言互联网时代,对信息和资源的保护越发苛刻,在所有应用中授权和认证是必不可少缺少的一部分。如果一个应用没有授权和认证那么这个应用就是不完整或者说不安全的应用。在.Net平台给我们提供了一套完整的授权认证框架,那就是IdentityServer4。它实现了OpenId Connect和OAuth2.0…

多库操作2:终于实现多个数据库操作

▼更多精彩推荐&#xff0c;上午10点到达▼圣诞节快乐在上周的文章【多库操作&#xff1a;多个数据库的动态切换&#xff08;一&#xff09;】中&#xff0c;我们简单说了说&#xff0c;如何切换数据库&#xff0c;虽然实现了大部分的功能&#xff0c;但是最后也遗留了小问题&a…

服务发现技术是如何演进出来的?

昨天写了一篇<微服务的时间和成本去哪儿了>&#xff0c;有人在底下留言&#xff1a;我的回答是&#xff1a;"微服务可以不用服务发现和负载均衡吗&#xff1f;它是微服务一个核心组件。怎么能说没有关系&#xff1f;"我觉得有必要来思考和总结一下服务发现技术…

3分钟搞懂前后端开发的区别

上周末见了好多开发的年轻朋友&#xff0c;问了我一个问题&#xff1a;“前后端的区别和要求是什么&#xff1f;”分不清前后端开发的区别和要求&#xff0c;一种是因为前后端都了解&#xff0c;号称“全栈工程师”&#xff0c;但又什么都不是很精通&#xff1b;另一种是前端的…

基于 Kubernetes 的微服务部署即代码

在基于 Kubernetes 的基础设施即代码一文中&#xff0c;我概要地介绍了基于 Kubernetes 的 .NET Core 微服务和 CI/CD 动手实践工作坊使用的基础设施是如何使用代码描述的&#xff0c;以及它的自动化执行过程。如果要查看基于 Kubernetes 的基础设施即代码架构全图&#xff0c;…

使用Arduino开发ESP32开发环境搭建

1.打开Arduino IDE&#xff0c;选择文件->首选项->设置 复制下方的ESP32板管理网址&#xff0c;添加到附加开发板管理器中&#xff1a; https://dl.espressif.com/dl/package_esp32_index.json 2.选择&#xff1a;工具->开发板->开发板管理器 在弹出的对话框中搜索…

.NET ORM FreeSql 第一个正式版本发布 v1.0.0

一、简介FreeSql 是 .NET 平台下的对象关系映射技术(O/RM)&#xff0c;支持 .NetCore 2.1 或 .NetFramework 4.0 或 Xamarin。从 0.0.1 发布到今历时整整一年的迭代更新&#xff0c;现在终于敢发布第一个正式版。本文内容从简&#xff0c;介绍项目的主要功能框架&#xff0c;以…

Windows上搭建EMQTT服务器

1.官网下载EMQ 2.复制如图文件路径 3.打开终端cmd&#xff0c;输入&#xff1a; 再输入&#xff1a; 4.打开浏览器&#xff0c;进入&#xff1a;http://192.168.1.25:18083/* (localhost可打开终端输入ipconfig查看) 用户名&#xff1a;admin 密码&#xff1a;public

.NET解所有相机RAW格式照片

再聊.NET解相机RAW格式照片上次我发了一篇文章《用.NET解索尼相机ARW格式照片》&#xff0c;提到通过安装 SonyRawFileDecoder的方式&#xff0c;然后调用 WindowsImagingComponents来解析 RAW格式文件。后来我经过进一步研究、探索&#xff0c;发现还有更简单的办法。新的方法…

AspNetCore结合Redis实践消息队列

这是年中首发在博客园上的文章&#xff0c;个人觉得是AspNetCore结合Redis做的一次比较优秀的消息队列重构&#xff0c;其中对于点对点/发布-订阅的思路应该也是面试必考题。引言.Net TPL Dataflow是一个进程内数据流管道&#xff0c;应对高并发、低延迟的要求非常有效&#xf…

提升Azure App Service的几个建议

本文介绍了6个技巧&#xff0c;这些技巧可以改善Azure App Service托管应用程序的性能。其中一些技巧是你现在就可以进行的配置变更&#xff0c;而其他技巧则可能需要对应用程序进行一些重新设计和重构&#xff0c; 本文的几个技巧对于常规企业部署依旧有指引作用。长话短说开发…

单向链表的逆转(数据结构)(c语言)

逆转单向链表的意思是&#xff1a;给定你一个单向链表&#xff0c;一个整数n&#xff08;n为要逆转的结点数&#xff09;&#xff0c;要求你把链表从头结点到第n个结点给逆转过来 图示&#xff1a; 给出一个单向链表&#xff0c;一个整数n4。也就是要求把该链表从头结点&#x…

广东职业教育信息化研究会2019年会暨区块链专题研讨会

兹定于2019年12月28日&#xff08;星期六&#xff09;上午9:30召开广东职业教育信息化研究会2019年会暨专题研讨会&#xff0c;本次会议由广东职业教育信息化研究会主办&#xff0c;华南师范大学网络教育学院协办。会议地址&#xff1a;广州市天河区中山大道西55号华南师范大学…

如何快速融入一个团队?

作者&#xff1a;邹溪源&#xff0c;长沙资深互联网从业者&#xff0c;架构师社区特邀嘉宾&#xff01;一我们难免需要离开一个圈子&#xff0c;加入一个陌生的集体。毋庸置疑&#xff0c;离开熟知的圈子&#xff0c;走向未知的圈子难免会产生许多畏惧甚至情怯&#xff0c;这都…

关于C#异步编程你应该了解的几点建议

前段时间写了一篇关于C#异步编程入门的文章&#xff0c;你可以点击《C#异步编程入门看这篇就够了》查看。这篇文章我们来讨论下关于C#异步编程几个不成文的建议&#xff0c;希望对你写出高性能的异步编程代码有所帮助。注&#xff1a;本文的很多内容都是学习《Effective C#》的…

数据库分区

一、分区原理分区并不是生成新的数据表&#xff0c;而是将表的数据均衡分摊到不同的硬盘&#xff0c;系统或是不同服务器存储介子中&#xff0c;实际上还是一张表。要实现这一功能&#xff0c;首先要了解数据库对水平分区表进行分区存储的原理。数据库分区和分表相似&#xff0…