前言

对于后端程序员来说,前端看起来很简单,但是各种框架非常多,如流行的前端三大框架Vue.js、Angular.js、React.js。每种框架又对应了多种匹配的类库,想打造前端开发完整的知识体系是不容易的。而看起来高大上的一些技术如图技术、分布式计算、云渲染等都多少跟前端技术有一定的联系,就萌生了做全栈开发的想法。
做全栈,最好的是一个技术打天下,而不是完全不相关的技术体系,本人十多年C++研发经验,可以说对C++的各种场景的研发算是比较清楚的,也因此造成了对其他技术的一些偏见。最近做各种项目,才发现某些语言的开发是真香(C#的扩展方法、反射机制、Linq、包管理等极大的提升了开发效率;NODEJS前后端一起开发,一套代码解决一次编译解决多端应用;Vue框架让我不在关心Dom操作)。技术是为解决问题服务,本身没有什么好坏之分。在特定场景下用好特定的技术,能够短平快的解决项目中的问题就是好技术。做全栈开发,最重要的是打通各种语言的链路(以C++为基础,打通各个语言的障碍)。
Webassembly技术就是一种可以让后端人员开发前端程序的一种技术,在使用Blazor之前是通过C++的EMSDK技术开发C++的Webassembly程序(其难度对于项目开发来说是地狱级别,编译、调试、接口封装非常废时间)。所以最近转栈到其他语言的WebAssembly开发。才发现了微软的Blazor 的C# WebAssembly的解决方案。

什么是Blazor

Blazor 是微软在 .NET 里推出的一个 WEB 客户端 UI 交互的框架,使用 Blazor 你可以代替 JavaScript 来实现自己的页面交互逻辑,可以很大程度上进行 C# 代码的复用,Blazor 对于 .NET 开发人员来说是一个不错的选择。
Blazor 有两种托管模式,一种是 Blazor Server 模式,基于 asp.net core 部署,客户端和服务器的交互通过 SignalR 来完成,来实现客户端 UI 的更新和行为的交互。
另外一种是 Blazor WebAssembly 模式, 将 Blazor 应用、其依赖项以及 .NET 运行时下载到浏览器(所以有一个缺陷就是在不同的端中可能要重新编译), 应用将在浏览器线程中直接执行。具有以下优点:
Blazor WebAssembly 托管模型具有以下优点

没有 .NET 服务器端依赖,应用下载到客户端后即可正常运行。
可充分利用客户端资源和功能。
工作可从服务器转移到客户端。
无需 ASP.NET Core Web 服务器即可托管应用。 无服务器部署方案可行,例如通过内容分发网络 (CDN) 为应用提供服务的方案。

Blazor WebAssembly 托管模型具有以下局限性:

应用仅可使用浏览器功能。
需要可用的客户端硬件和软件(例如 WebAssembly 支持)。
下载项大小较大,应用加载耗时较长。
.NET 运行时和工具支持不够完善。 例如,.NET Standard 支持和调试方面存在限制。

本文主要介绍WebAssembly技术。

Blazor安装

  1. 安装Asp.net 环境 并确认勾选WebAssembly组件

  1. 安装完成之后打开VS选择Blazor项目,选择Blazor WebAssembly应用,后续使用默认选项即可

  1. 创建项目之后如图

  1. 编译之后运行

Blazor项目结构

这是创建一个Blazor WebAssembly的大致模板

  • Counter 组件 (Counter.razor):实现“计数器”页面。
  • FetchData 组件 (FetchData.razor):实现“提取数据”页面。
  • Index 组件 (Index.razor):实现主页。
  • Properties/launchSettings.json:保留开发环境配置。
  • Shared 文件夹:包含以下共享组件和样式表:
  • MainLayout 组件 (MainLayout.razor):应用的布局组件
  • MainLayout.razor.css:应用主布局的样式表。
  • NavMenu 组件 (NavMenu.razor):实现边栏导航。 包括 NavLink 组件 (NavLink),该组件可向其他 Razor 组件呈现导航链接。 NavLink 组件会在系统加载其组件时自动指示选定状态,这有助于用户了解当前显示的组件。
  • NavMenu.razor.css:应用导航菜单的样式表。
  • SurveyPrompt 组件 (SurveyPrompt.razor):Blazor 调查组件(子组件)
  • wwwroot:应用的 Web 根目录文件夹,其中包含应用的公共静态资产,包括 appsettings.json 和配置设置的环境应用设置文件。 index.html 网页是实现为 HTML 页面的应用的根页面.

最初请求应用的任何页面时,都会呈现此页面并在响应中返回。
此页面指定根 App 组件的呈现位置。 使用 app 的 id (

Loading…

) 在 div DOM 元素的位置呈现组件。

  • _Imports.razor:包括要包含在应用组件 (.razor) 中的常见 Razor 指令,如用于命名空间的 @using 指令,相当于全局的引用组件的命令
  • App.razor:应用的根组件,用于使用 Router 组件来设置客户端路由。 Router 组件会截获浏览器导航并呈现与请求的地址匹配的页面。
  • Program.cs:应用入口点,用于设置 WebAssembly 主机:

App 组件是应用的根组件。 对于根组件集合 (builder.RootComponents.Add(“#app”)),使用 app 的 id(wwwroot/index.html 中的

Loading…

)将 App 组件指定为 div DOM 元素。
添加并配置了服务(例如,builder.Services.AddSingleton<IMyDependency, MyDependency>())。

编写第一个页面

Blazor的页面编写使用的是razor的语法,不是类似于前端的JS代码或者HTML写法,razor语法可参考相关文章

Blazor的页面结构有点类似于Vue的结构,有页面结构+逻辑代码组成。

@page "/counter"  //页面路由,如果是一个独立的页面需要加上这个页面标记,如果作为一个子组件则没有必要<PageTitle>Counter</PageTitle>  //<h1>Counter</h1><p role="status">Current count: @currentCount</p> //@是razor的语法,用于绑定C#的属性或者方法来的<button class="btn btn-primary" @onclick="IncrementCount">Click me</button> //直接调用方法//C#代码块@code {private int currentCount = 0;private void IncrementCount(){currentCount++;}}

这里面一开始看的时候会比较迷惑,C#和页面代码写在一起了,而页面代码竟然可以调用C#的代码,也没有一个类的结构。要理解这个界面我们应该把这个组件理解为一个类对象来处理,页面和逻辑代码同属于一个类里面,虽然有的时候看起来在两个不同的文件里面,但就是一个类(继承ComponentBase类),只是分不同的地方编写而已。关于这块C#的组织方式可以参考
razor组件的C#代码组织形式 https://www.cnblogs.com/harrychinese/p/blazaor_CSharp_code.html
在这里面随便介绍下razor的相关指令

@code是Razor 组件专用,用于在@code代码块中定义页面类需要的字段,属性,方法等定义。@code是@functions的别名。但是建议在Razor组件中使用@code,而非@functions.@implements 指令,类比如C# 类的implements.@inherits: 类比C#的中继承,在Blazor的类的组织方式里面有说明@inject: 从DI容器中引入需要的类,一个单例类的引入。在C#类中使用[inject]标记@layout: 指定页面需要继承的布局模板。@model: 专用于MVC 视图和Razor Page页面,用于指定model@namespace: 用户指定生成的页面类所处的名称空间。@page指令:用户razor page项目或者razor 组件,用于定义路由。@preservewhitespace: 是否保留空白,默认是false.@section: 用户razor page或者是mvc

使用Skia实现Blazor的绘图功能

在Blazor实现绘图可以使用类似于html的canvas功能,使用库 Blazor.Extensions.Canvas实现,内部有很多示例。由于在项目使用skia库比较多,所以对于Blazor绘图使用skia库来实现.

1. skia库安装

使用nuget安装SkiaSharp 和 SkiaSharp.Views.Blazor库,SkiaSharp是基础库,SkiaSharp.Views.Blazor 负责图面的显示。

2. 创建绘图页面

  1. 添加一个绘图页面,本人直接修改主页面 skiacanvas.razor
  2. 添加一个对应的页面的代码类 skiacanvas.razor.cs 实现与skiacanvas.razor页面进行绑定
  3. 在skiacanvas.razor中添加SKCanvasView标签,设置IgnorePixelScaling属性为true(表示为忽略按像素缩放,不然在鼠标事件中得到的偏移值与实际值是有偏差的,这个跟系统的屏幕缩放比例成一个正比关系),指定@ref=“canvasView”这个canvasView对象是在C#代码中定义的成员变量 SKCanvasView canvasView
<Layout ShowFooter="false">   // 这里面使用了BootstrapBlazor组件库进行页面布局<Header><PageTitle>XDGraph WEB系统</PageTitle><br/></Header>
<Main><SKCanvasView @ref="canvasView" IgnorePixelScaling=true style="@CursorType" OnPaintSurface="OnPaintSurface"  @onkeydown="OnCanvasKeyDown" @onmousemove="OnCanvasMouseMove"  @onwheel="OnCanvasMouseWheel" @onmousedown="OnCanvasMouseDown" tabindex="0"@onmouseup="OnCanvasMouseUp"/>
</Main>
</Layout>

注意事项:

  1. 默认情况下SKCanvasView标签是不支持键盘事件的(与html5的canvas类似),要响应键盘事件需要给定 tabindex属性
  2. OnPaintSurface 事件是必须实现的,因为所有的绘图都是在OnPaintSurface事件中进行
  3. canvasView绑定对象的初始化并不是在对应的c#代码的构造函数中,而是在页面初始化完成之后。

3. SkiaCanvas.Razor.cs代码实现

以下是代码,实际代码在项目中不可直接贴出来.

using SkiaSharp;
using SkiaSharp.Views.Blazor;
public partial class SkiaCanvas
{protected SKCanvasView canvasView=null;public void OnCanvasKeyDown(KeyboardEventArgs e){//键盘事件}public void OnCanvasMouseWheel(WheelEventArgs e){}public void OnCanvasMouseMove(MouseEventArgs e){var mouse_event = toEvent(e);_component.MouseMove(mouse_event);move_point =new Point(mouse_event.OffsetX,mouse_event.OffsetY);}public void OnPaintSurface(SKPaintSurfaceEventArgs e){var canvas = e.Surface.Canvas;//SKBitmap bitmap = new SKBitmap(e.Info.Width, e.Info.Height);canvas.Clear(SKColors.White);//调用底层函数绘制_component.Graph.View.Redraw(canvas);//绘制坐标;using var paint = new SKPaint{Color = SKColors.Black,IsAntialias = true,Typeface = SkiaChinaFont.ChinaFont,TextSize = 24};if (move_point != null){//move_point是mousemove事件中获取的,直接在渲染接口中好像没有办法获取到;var msg1 = $"x:{move_point.X.ToString("F2")} y:{move_point.Y.ToString("F2")}";canvas.DrawText(msg1, 0, e.Info.Height-30, paint);}}}

4. 关于绘图界面的自适应问题

SKCanvasView的窗口大小使用浏览器窗口变化,组件中标签大小的设置通过style="width:900px;height=900px"类似的代码来实现,razor组件代码里面没有对应的事件响应,包括默认前端语言也没有,一般通过监听window.reszie事件来处理。这就涉及到C#与JS的交互问题。所幸的是有一个开源库帮我们封装好了 BlazorPro.BlazorSize这个库解决了大小改变的适应性问题。具体参考库的使用方法.

下面代码在原来的代码上增加以下代码实现.

    public static string DefaultStyle= "width:100%;heiht:100%;";[Parameter]public string CursorType { get; set; } = DefaultStyle;protected override void OnAfterRender(bool firstRender){if (firstRender){// Subscribe to the OnResized event. This will do work when the browser is resized.listener.OnResized += WindowResized;}}void WindowResized(object? sender,BrowserWindowSize window){browser = window;int width = browser.Width;int height = browser.Height-100;DefaultStyle = $"width:100%;height:{height}px;";CursorType = DefaultStyle;StateHasChanged();}

5. 中文文字显示

默认情况下Skia绘制中文字符显示不出来,这个时候需要添加对中文字体的显示.

 public static class SkiaChinaFont{public static SKTypeface ChinaFont { get; private set; }static SkiaChinaFont(){//加载资源方案,设置字体文件属性为嵌入的资源(需要注意)try{//嵌入资源的访问是应用程序名加上路径,路径使用.而不是使用/。//如XDGraphWeb.res.DroidSansFallback.ttf 表示的是应用程序名为XDGraphWeb 在当前项目的res文件夹下的DroidSansFallback.ttf文件.var fontStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("XDGraphWeb.res.DroidSansFallback.ttf");ChinaFont = SKTypeface.FromStream(fontStream);}catch(Exception e){var error=e.Message;int c = 0;}}}

6. 成果

Blazor之静态资源的访问

在WebAssembly中的静态资源有两种一种是C#的内嵌资源,还有一种是wwwroot目录下的文件访问。

1. 内嵌资源

我们平时接触的更多的是本地文件系统,或者是 FTP 、对象存储这类运行在远程服务器上的文件系统,这些都是非内嵌资源,所以,内嵌资源主要是指那些没有目录层级的文件资源,因为它会在编译的时候“嵌入”到动态链接库(DLL)中。像之前引入skia中的资源文件就是属于内嵌资源的访问。

  1. 在 Visual Studio 中选中指定文件,在其属性窗口中选择生成操作为嵌入的资源


这样,我们就完成了内嵌资源的定义。而定义内嵌资源,本质上还是为了在运行时期间去读取和使用,那么,自然而然地,我们不禁要问,该怎么读取这些内嵌资源呢?在Assembly类中,微软为我们提供了下列接口来处理内嵌资源。

public virtual ManifestResourceInfo GetManifestResourceInfo(string resourceName);
public virtual string[] GetManifestResourceNames();//返回资源名称
public virtual Stream GetManifestResourceStream(Type type, string name);//获取资源中的留数据
public virtual Stream GetManifestResourceStream(string name);

通过GetManifestResourceNames这个方法我们发现其资源是通过A.B.C的路径方式来访问的.

var assembly = Assembly.GetExecutingAssembly();
var resources = assembly.GetManifestResourceNames();
resources.ToList().ForEach(x => Console.WriteLine(x));
var fileInfo = assembly.GetManifestResourceInfo(resources[0]);
var fileStream = assembly.GetManifestResourceStream(resources[0]);//获取文件的字节流

2. httpClient

HttpClient 类所在库为 System.Net.Http,Blazor webassembly 默认模版已经自动将HttpClient 注册到DI 容器中了, 使用起来非常方便. Program.Main 函数注册DI容器代码

builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });

直接使用 HttpClient 问题有:

  • HttpClient 主要问题是, 即使 Dispose 之后, 也不能即时关闭 socket 连接, 在 windows 下, 默认需要等 240秒之后才能关闭 socket. 短时大量使用 HttpClient, 会将客户端和服务器端 socket 连接消耗殆尽, 详见参考文档1的分析. 所以, 客户端应用程序一般使用单例模式使用 HttpClient 类. Blazor webassembly 也是如此.
  • 如果使用单例模式, 如要为 不同url 设置不同的 header, 就很不方便.
  • HttpClient 还会缓存 IP, 如果 DNS 之后有更新, HttpClient 仍会使用老的 IP

所以一般情况下都是将 HttpClient注册为单例类,注册为单例之后在Razor.cs代码为

 //直接获取单例类;
[Inject]
private HttpClient Http { get; set; }  //一定要设置为get;set 否则会崩溃。HttpClient只能设置在这样的组件类中设置,普通类设置无效还有可能出问题。

HttpClient的使用

// 读取html数据,在wwwroot目录下的数据
var xml_string= await Http.GetStringAsync("/sample-data/defaultConfig.Xml");
//获取json数据protected override async Task OnInitializedAsync()
{Items = await Http.GetFromJsonAsync<MenuItem[]>("sample-data/menu.json");  return;
}

Blazor之BootstrapBlazor界面库

https://www.blazor.zone/introduction BootstrapBlazor界面库提供了近200个组件,基本涵盖了前端开发的大多数界面使用。

  1. 对话框的使用

@using System.ComponentModel.DataAnnotations
@using Microsoft.Extensions.Localization
@using System.Xml
<div><ValidateForm Model="Project" OnValidSubmit="OnSubmit"><div class="row g-3"><div class="col-12"><InputUpload @bind-Value="@Project.Mxe" DisplayText="工程文件名"  /></div><div class="col-12"><Button ButtonType="@ButtonType.Submit" Text="提交"></Button></div></div></ValidateForm>
</div>
@code
{//表单验证设置private class PersonInfo{[Required][FileValidation(Extensions = new string[] { ".mxe" }, FileSize = 2*1024 * 1024)]public IBrowserFile? Mxe { get; set; }}private PersonInfo Project { get; set; } = new PersonInfo();//对话框的参数传递,返回值必须是object[CascadingParameter(Name = "BodyContext")]public object? Graph { get; set; }//设置回调函数参数;[Parameter]public Action? OnClose { get; set; }//执行提交功能,打开本地文件,相当于实现了文件上传功能private async Task OnSubmit(EditContext context){var graph=Graph as XDGraph;if(graph==null || Project.Mxe==null){return;}MemoryStream ms = new MemoryStream();await  Project.Mxe.OpenReadStream().CopyToAsync(ms);//从内存数据流中读取文件;XmlDocument docxml = new XmlDocument();byte[] b = ms.ToArray();string s = System.Text.Encoding.UTF8.GetString(b, 0, b.Length);docxml.LoadXml(s);//删除历史;graph.Editor().UndoManager().Clear();var codec = new XDCodec();graph.SetModel(codec.DecodeGraphModel(docxml, graph.Model));graph.Repaint();if(OnClose!=null){OnClose.Invoke();}return ;}
}

使用对话框功能

public async Task OpenFile(){var option = new DialogOption(){Title = "打开本地工程文件",IsKeyboard=true,ShowHeaderCloseButton=true};//也可以当成参数传递;option.BodyContext = MainGrpah;option.BodyTemplate = BootstrapDynamicComponent.CreateComponent<OpenDialogComponent>(new Dictionary<string, object?>{[nameof(OpenDialogComponent.OnClose)] = new Action(async () => await option.Dialog.Close())}).Render();await DialogService.Show(option);}

Blazor 相关学习网站

Github上关于Blazor的例子 https://github.com/syncfusion/blazor-samples
Blazor.Extensions.Canvas https://github.com/mizrael/BlazorCanvas
SkiaSharp在网页上绘图 https://www.cnblogs.com/sunnytrudeau/p/15574467.html
bibi视频教程 https://www.bilibili.com/video/BV19K4y1e7kd?p=1
Blazor官网 https://docs.microsoft.com/zh-cn/aspnet/core/blazor/?view=aspnetcore-6.0
https://github.com/AdrienTorris/awesome-blaz
blazor界面库 https://www.blazor.zone/introduction
blazor的C#组织方式 https://www.cnblogs.com/harrychinese/p/blazaor_CSharp_code.html
Blazor的C#和JS互操作 https://www.cnblogs.com/functionMC/p/16552500.html

Blazor开发WEB程序相关推荐

  1. 用JSP+JDBC开发Web程序

    以前一直想找个纯粹的JSP+JDBC开发Web程序的架构,一直没有找到合适的,后来自己写了一个简单实现,并实施了几个项目. 此开发架构的特点是: 1.架构技术简单,只包含JSP和JDBC,不需要学习即 ...

  2. [存档]使用.Net开发web程序时现在比较流行的前台技术都有什么?

    如题,我一直做winform项目,过些天有个web项目.我想知道前台设计现在流行什么呀,Silverlight.ExtJS还是JQuery等.另外开发web程序有没有什么流行的框架呀.像java的Sp ...

  3. python 题库系统,Python可以开发Web程序,也可以管理操作系统。

    Python可以开发Web程序,也可以管理操作系统. 更多相关问题 请论述什么是正常?什么是异常? 哪些用地需要招标.拍卖或者挂牌出让国有建设用地使用权? 如何防止给水管道振动? 下列哪些事件的发生会 ...

  4. 开发 web 程序服务 之 源码分析

    文章目录 开发 web 程序服务 之 源码分析 前言 http 包源码 路由部分 监听和服务部分 mux 库源码 源码分析 创建路由 路由匹配 总结 开发 web 程序服务 之 源码分析 前言 本文的 ...

  5. [Web开发] Web程序调式的利器 - Fiddler (HTTP协议监视工具)

    在做Web开发的时候,了解你的Web程序和IE如何通讯是非常有用的,尤其是做Web程序的性能优化.Fiddler 就是这么一个HTTP协议调试利器,它由微软IE开发组的一个工程师开发,可以帮助你全面分 ...

  6. python可以开发web程序吗_【分享|python部署开发的web程序有9种方法】- 环球网校...

    [摘要]当今世界充满了各种数据,而python是其中一种的重要组成部分.然而,若想其有所应用,我们需要对这些python理论进行实践.其中包含很多有趣的的过程,然后将其用于某些方面.其中python部 ...

  7. Go语言开发Web程序

    Go语言实现编辑.保存.查看,代码量比Java少很多,确实不错 将以下文件放到同一个目录,运行 go run wiki.go 启动web程序,打开浏览器 http://localhost:8080/e ...

  8. java se 开发web程序_JDiy快速开发WEB之javaSE环境搭建-初级

    大学的时候对web开发很感兴趣,对网页中的动画,对用户注册,对网页中表格填写等等都倍感兴趣.加之又有专业课程编程语言java,因此,对java web产生了浓厚的兴趣,再加之有北京圣思园 风中叶 大师 ...

  9. python创建虚拟环境_Python学习笔记:创建Python开发Web程序的虚拟环境

    学习Excel技术,关注微信公众号: excelperfect 这段时间利用业余时间在断断续续地看Eric Matthes著的<Python编程从入门到实践>这本书,毫不夸张地说,这真的是 ...

最新文章

  1. VS中 无法创建虚拟目录 本地IIS IIS Express 外部主机
  2. python本地读csv文件_python读写csv文件方法详细总结
  3. 通过零知识证明,成为重要的区块链革新者
  4. 如何在零停机的情况下迁移 Kubernetes 集群
  5. 为何各家抢滩物联网?
  6. Ubuntu 8.04 Linux + Apache2 + MySQL5 + PHP + Tomcat5.5 整合安装
  7. COMSOL:案列应用实操教学---光电
  8. Snipaste截图软件安装、使用详细教程(附下载链接)
  9. Java程序二进制转化为十进制_用java程序实现二进制像十进制转化或十进 – 手机爱问...
  10. 杭州电子科技大学acm--2016
  11. 超详细教程:YOLO_V3(yolov3)训练自己的数据
  12. 【经典】吴恩达《机器学习》课程
  13. B. Catching Cheaters(cf)dp
  14. 大数据学习——相关资源
  15. 歌德巴赫猜想---java
  16. IOS 系统振动调用
  17. 5层因特网协议栈 和 7层OSI参考模型
  18. JavaScript内置方法-Date对象
  19. Chip天线(WiFi/蓝牙陶瓷芯片天线) 选型
  20. 打印机 WIA 无法停止服务,Windows无法停止Windows Image Acquisition(WIA)服务(位于本地计算 机上)。

热门文章

  1. hadoop、hive安装
  2. shell之awk命令详解
  3. 校园人脸识别门禁的实施方案有效隔离闲杂人员
  4. Unity中的异步编程【1】—— Unity与async 、 await
  5. Centos7:solr伪集群(SolrCloud)搭建
  6. Xilinx Aurora 8B/10B IP核详解和仿真
  7. BGP路径属性与选路原则
  8. .NET编程和SQL Server ——Sql Server 与CLR集成
  9. 游戏行业网页整站模板下载_游戏 整站 高光 传奇 黑色
  10. 可爱猫+python3+Flask+aiohttp简单搭建微信机器人