马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
做这个的目的是想利用 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:
- 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 目录,然后编辑项目文件,添加如下节点,以便把网页文件嵌入程序集。- <Window
- x:
- 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>
复制代码 你的网页启动页面位置如果是这样的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 };
复制代码 2、.Net
在.Net 这边类似,利用 WebView2 的 WebMessageReceived 变乱和 PostWebMessageAsString 方法来与前端通讯,后端通过 WebMessageReceived 处理来自前端的beginInvokeDotNetFromJS、endInvokeJSFromDotNet、ReceiveByteArrayFromJS的消息,然后通过静态类 DotNetDispatcher 中的 BeginInvokeDotNet、EndInvokeJS、ReceiveByteArray 来处理,通过继承 JSRuntime,实现 BeginInvokeJS、EndInvokeDotNet、SendByteArray 方法,通过 PostWebMessageAsString 发送数据到前端。在这里不给出代码,感兴趣的直接查看 https://github.com/HekunX/wvf 仓库。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
|