C#Web开发之blazor体验

前言

先说结论:blazor做Web开发绝对好东西。对于我个人来说,有两个重要的原因:

  • 特别喜欢C#
  • 特别不喜欢JavaScript

在了解到blazor居然可以使用c#做Web开发且完全可以不用编写JavaScript代码时,第一感受是,居然有这么懂自己的东西,果断尝鲜体验!

网上大概也了解了一下,基本上都说得过于官方和理论,不够简单直接,这里直接说调研的结论。

  • 使用blazor做web开发非常地高效(包括前后端),而且还是在资料不多的情况下(生态不太好,但绝对是好东西)。
  • blazor提供两种模式:WebAssemblyServer
  • 建议使用 Server 模式,WebAssembly慢的受不了,一个简单的demo首次启动和每次刷新时都要将近 10秒钟时间,而相同demo项目Server模式基本是瞬间显示的。
  • WebAssembly模式还有一个弊端:目前只有一个线程,容易出现 UI 界面卡死的问题。据说以后会改,谁知道啥时候呢。
  • 简单的项目没必要搞什么前后分离,前后端分离也要看项目大小,小项目直接用混合模式也挺好的。
  • 简单项目使用 Server 模式就可以完全前后端一锅端了,非常的简单高效。
  • 如果前端设计能像客户端界面开发那样有控件拖放,就无敌了。

笔者本身不是做web开发的,但是对全栈比较感兴趣,在前几年也接触和体验了一下SpringBoot开发框架,参见:SpringBoot极简教程,因为个人不喜欢Java更青睐c#,所以体验之后弄清楚如何使用及流程也就作罢了,日后并没有再接触使用。但是有了之前的了解,至少有了web开发的基础知识储备,因此此次使用blazor也会比较地快和顺利。这一次也会以一个具体的demo项目来做介绍:搭建一个局域网环境使用的APP管理平台

背景是这样的:在很久之前我们团队就想搭建一个这样的内部平台,但是我们都是客户端开发(非前端开发),对这块没有经验也不擅长,也就作罢了。我们是做安卓安全研究的,日常会遇到各种各样的样本,具有重复使用的价值,但是都散落在组内各人自己的电脑里,没有汇总起来,时间久了也容易找不到。如果能在内网搭建一个这样的样本管理平台就好了,遇到新样本的时候上传上去,需要的时候检索下载下来。我们也想过用共享文件夹ftp服务器的形式搭建,但是这都是单纯的文件上传和下载,没有样本的基础信息,并没有使用开来。

但这个需求也不算是刚需,只能说有了更好。但是在遇到blazor的时候,我就想大胆试一下,本身C#的开发效率非常高,心想应该花不了多少时间,于是开始构思:

  • 需要有文件上传的功能;
  • 需要有对APP样本文件进行解析的功能,并做基础信息的展示;
  • 能检索和下载样本文件;

本着快速编程实现的想法,就想着如果能找到现成的代码改一改就行了,于是从blazor文件上传的关键词找到了这个demo:File Upload with Blazor WebAssembly and ASP.NET Core Web API,GitHub地址:CodeMazeBlog/blazor-series at blazor-file-upload。工程里面有两个项目,分别是server(服务端)和client(客户端)。这是一个前后端分离的项目,结构比较清晰,如果打算上手学习blazor,是一个非常好的入门练手项目。这是一个管理产品(product)的demo,基本上和我的需求类似,所以就拿这个开刀修改起来。遇到了一些坑:

  • 这个前端是WebAssembly模式,问题很严重,就是前面提的速度超级慢和界面卡死问题;
  • 服务端连接的数据库是SQL Server的,这个遇到问题不好解决花了一些时间,网上MySQL的资料多一些,好在及时换成了MySQL

全程是以逆向工程的思路去实现的,遇到了非常多的问题,基本就是遇到一个解决一个,最终把项目跑起来了,效果图如下:

C#Web开发之blazor体验_第1张图片

主要功能介绍:

  • 打开链接 http://[ip地址]:5000/apps 即可显示样本列表;
  • 支持按照名称检索样本;
  • 支持按照不同字段排序,默认按照更新时间倒叙显示;
  • 支持一键浏览本地文件上传,服务端自动解析样本信息并入库;
  • 支持样本信息的删除和更新;
  • 默认局域网内http方式无登录授权访问;
  • 点击图标下载样本到本地电脑;

后面的教程会以正向工程的顺序讲解,然后穿插一些实际修改demo项目的经验和问题。

一、数据库配置

1、数据库软件安装

  • MariaDB,账号密码弄个测试的简单一点的:root/123
  • 安装:ODBC Connector - MariaDB Knowledge Base
  • Navicat:可视化操作数据库,不用敲SQL命令,方面快捷;

2、VisualStudio连接MySQL

VisualStudio默认使用的数据库是SQL Server,先改为链接MySQL的数据库,这里使用开源的MariaDBVisualStudio链接MariaDB数据库需要通过ODBC方式,因此需要安装ODBC Connector - MariaDB Knowledge Base,然后进行ODBC数据源配置:

  • 打开 ODBC数据源管理程序(Win + R 输入命令:odbcad32 );
  • 添加,选择:MariaDB ODBC 3.0 Driver,Name填:MariaDB,下一步;
  • 选择:TCP/IP,ServerName填:127.0.0.1,User name和Password分别填:root 123
  • Test DSN,提示成功说明配置正确,后面一路下一步即可。
  • VisualStudio里打开菜单工具 - 连接到数据库,选择:Microsoft ODBC 数据源,刚刚的MariaDB名称,就能在服务器资源管理器里看到数据库链接了。

参考:

  • MariaDB + Visual Studio 2017 环境下的 ODBC 入门开发

  • 参考:How to Use MySQL Database With .Net Core And Entity Framework?

3、创建数据库和表

  • 打开MariaDB控制台,创建数据库:CREATE DATABASE appdb;然后source apps.sql;,apps.sql内容如下(可以在Navicat里设计,比较方便):
/*
 Navicat Premium Data Transfer

 Source Server         : 127.0.0.1
 Source Server Type    : MySQL
 Source Server Version : 101100 (10.11.0-MariaDB)
 Source Host           : localhost:3306
 Source Schema         : appdb

 Target Server Type    : MySQL
 Target Server Version : 101100 (10.11.0-MariaDB)
 File Encoding         : 65001

 Date: 18/11/2022 00:16:42
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for apps
-- ----------------------------
DROP TABLE IF EXISTS `apps`;
CREATE TABLE `apps`  (
  `Id` bigint NOT NULL AUTO_INCREMENT,
  `Name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
  `Package` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
  `Icon` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL,
  `VersionName` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `VersionCode` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `ShellName` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `Tags` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `Memo` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `Operator` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `dbPath` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL,
  `Url` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL,
  `CreationDate` datetime(6) NOT NULL DEFAULT current_timestamp(6),
  `ModifiedDate` datetime(6) NOT NULL DEFAULT current_timestamp(6) ON UPDATE CURRENT_TIMESTAMP(6),
  `Md5` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '',
  `FileSize` int UNSIGNED NULL DEFAULT 0,
  PRIMARY KEY (`Id`) USING BTREE,
  UNIQUE INDEX `index_id`(`Id` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 41 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Records of apps
-- ----------------------------
INSERT INTO `apps` VALUES (36, '测试App', 'com.test', 'https://appimg.dbankcdn.com/application/icon144/629336112d984dc1950f3992e0efdae0.png', '1.1', '2133', '360', NULL, '这个包有xxx的问题,版本发布的时候可以测一下', 'zhangsan', 'share\\app\\dytt8.apk', 'file:\\\\192.168.1.2\\share\\app\\dytt8.apk', '2022-11-17 23:44:00.872375', '2022-11-17 23:55:03.068345', NULL, 1973292);
INSERT INTO `apps` VALUES (38, '测试App', 'com.test', 'https://appimg.dbankcdn.com/application/icon144/629336112d984dc1950f3992e0efdae0.png', '1.1', '2133', '无', '加固 隐私', '这个包有xxx的问题,版本发布的时候可以测一下', 'zhangsan', 'share\\app\\184543_3.apk', 'file:\\\\192.168.1.2\\share\\app\\184543_3.apk', '2022-11-17 23:47:57.061125', '2022-11-18 00:10:47.843875', NULL, 46668230);
INSERT INTO `apps` VALUES (39, '测试App', 'com.test', 'https://appimg.dbankcdn.com/application/icon144/629336112d984dc1950f3992e0efdae0.png', '1.1', '2133', '无', NULL, '这个包有xxx的问题,版本发布的时候可以测一下', 'zhangsan', 'share\\app\\dytt8.apk', 'file:\\\\192.168.1.2\\share\\app\\dytt8.apk', '2022-11-18 00:02:13.713289', '2022-11-18 00:02:13.715084', NULL, 1973292);

SET FOREIGN_KEY_CHECKS = 1;
  • 创建一个名为AppInfo的Model:

    public class AppInfo {
        //[Key]
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public long Id { get; set; }
    
        // App名称
    
        //[Required(ErrorMessage = "Name is required field")]
        [StringLength(maximumLength: 64)]
        public string Name { get; set; }
    
        // 包名
    
        //[Required(ErrorMessage = "Package is required field")]
        [StringLength(maximumLength: 100, MinimumLength = 3)]
        public string Package { get; set; }
    
        // 图标
        public string Icon { get; set; }
    
        // 版本号
        [StringLength(maximumLength: 32)]
        public string VersionName { get; set; }
    
        [StringLength(maximumLength: 32)]
        public string VersionCode { get; set; }
    
        // 壳特征名称
        [StringLength(maximumLength: 32)]
        public string ShellName { get; set; } = "null";
    
        // 标签
        [StringLength(maximumLength: 64)]
        public string Tags { get; set; }
    
        // 备注
        [StringLength(maximumLength: 128)]
        public string Memo { get; set; }
    
    
        // 操作人员
        [StringLength(maximumLength: 32)]
        public string Operator { get; set; }
    
    
        // 数据库存储路径(相对路径)
        public string dbPath { get; set; }
    
        public string Url { get; set; }
    
        public DateTime CreationDate { get; set; }
    
        // 对应的是自动更新时间的数据库字段
        [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
        public DateTime ModifiedDate { get; set; }
    
        public string Md5 { get; set; }
    
        public long FileSize { get; set; }
    }
    
  • 服务端项目(BlazorDemo.Server)添加三方库:Pomelo.EntityFrameworkCore.MySql,然后修改Startup.cs函数ConfigureServices的代码为:

    var connectionString = Configuration.GetConnectionString("sqlConnection");
    services.AddDbContext(opt => opt.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString)));
    
  • 服务端appsettings.json修改为:

    {
      "Logging": {
        "LogLevel": {
          "Default": "Information",
          "Microsoft": "Warning",
          "Microsoft.Hosting.Lifetime": "Information",
          "Microsoft.EntityFrameworkCore": "Information"
        }
      },
      "ConnectionStrings": {
        "sqlConnection": "SERVER=127.0.0.1;UID=root;PWD=123; database=appdb;"
      },
      "AllowedHosts": "*"
    }
    

4、数据库迁移操作

在VisualStudio的PM控制台下输入命令。

Add-Migration Initial
Update-Database
Remove-Migration

二、服务器配置

一共需要设置 4 个服务器,分别是:

  1. 部署前端的服务器;

  2. 部署API接口的服务器;

  3. 数据库服务器;

  4. 文件下载服务器;

其实前两个服务器可以合并为一个。因为我们是内网使用,所有的服务器最后都用了同一台Windows普通电脑来完成。

先说最简单的文件服务器,直接使用Python的一句话命令就可以完成了。在电脑上选择一个目录作为存储样本文件使用,例如E:\share,然后在该目录下创建一个批处理文件httpServer.bat,内容:python -m http.server,每次需要启动文件服务器的时候运行下httpServer.bat即可。如果全部使用dotnet技术栈,内容也可以是:

Rem python -m http.server
Rem dotnet tool install --global dotnet-serve
dotnet-serve -p 8000 -a 0.0.0.0

前后端可以分离开发,也可以混合开发。一开始借鉴的开源代码是前后端分离的,但是后来因为修改成了server模式,变成了混合模式。但是无论哪种都可以把前后端的项目部署在同一台服务器上,仅仅是开放的端口不一样罢了。最终配置如下:

  • 服务端(BlazorDemo.Server)的launchSettings.json内容:

    {
      "iisSettings": {
        "windowsAuthentication": false,
        "anonymousAuthentication": true,
        "iisExpress": {
          "applicationUrl": "http://localhost:52497",
          "sslPort": 0
        }
      },
      "profiles": {
        "BlazorDemo.Server": {
          "commandName": "Project",
          "launchBrowser": false,
          "applicationUrl": "http://localhost:5010;http://*:5010",
          "environmentVariables": {
            "ASPNETCORE_ENVIRONMENT": "Development"
          }
        }
      }
    }
    
  • 前端(BlazorDemo.Client)的appsettings.json内容:

    {
      "Logging": {
        "LogLevel": {
          "Default": "Information",
          "Microsoft.AspNetCore": "Warning"
        }
      },
      "AllowedHosts": "*"
    }
    

三、支持局域网访问

https的配置比较麻烦,而且第一次玩这个,不想太花时间折腾,本着先跑通的想法,先简单弄起来。如果是新创建项目的话,就不要勾选https即可。

  • server项目里面Startup.cs文件Configure函数注释掉这两句调用:

    //app.UseHttpsRedirection();
    //app.UseAuthorization();
    
  • server项目launchSettings.json去掉对https的支持,修改为:

    {
      "iisSettings": {
        "windowsAuthentication": false,
        "anonymousAuthentication": true,
        "iisExpress": {
          "applicationUrl": "http://localhost:52497",
          "sslPort": 0
        }
      },
      "profiles": {
        "BlazorProducts.Server": {
          "commandName": "Project",
          "launchBrowser": false,
          "applicationUrl": "http://localhost:5010;http://*:5010",
          "environmentVariables": {
            "ASPNETCORE_ENVIRONMENT": "Development"
          }
        }
      }
    }
    
  • client项目的launchSettings.json去掉对https的支持,修改为:

{
  "profiles": {
    "BlazorProducts.Client": {
      "commandName": "Project",
      "dotnetRunMessages": "true",
      "launchBrowser": true,
      "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
      "applicationUrl": "http://localhost:5000;http://*:5000;",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    }
  }
}
  • client项目代码里面凡是涉及到API接口调用的地方,一律修改为 http://作为服务器的电脑的具体IP地址:5010

四、界面组件

千万不要自己从零去写,没经验的话比较花时间,强烈推荐使用 Bootstrap Blazor - 企业级 UI 组件库 ,是个开源项目,GitHub地址见:BootstrapBlazor,总体使用比较方便,官网有详细使用教程和示例代码,直接拿来使用即可。文件上传功能就是直接用了这个 上传组件 Upload ,很方便,开发效率很高,使用的组件情况如下。

删除确认弹框:


文件上传:


    
private async Task OnClickToUpload(UploadFile file) {
	await SaveToFile(file);
}

消息通知(消息提示):

[Inject]
[NotNull]
private MessageService? MessageService { get; set; }


await MessageService.Show(new MessageOption() {
	Content = e.Message
});

五、全局异常处理

这个可以借鉴 深入浅出Blazor webassembly之异常错误显示,也可以直接使用 全局异常 - Bootstrap Blazor - 企业级 UI 组件库。

六、发布部署

发布

两种方法:

  1. 使用VisualStudio菜单:生成 - 发布,按照向导生成。
  2. 使用dotnet命令:dotnet publish -c Release

部署

  • 选择一台Windows电脑作为服务器使用,记录其 IP 地址;

  • 安装:dotnet、MariaDB(账号密码弄个测试的简单一点的:root/123)、ODBC Connector - MariaDB Knowledge Base

  • 配置好数据库;

  • 选择一个文件夹作为http服务器,目录下放好httpServer.bat,一键运行开启http服务。

  • 为保证局域网的其他设备能够访问,客户端和服务端的appsettings.json做好类似如下配置:

    "AllowedHosts": "*",
    "Urls": "http://*:5010",
    
  • 配置ApkInfoTool工具;

  • 启动服务端:dotnet run

  • 启动前端:dotnet run

  • 其他设备访问地址:http://IP地址:5000

七、注意事项

  • 服务器项目里的接口函数一律要用anync Task返回类型,否则容易出现错误:Cannot access a disposed context instance,参考:Resolved- DBContext ObjectDisposedException:Cannot access a disposed of object

  • WebAssembly的缺点:不支持多线程容易UI卡死;启动和刷新速度特别慢;

  • 新建项目的时候勾选ASP.NET Core托管自动生成的解决方案包含三个项目,服务端也一并包含;

  • 回调函数用await InvokeAsync,例如:

    _timer = new Timer(async (sender) => {
        await InvokeAsync(() => {
            SearchCallback.InvokeAsync(SearchText);
        });
        _timer.Dispose();
    }, null, 500, 0);
    
  • UI的响应函数类型要用async Task,如果内部没有调用异步函数,可以在最后调用下:await Task.CompletedTask;,例如:

    public partial class Search {
        private Timer _timer;
    
        public string SearchText { get; set; }
    
        [Parameter]
        public EventCallback SearchCallback { get; set; }
    
        private async Task OnSearch() {
            if (_timer != null)
                _timer.Dispose();
    
            _timer = new Timer(async (sender) => {
                await InvokeAsync(() => {
                    SearchCallback.InvokeAsync(SearchText);
                });
                _timer.Dispose();
            }, null, 500, 0);
            await Task.CompletedTask;
        }
    }
    

参考

  • 学习ASP.NET Core Blazor编程系列四——迁移 - DotNet菜园
  • net::ERR_CERT_AUTHORITY_INVALID 在 ASP.NET Core 中
  • ASP.NET内核中的net::ERR_CERT_AUTHORITY_INVALID - 问答 - 腾讯云开发者社区-腾讯云
  • MariaDB + Visual Studio 2017 环境下的 ODBC 入门开发
  • 文件上传参考:Upload files using InputFile component in Blazor,上传进度参考:c# web api post with progress reporting
  • 增删改查参考:Asp.Net Core Blazor 开发体验
  • fix to OnInitializedAsync being called twice in Blazor server project
  • Blazor Navigation: Update URL without changing reloading page

开源项目参考:

  • CodeMazeBlog/blazor-series at blazor-file-upload
  • ErikXu/kubernetes-filesystem: System to browse, upload or download files for pods or containers in kubernetes.
  • WebApiClient: An open source project based on the HttpClient. You only need to define the c# interface and modify the related features to invoke the client library of the remote http interface asynchronously.
  • 原文 — 朱皮特的烂笔头

你可能感兴趣的:(其他,c#,前端,开发语言)