做这个的目的是想使用 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"

使用组件

<Window
x: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 仓库。

抽离BlazorWebview中的.Net与Javascript的互操作库的更多相关文章

  1. 【译】用jQuery 处理XML--浏览器中的XML与JavaScript

    用jQuery 处理XML--写在前面的话 用jQuery 处理XML-- DOM(文本对象模型)简介 用jQuery 处理XML--浏览器中的XML与JavaScript 用jQuery 处理XML ...

  2. 在C#中调用VBScript和JavaScript等脚本的实现

    在C#中调用VBScript.JavaScript等脚本的实现 作者:郑佐 2004-04-26 以前在做工作流(workflow)项目的时候,里面有一项就是在用户制定流程定义时可以编写脚本来控制活动 ...

  3. 解决eclipse-helios中Errors running builder JavaScript Validator的问题

    原文:http://blog.ywxyn.com/index.php/archives/713 解决eclipse-helios中Errors running builder JavaScript V ...

  4. 浏览器中的XML与JavaScript

    浏览器中的XML与JavaScript 在处理XML前,你需要在JavaScript中获取它.这一部分展示了一些不同的方法用来在JavaScript中获取XML并且对它进行处理. XML的节点类型 在 ...

  5. #Windows Phone:在HTML5专案中,如何从Javascript传送字串到C#的APP端

    原文:#Windows Phone:在HTML5专案中,如何从Javascript传送字串到C#的APP端 #Windows Phone:在HTML5专案中,如何从Javascript传送字串到C#的 ...

  6. 记录下项目中常用到的JavaScript/JQuery代码二(大量实例)

    记录下项目中常用到的JavaScript/JQuery代码一(大量实例) 1.input输入框监听变化 <input type="text" style="widt ...

  7. 第61节:Java中的DOM和Javascript技术

    Java中的DOM和Javascript技术 DOM是一门技术,是文档对象模型.所需的文档只有标记型文档,如我们所学的html文档(文档中的所有标签都封装成为对象了) DOM: 为Document O ...

  8. 在android中实现webview与javascript之间的交互(转)

    参见“在android中实现webview与javascript之间的交互”

  9. 面试中注意3个javascript的问题

    JavaScript 是所有现代浏览器的官方语言.因此,各种语言的开发者面试中都会遇到 JavaScript 问题. 本文不讲最新的 JavaScript 库,通用开发实践,或任何新的 ES6 函数. ...

  10. java editor template Eclipse中的快速Java\JavaScript代码模板使用

    java editor template Eclipse中的快速Java\JavaScript代码模板使用 学习了:http://technicalsearch.iteye.com/blog/2150 ...

随机推荐

  1. CDS标准视图:维修工单实际成本数据 I_MaintOrderActualCostDataCube

    视图名称:维修工单实际成本数据 I_MaintOrderActualCostDataCube 视图类型:基础 视图代码: 点击查看代码 @VDM.viewType: #COMPOSITE @AbapC ...

  2. Mac上安装mongoDB详细教程

    Mac OSX 平台安装 MongoDB MongoDB 提供了 OSX 平台上 64 位的安装包,你可以在官网下载安装包. 下载地址:https://www.mongodb.com/download ...

  3. 揭秘10种主流PLC在ModbusTCP通信中的速度表现!

    大家好!我是付工. 通透!终于把ModbusRTU弄明白了 这样看来,ModbusTCP协议太简单了 太简单了!C#轻松实现Modbus通信 前面给大家介绍了一系列关于Modbus和ModbusTCP ...

  4. 如何分析jmeter性能测试数据

    1.jmeter插件 https://blog.csdn.net/weixin_39430584/article/details/80947093 http://www.doc88.com/p-214 ...

  5. java重载-构造方法也存在重载-数据类型的提升

    重载 1.一个类中不能声明多个相同的方法,属性. 2.上面的相同指的是方法名,参数列表相同.和返回值类型无关. 3.如果方法名相同,但是参数列表(个数,顺序,类型)不相同,会认为是不同的方法,在jav ...

  6. RAW镜像格式介绍

    本文分享自天翼云开发者社区<RAW镜像格式介绍>,作者:z****n RAW(Raw Disk Image)是一种简单而基本的虚拟化镜像格式,用于存储虚拟机的磁盘内容.它是一种原始的二进制 ...

  7. Linux名词介绍

    AVX2:高级向量扩展指令集(英语:Advanced Vector Extensions,简称AVX)是x86架构微处理器中的指令集, AVX2指令集将大多数整数命令操作扩展到256位,并引入了熔合乘 ...

  8. Luogu P9869 NOIp2023 三值逻辑 题解 [ 绿 ] [ 带权并查集 ]

    三值逻辑:有点坑并且细节较繁琐,但有点板子的并查集. 修改操作 发现对于每个点,只有对他的最后一次操作才是有用的,所以记录下最终的祖先即可. 然而这里并不能用并查集来实现,因为并查集它具有的是传递性, ...

  9. [BZOJ3600] 没有人的算术 题解

    妙不可言!妙绝人寰! 单点修,区间查,包是线段树的.考虑如何比较两节点大小. 考虑二叉搜索树,我们只要再给每个节点附一个权值,就可以比较了! 注意力相当惊人的注意到,假如给每个点一个区间 \([l_x ...

  10. 傻妞PLUS机器人教程——安装

    特性 简单易用的消息搬运功能. 简单强大的自定义回复功能. 完整支持 ECMAScript 5.1 的插件系统,基于 otto. 支持通过内置的阉割版 Express / fetch ,接入互联网. ...