G
N
I
D
A
O
L

浏览器中的 .Net Core —— Blazor WebAssembly 初体验

前言

       在两年多以前就听闻 Blazor 框架,是 .Net 之父的业余实验性项目,其目的是探索 .Net 与 WebAssembly 的兼容性和应用前景。现在这个项目已经正式成为 Asp.Net Core 框架的一部分,公开了预览版,官方教程也基本写好上线了。就着这个机会,顺便体验一下这个框架用起来如何。

       之前在网上搜索 Blazor 的相关信息的时候发现吵得很厉害。前端开发者大多觉得有 Vue 之类的前端 MVVM 框架已经够用,没有 C# 插足的余地。甚至很多 C# 开发者也不知道这个框架的基本工作原理,觉得是把 C# 翻译成 js,翻译之后就变成了类似 Vue 的东西。还有人觉得这是下一个 Flash 或者 Silverlight。毛主席曾经说过:没有调查就没有发言权。对于说这种话的人,我只想说,少刷几分钟抖音快手随便搜下百度都能搞清楚怎么回事,作为 C# 开发者,这都理解不了我是真不知道是怎么学的 C#,难道真是传说中的拖控件一把梭,然后就没然后了?

       简单说明下 Blazor WebAssembly 的工作原理。就是在 WebAssembly 框架的基础上,实现了一个 .Net Core Runtime,用一个启动 js 下载相关 dll、初始化 .Net 虚拟机、启动虚拟机运行入口函数,接下来就和一个正常 .Net 程序一样,该怎么运行怎么运行。用 Java 的说法就是在浏览器中运行的 jvm。从此,.Net 跨平台领先 Java 一步,除了 Windows、Linux、MacOS之外,还要加上浏览器。悄悄说一下,浏览器上的运行时实现了 netstandard 2.1,待遇比传统的 .Net Framework 还好。要说缺点就是调试很麻烦,因为整个运行过程和服务器无关,在 VS 下断点也没用,不知道是预览版没做好还是什么原因,顺便导致出问题很难跟踪。还有改了代码要重新编译项目,不能像 MVC 那样改了 cshtml 刷新下浏览器就生效。每次重启调试太耐等了。

正文

       目前 Blazor WebAssembly 还不是默认项目模板的一部分,需要自行下载模板才能在 VS 2019 的项目模板里找到,需要的可以移步官方教程。不知道有多少园友知道我有个专门收集各种各种我觉得有趣的示例代码的项目,当然也有很多代码是我自己写的。我就冒出了一个想法,如何把这个项目也集成到我的现有项目中。毕竟创建独立的项目和在现有项目中融合新东西完全是两种感觉,很多组件一旦融合就会各种冲突打架,需要深入了解他们才能知道冲突有没有办法解决,要如何解决。

       经过几天的研究,我成功把 Blazor WebAssembly 项目融合进了我的主项目。同时进行了一些改造。主要方法还是先新建一个模板项目,然后对比代码差异,融合代码,补充 nuget 包。接下来简要说明下在现有 Asp.Net Core 项目中增加 Blazor WebAssembly 项目的主要步骤。在我的项目中,/blazor 是 Blazor 根目录,各种修改都配合这个设定,各位请根据自己的情况修改。

客户端

       1、新建一个包含 Asp.Net Core 宿主服务器的 Blazor WebAssembly 项目。纯 Blazor WebAssembly 项目发布后可以放到静态文件服务器,宿主服务器也只是当文件服务器用。把客户端项目复制到主项目解决方案中,在 VS 中添加现有项目。如果修改过项目名称和命名空间,请重启 VS,不然可能报错。

       2、复制共享项目到主项目的解决方案,修复项目引用。

       3、修改 wwwroot/index.html,修改 <head> 标签中的 <base href="/" /> 为 <base href="/blazor" />。

       4、在 wwwroot 文件夹新建文件夹 blazor,把 wwwroot 下的其他文件和文件夹放进 wwwroot/blazor 文件夹,避免和主项目路径冲突,同样地,主项目也不能再用 /blazor/xxx 了。

服务端

       1、安装 nuget 包 Microsoft.AspNetCore.Blazor.Server,要勾上包括预发行版,不然搜不到。

       2、在主项目中引用客户端项目和共享项目。

       3、在 Startup.ConfigureServices 中增加代码:

1 services.AddResponseCompression(opts =>
2 {
3     opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
4         new[] { "application/octet-stream" });
5 });

       4、在 Startup.Configure 中增加 app.UseBlazorDebugging(); 如果只想在开发环境使用,自行增加 if 判断。一般跟 app.UseDeveloperExceptionPage(); 放在一起。

       5、在 Startup.Configure 中注册 Blazor 文件,注意类型参数,是客户端项目的 Program 类:

app.UseClientSideBlazorFiles<BlazorApp.Client.Program>();

       6、在 Startup.Configure 中检查是否有并补充 app.UseStaticFiles();

       7、在 Startup.Configure 中注册路由终结点,注意 "/blazor/{**subPath}" 这一段,表示把 blazor 映射到 /blazor/xxx 去。{**subPath} 这一段是路由终结点参数捕获语法,里面的 subPath 可以乱写,但不能空着不填,不然启动不了。因为 Blazor WebAssembly 启动之后路由都是前端完成的,跟服务器没有任何关系,所以可以乱填。只是要满足系统的语法要求好让服务器能正常启动。

1 app.UseEndpoints(endpoints =>
2 {
3     //以前的 mvc、api 等等各种注册。
4 
5     //映射 Blazor 客户端终结点
6     endpoints.MapFallbackToClientSideBlazor<BlazorApp.Client.Program>("/blazor/{**subPath}", "index.html");
7 });

       至此,融合工作完成,可以正常启动项目并访问 /blazor 体验效果了。

效果预览

在两个浏览器(Chrome、Edge by Chromium)分别登录不同账号,分别使用 Blazor WebAssembly SignalR .Net Core Client 和 SignalR Javascript Client 连接 SignalR 服务,手机(Edge Android)再登录另一个账号使用 Blazor WebAssembly SignalR .Net Core Client 连接 SignalR 服务(域名是花生壳域名做 DDNS)。实现跨平台跨终端实时聊天。运行在 Release 发布模式。

 

 结语

       总体来说,Blazor 的体验还是很不错的,整体风格和 Asp.Net Core 几乎一摸一样,我看见的一瞬间就感觉非常亲切。依赖注入系统也可以正常使用,只是区别是 Scope 生命周期的实际效果和单例是一样的,因为整个应用就只有一个 Scope,但是 Blazor Server Side 就有区别了,每个 SignalR 连接绑定一个 Scope,掉线重连成功也会恢复到原先的 Scope ,一直连不上太久就不行了,整个服务器进程包含一个单例。所以在注册的时候尽量用 Scope,避免单例成习惯,缓不过来。

       Razor 语法也是个神一样的设计,最初是作为 MVC3 的视图引擎推出,在 MVC4 成为默认引擎。好像 MVC5 取消了 aspx 视图引擎支持,.Net Core 彻底取消了和 aspx 有关的一切东西。现在,Razor 又成为了一个前端渲染框架,真是老树发新芽,又是一春。在 html 模板渲染上,我用下来就是 Razor 的 @ 和 Vue 的双花括号语法特别顺手,aspx 那种尖括号语法实在看的头昏脑胀。本来 html 就各种尖括号,还要再来一堆尖括号,VS 高亮都看得头疼,更别说一般文本编辑器打开了,根本看不懂,到底谁和谁是一对?Razor 就特别爽,代码和标记自动识别切换,局部代码块,局部变量,比起 Vue 是有过之而无不及(Vue 的变量作用域师从 js,是真的晕)。

       根据目前使用的情况来看,只要不包含涉及系统底层调用的库都可以正常使用,比如和进程、线程、硬件驱动相关、本机 dll 互操作这种。

       我在模板项目中,增加了 SignalR 客户端使用示例。也是根据官方教程修改,而且使用的客户端库就是普通 .Net 客户端库,和控制台、桌面程序用是同一套 dll。微软果然神,到底是怎么把网络相关的 API 底层实现神不知鬼不觉地的换掉的。默认注入的 HttpClient 也是 System.Net.Http 命名空间的。

       由于网络通信底层实际上是依赖浏览器,所以浏览器会自动把 HttpClient 的请求嫁接到浏览器上,相关的 Headers、Cookies 自然也会自动携带上。所以如果 Blazor 应用和普通网页在同一个域,这些东西实际上会共享。我的身份认证相关功能就是利用这个特点偷懒实现的。如果不在同一个域,最简单实用的方法就是用 IdentityServer4 作为认证服务,客户端引用 nuget 包 IdentityModel,这个包会给 HttpClient 增加一堆用来和 OpenId Connect、OAuth2.0 协议交互的扩展方法,当作两个程序用开放协议配合工作来写就行。用 IdentityModel 扩展获取 Access Token,请求的时候把 Token 加进 HttpClient 的 Headers,当然也可以用 IdentityModel 的扩展来注入,更方便。

       对于 SPA 应用来说,状态管理一定是无法绕过的,不过在 Blazor 中,直接用依赖注入来管理状态就可以了。如果需要刷新页面也不丢失状态的话,可以考虑使用 ILocalStorage 服务来持久化状态,或者其他持久化方案也行,反正支持 js 互操作,先随便封装一个应急,等 C# 的原生组件出来了再看怎么办。

       与服务器的交互除了常规的 Web Api,还有内部预览阶段的 gRPC-Web,等这东西搞定了,Blazor 极限使用一切二进制数据,那效率不知道能提升多少。Asp.Net Core 3.0 全面支持 HTTP2,Chrome 好像从 70 往后也都支持 HTTP2,gRPC-Web 原生使用 HTTP2 肯定比现在包一层兼容层支持 HTTP1.1 来的好。可以说在浏览器的限制下,能做的应该都差不多。不知道以后浏览器会不会开放线程接口让 WebAssembly 使用内核线程执行计算密集型任务(好像会更方便黑客把浏览器当矿机啊,开放的问题也是多的不行,感觉浏览器就是个黑暗森林,网站服务器要防用户搞破坏,用户也要防网站用脚本搞破坏,这个猜疑链也导致浏览器各种限制,难啊)。

        作为一个杂食性开发者,对于 Blazor 与 Vue 的争论这种东西我是无所谓的,只要在我的知识范围内在我能接受的开发复杂度内解决问题,其他的都是浮云。就像邓爷爷说的:实践是检验真理的唯一标准;管他黑猫白猫,抓到耗子就是好猫;这才是我的信条,彻底的实用主义。

       啊,C# 这种强类型安全语言进入前端领域有点激动,不注意就说了一大通。被 js 那诡异的对象类型,动态作用域坑的实在是不行,敲键盘的时候心虚不知道会不会莫名其妙突然就出问题,实在是对心脏不好。还是喜欢 C#,什么东西都清晰明了,不埋暗坑,外加 DLR 和 dynamic,真是进可攻、退可守。js 完全没有退路,实在是伤不起。

       在最后发布以后才发现 Blazor 聊天进不去,调试正常,试了N多办法都没搞定,最后删除发布文件夹中的所有 dll,关闭 VS,删除 obj、bin 文件夹,重新发布才正常。真是坑爹。

 

       2020-5-26 更新:已经更新到候选版,迁移方法参见博文:Blazor WebAssembly 候选版迁移手记

 

       转载请完整保留以下内容并在显眼位置标注,未经授权删除以下内容进行转载盗用的,保留追究法律责任的权利!

  本文地址:https://www.cnblogs.com/coredx/p/12342936.html

  完整源代码:Github

  里面有各种小东西,这只是其中之一,不嫌弃的话可以Star一下。

posted @ 2020-02-22 16:58  coredx  阅读(9832)  评论(22编辑  收藏  举报