抽离BlazorWebview中的.Net与Javascript的互操作库
笔记哥 /
04-01 /
16点赞 /
0评论 /
343阅读
做这个的目的是想使用 Blazor 中的 Javascript 与 C#中的互操作,但是又不需要加载 Blazor 整个类库,另外 BlazorWebView 组件没有支持直接通过 Http 协议加载 web 页面,调试的时候需要先把后端接口写好,然后前端打包,然后一起调试,感觉很麻烦,因此想能不能把互操作这部分功能单独抽离出来。后面研究了 asp.net core 关于这部分的源码,发现可行,于是抽离出来了这部分功能,由于 Microsoft.JSInterop 这个 nuget 包不支持.Net Framework,顺便还移植到了.Net Framework 平台。正常使用已将近 1 年。现写文章记录回忆一下,也给有需要的朋友研究研究。
# 一、如何使用
带互操作的 WebView 已经支持了.Net Framework 下的 WPF 和 MAUI 中的安卓端。工作上需要这两个,其他平台暂时不支持。官方 nuget 仓库上,上传了最近一个 WPF 的版本。
### 1、安装
使用 nuget 包管理器搜索`HSoft.WebView.NetFramework.WPF`然后安装即可。
### 2、引入 Webview 组件
打开一个 xaml 文件,引入组件命名空间
```csharp
xmlns:wpf="clr-namespace:HSoft.WebView.NetFramework.WPF;assembly=HSoft.WebView.NetFramework.WPF"
```
使用组件
```csharp
```
如果是开发模式下,Source 填写你的前端服务器地址,生产环境,则一般填写``。项目新增一个 wwwroot 目录,然后编辑项目文件,添加如下节点,以便把网页文件嵌入程序集。
```csharp
```
你的网页启动页面位置如果是这样的`wwwroot\index.html`,则对应的Source为
# 二、原理
开门见山,借助 Microsoft.JSInterop 和前端的@microsoft/dotnet-js-interop 包,便可实现 Javascript和C#的互操作。这两个包定义除信息传递通道之外的所有必要的信息。因此,我们只需要把传送通道给补充上就可以正常工作。直接使用 Webview2 组件的 IPC 通讯,也就是 chrome.webview.postMessage 和 chrome.webview.addEventListener("message", (e: any))来发送和接受消息。
### 1、Javascript
在前端引入@microsoft/dotnet-js-interop 包。使用 DotNet.attachDispatcher 创建 dispatcher。
```csharp
import { DotNet } from "@microsoft/dotnet-js-interop";
let dispatcher: DotNet.ICallDispatcher;
dispatcher = DotNet.attachDispatcher({
sendByteArray: sendByteArray,
beginInvokeDotNetFromJS: beginInvokeDotNetFromJS,
endInvokeJSFromDotNet: endInvokeJSFromDotNet,
});
```
主要实现三个函数,这三个函数使用 postMessage 发送消息到.Net 端。
- sendByteArray(当传递参数中含有字节数组的时候调用这个)
- beginInvokeDotNetFromJS(从 JS 调用.Net 方法)
- endInvokeJSFromDotNet(从.Net 调用 JS,JS 这边处理完毕需要调用此方法告知.Net 调用完毕)
### sendByteArray
```csharp
function sendByteArray(id: number, data: Uint8Array): void {
const dataBase64Encoded = base64EncodeByteArray(data);
(window as any).chrome.webview.postMessage([
"ReceiveByteArrayFromJS",
id,
dataBase64Encoded,
]);
}
```
### beginInvokeDotNetFromJS
```csharp
function beginInvokeDotNetFromJS(
callId: number,
assemblyName: string | null,
methodIdentifier: string,
dotNetObjectId: number | null,
argsJson: string
): void {
console.log("beginInvokeDotNetFromJS");
(window as any).chrome.webview.postMessage([
"beginInvokeDotNetFromJS",
callId ? callId.toString() : null,
assemblyName,
methodIdentifier,
dotNetObjectId || 0,
argsJson,
]);
}
```
### endInvokeJSFromDotNet
```csharp
function endInvokeJSFromDotNet(
callId: number,
succeeded: boolean,
resultOrError: any
): void {
console.log("beginInvokeDotNetFromJS");
(window as any).chrome.webview.postMessage([
"endInvokeJSFromDotNet",
callId ? callId.toString() : null,
succeeded,
resultOrError,
]);
}
```
### 工具函数
```csharp
function base64EncodeByteArray(data: Uint8Array) {
// Base64 encode a (large) byte array
// Note `btoa(String.fromCharCode.apply(null, data as unknown as number[]));`
// isn't sufficient as the `apply` over a large array overflows the stack.
const charBytes = new Array(data.length);
for (var i = 0; i < data.length; i++) {
charBytes[i] = String.fromCharCode(data[i]);
}
const dataBase64Encoded = btoa(charBytes.join(""));
return dataBase64Encoded;
}
//
// TODO: If the data is large, consider switching over to the native decoder as in
// But don't force it to be async all the time. Yielding execution leads to perceptible lag.
function base64ToArrayBuffer(base64: string): Uint8Array {
const binaryString = atob(base64);
const length = binaryString.length;
const result = new Uint8Array(length);
for (let i = 0; i < length; i++) {
result[i] = binaryString.charCodeAt(i);
}
return result;
}
```
### 接收来自.Net 的消息并处理
```csharp
(window as any).chrome.webview.addEventListener("message", (e: any) => {
var ob = JSON.parse(e.data);
switch (ob[0]) {
case "EndInvokeDotNet": {
dispatcher.endInvokeDotNetFromJS(ob[1], ob[2], ob[3]);
break;
}
case "BeginInvokeJS": {
dispatcher.beginInvokeJSFromDotNet(ob[1], ob[2], ob[3], ob[4], ob[5]);
break;
}
case "SendByteArrayToJS": {
let id = ob[1];
let base64Data = ob[2];
const data = base64ToArrayBuffer(base64Data);
dispatcher.receiveByteArray(id,data);
break;
}
default: {
console.error(`不支持的消息类型${e.data}`);
}
}
});
```
### window 对象增加属性
```csharp
(window as any)["DotNet"] = DotNet;
export { DotNet };
```
### 完整代码
```csharp
import { DotNet } from "@microsoft/dotnet-js-interop";
let dispatcher: DotNet.ICallDispatcher;
dispatcher = DotNet.attachDispatcher({
sendByteArray: sendByteArray,
beginInvokeDotNetFromJS: beginInvokeDotNetFromJS,
endInvokeJSFromDotNet: endInvokeJSFromDotNet,
});
function sendByteArray(id: number, data: Uint8Array): void {
const dataBase64Encoded = base64EncodeByteArray(data);
(window as any).chrome.webview.postMessage([
"ReceiveByteArrayFromJS",
id,
dataBase64Encoded,
]);
}
function beginInvokeDotNetFromJS(
callId: number,
assemblyName: string | null,
methodIdentifier: string,
dotNetObjectId: number | null,
argsJson: string
): void {
console.log("beginInvokeDotNetFromJS");
(window as any).chrome.webview.postMessage([
"beginInvokeDotNetFromJS",
callId ? callId.toString() : null,
assemblyName,
methodIdentifier,
dotNetObjectId || 0,
argsJson,
]);
}
function endInvokeJSFromDotNet(
callId: number,
succeeded: boolean,
resultOrError: any
): void {
console.log("beginInvokeDotNetFromJS");
(window as any).chrome.webview.postMessage([
"endInvokeJSFromDotNet",
callId ? callId.toString() : null,
succeeded,
resultOrError,
]);
}
function base64EncodeByteArray(data: Uint8Array) {
// Base64 encode a (large) byte array
// Note `btoa(String.fromCharCode.apply(null, data as unknown as number[]));`
// isn't sufficient as the `apply` over a large array overflows the stack.
const charBytes = new Array(data.length);
for (var i = 0; i < data.length; i++) {
charBytes[i] = String.fromCharCode(data[i]);
}
const dataBase64Encoded = btoa(charBytes.join(""));
return dataBase64Encoded;
}
//
// TODO: If the data is large, consider switching over to the native decoder as in
// But don't force it to be async all the time. Yielding execution leads to perceptible lag.
function base64ToArrayBuffer(base64: string): Uint8Array {
const binaryString = atob(base64);
const length = binaryString.length;
const result = new Uint8Array(length);
for (let i = 0; i < length; i++) {
result[i] = binaryString.charCodeAt(i);
}
return result;
}
(window as any).chrome.webview.addEventListener("message", (e: any) => {
var ob = JSON.parse(e.data);
switch (ob[0]) {
case "EndInvokeDotNet": {
dispatcher.endInvokeDotNetFromJS(ob[1], ob[2], ob[3]);
break;
}
case "BeginInvokeJS": {
dispatcher.beginInvokeJSFromDotNet(ob[1], ob[2], ob[3], ob[4], ob[5]);
break;
}
case "SendByteArrayToJS": {
let id = ob[1];
let base64Data = ob[2];
const data = base64ToArrayBuffer(base64Data);
dispatcher.receiveByteArray(id,data);
break;
}
default: {
console.error(`不支持的消息类型${e.data}`);
}
}
});
(window as any)["DotNet"] = DotNet;
export { DotNet };
```
# 二、.Net
在.Net 这边类似,使用 WebView2 的 WebMessageReceived 事件和 PostWebMessageAsString 方法来与前端通讯,后端通过 WebMessageReceived 处理来自前端的**beginInvokeDotNetFromJS**、**endInvokeJSFromDotNet**、**ReceiveByteArrayFromJS**的消息,然后通过静态类 DotNetDispatcher 中的 BeginInvokeDotNet、EndInvokeJS、ReceiveByteArray 来处理,通过继承 JSRuntime,实现 BeginInvokeJS、EndInvokeDotNet、SendByteArray 方法,通过 PostWebMessageAsString 发送数据到前端。在这里不给出代码,感兴趣的直接查看 在下方资源处查看仓库代码。
本文来自投稿,不代表本站立场,如若转载,请注明出处:http//www.knowhub.vip/share/2/1903
相关资源
抽离BlazorWebview中的.Net与Javascript的互操作库DEMO
查看内容
- 热门的技术博文分享
- 1 . ESP实现Web服务器
- 2 . 从零到一:打造高效的金仓社区 API 集成到 MCP 服务方案
- 3 . 使用C#构建一个同时问多个LLM并总结的小工具
- 4 . .NET 原生驾驭 AI 新基建实战系列Milvus ── 大规模 AI 应用的向量数据库首选
- 5 . 在Avalonia/C#中使用依赖注入过程记录
- 6 . [设计模式/Java] 设计模式之工厂方法模式
- 7 . 5. RabbitMQ 消息队列中 Exchanges(交换机) 的详细说明
- 8 . SQL 中的各种连接 JOIN 的区别总结!
- 9 . JavaScript 中防抖和节流的多种实现方式及应用场景
- 10 . SaltStack 远程命令执行中文乱码问题
- 11 . 推荐10个 DeepSeek 神级提示词,建议搜藏起来使用
- 12 . C#基础:枚举、数组、类型、函数等解析
- 13 . VMware平台的Ubuntu部署完全分布式Hadoop环境
- 14 . C# 多项目打包时如何将项目引用转为包依赖
- 15 . Chrome 135 版本开发者工具(DevTools)更新内容
- 16 . 从零创建npm依赖,只需执行一条命令
- 17 . 关于 Newtonsoft.Json 和 System.Text.Json 混用导致的的序列化不识别的问题
- 18 . 大模型微调实战之训练数据集准备的艺术与科学
- 19 . Windows快速安装MongoDB之Mongo实战
- 20 . 探索 C# 14 新功能:实用特性为编程带来便利