抽离BlazorWebview中的.Net与Javascript的互操作库

news/2025/4/2 22:55:40/文章来源:https://www.cnblogs.com/hkfyf/p/18804314

做这个的目的是想使用 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 文件,引入组件命名空间

xmlns:wpf="clr-namespace:HSoft.WebView.NetFramework.WPF;assembly=HSoft.WebView.NetFramework.WPF"

使用组件

<Windowx:Class="TestWVF.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:local="clr-namespace:TestWVF"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"xmlns:wpf="clr-namespace:HSoft.WebView.NetFramework.WPF;assembly=HSoft.WebView.NetFramework.WPF"Title="MainWindow"Width="800"Height="450"mc:Ignorable="d"><Grid><wpf:WebView Source="http://localhost:5173" /></Grid>
</Window>

如果是开发模式下,Source 填写你的前端服务器地址,生产环境,则一般填写http://0.0.0.0/index.html。项目新增一个 wwwroot 目录,然后编辑项目文件,添加如下节点,以便把网页文件嵌入程序集。

<?xml version="1.0" encoding="utf-8"?>
<Project><!--...--><ItemGroup><EmbeddedResource Include="wwwroot\**\*"></EmbeddedResource></ItemGroup><!--...-->
</Project>

你的网页启动页面位置如果是这样的wwwroot\index.html,则对应的Source为http://0.0.0.0/index.html。

二、原理

开门见山,借助 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。

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

function sendByteArray(id: number, data: Uint8Array): void {const dataBase64Encoded = base64EncodeByteArray(data);(window as any).chrome.webview.postMessage(["ReceiveByteArrayFromJS",id,dataBase64Encoded,]);
}

beginInvokeDotNetFromJS

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

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;
}// https://stackoverflow.com/a/21797381
// TODO: If the data is large, consider switching over to the native decoder as in https://stackoverflow.com/a/54123275
// 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 的消息并处理

(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 对象增加属性

(window as any)["DotNet"] = DotNet;
export { DotNet };

完整代码

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;
}
// https://stackoverflow.com/a/21797381
// TODO: If the data is large, consider switching over to the native decoder as in https://stackoverflow.com/a/54123275
// 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 处理来自前端的beginInvokeDotNetFromJSendInvokeJSFromDotNetReceiveByteArrayFromJS的消息,然后通过静态类 DotNetDispatcher 中的 BeginInvokeDotNet、EndInvokeJS、ReceiveByteArray 来处理,通过继承 JSRuntime,实现 BeginInvokeJS、EndInvokeDotNet、SendByteArray 方法,通过 PostWebMessageAsString 发送数据到前端。在这里不给出代码,感兴趣的直接查看 https://github.com/HekunX/wvf 仓库。

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

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

相关文章

算法备案没产品可以申请吗?

算法备案复审阶段涉及产品信息填报,所以一度让一些开发者有这样的错误认知:只有等产品要上线了,才能火急火燎地去申请算法备案。但这个观点其实是错误的,其实开发者也可以在没有具体产品的情况下发起算法备案申请。只要材料合法合规,也能取得备案号。下面是一些具体信息介…

工业通信协议“牵手密码”,Ethernet IP转Profinet网关的桥梁魔法

在当前工业自动化领域,实时以太网技术已经成为至关重要的通信标准之一。Profinet和EtherNetIP作为两种广泛采用的实时以太网协议,各自拥有其独特的性能优势和适用场景。本文旨在探讨稳联技术Profinet转EtherNetIP网关WL-PN-EIPM的功能,并评估其在节能实施与监测方面的应用价…

LeetCode刷题-动态规划-爬楼梯

LeetCode刷题-动态规划-爬楼梯 题目: 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢? 示例 1: 输入:n = 2 输出:2 解释:有两种方法可以爬到楼顶。1 阶 + 1 阶 2 阶 示例 2:输入:n = 3 输出:3 解释:有…

【攻防世界】Hidden-Message

⭕、知识点 流量分析/端口号隐写/tshark/json文件处理 一、题目二、解法 1、端口号个位呈现有规律的01交替,可能隐藏信息。 2、为便于提取信息,使用kali的tshark对其进行转存 tshark -r input.pcap -T json > output.txt注意在使用tshark时应避免使用root账户 否则会出现如…

022 props组件交互

.vue 的文件,就是一个组件,每个.vue 文件就是每个页面html 的时候,每个页面都是一个 htmlvue2 和 vue3 的生命周期钩子是不同的components:常用的组件,公共的组件views:用来存放页面的新建项目,删除HelloWorld.vue components也删除views删除 这个index.js删除 这两页面…

客户端打开BI报表提示 Your current browser is not supported”

win7的打开会报这个问题, win11可以正常打开, 应该是环境差异导致。

Linux-常用命令(3)

Linux-常用命令(3)Linux常用命令 查看文件 cat命令 cat命令可以创建一个或者多个文件、查看文件内容、连接文件,常用于查看文件内容 cat 文件名 //显示文件内容 cat -n 文件名 //显示文件内容,并显示行号 cat - 文件名 //显示文件内容(包括不可见字符)系统时间 date命令…

【EI】机器人与传感器网络国际会议(RoSeN 2025)

第一届机器人与传感器网络国际会议(RoSeN 2025)将于2025年5月16-18日在贵阳举行,会议将围绕机器人展开的在机器人、人机交互、传感、智能控制等相关研究领域,邀请国内外数位在此领域学术卓越的学者专家做相关致辞与报告,共同探讨机器人发展最新发展方向及行业前沿动态。会…

[转]玩客云刷armbian后根目录扩展

地址:玩客云刷armbian后根目录扩展_IT码迹最近拼夕夕搞了个玩客云,自己懒得刷机(太麻烦,还要绝育什么的)所以直接买的刷好的,商家送了个U盘32G已经做好了镜像。 商家镜像刷了不少东西除了openwrt,其他几个docker镜像都是armbian比较好用的。不过在我要安装其他插件的时候发…

生成未来:解码智能技术驱动的产业革命

在人工智能浪潮的推动下,AI生图与视频技术正以惊人的速度重塑人类的生产方式。从一张图片的生成到一段视频的秒级渲染,技术的突破不仅解放了生产力,更催生了全新的商业生态。这场变革的核心,在于用算法替代重复劳动,以智能激发无限创意,而这一切仅仅是开端。 一、技术突破…

云终端远程自动调用开关机功能

云桌面项目由于缺少一键关机和开机功能,通过Linux实现自动化调用开机和关机 1、收集所有终端信息的MAC地址收集方式可以采用ipscan25.exe也可以通过cmd下arp -a方式收集MAC地址,同时记录MAC可以IP地址的对应关系。2、所有终端安装openssh使用系统自带或者下载OpenSSH-Win64-v…