利用 Avalonia UI 构建 Blazor 混合应用程序

在这里插入图片描述

Blazor 是一个 .NET 前端框架,用于仅使用 .NET 技术构建 Web 应用程序。2021 年,Blazor 扩展到桌面端,推出了 Blazor Hybrid(混合),使开发者可以在桌面平台上使用已有的技能。

Blazor 混合应用程序是传统的桌面应用程序,它们在一个 Web View 控件中托管实际的 Blazor Web 应用程序。虽然这些应用程序使用 .NET MAUI 作为桌面端技术,但如果不符合需求,也可以使用其他框架。

MAUI 的局限性在于它缺乏对 Linux 的支持,并且在 Windows 和 macOS 上使用不同的 Browser Engine。Microsoft Edge 和 Safari 在实现 Web 标准、执行 JavaScript 以及页面渲染方面存在差异。这些差异在高级应用程序中可能会导致 bug 并需要额外的测试。

如果 MAUI 不符合您的要求,可以考虑选择 Avalonia UI,它是一个跨平台的 UI 库,其生态系统中包含多个基于 Chromium 的 Web View。

在本文中,我们将探讨如何使用 Avalonia UI 和 DotNetBrowser 作为 Web View 来创建 Blazor 混合应用程序。

使用模板快速入门

要使用 DotNetBrowser 和 Avalonia UI 创建一个基本的 Blazor 混合应用程序,请使用我们的模板:

dotnet new install DotNetBrowser.Templates

然后,获取 DotNetBrowser 的免费 30 天试用许可证。

从模板创建一个 Blazor 混合应用程序,并将您的许可证密钥作为参数传递:

dotnet new dotnetbrowser.blazor.avalonia.app -o Blazor.AvaloniaUi -li <your_license_key>

然后运行应用程序:

dotnet run --project Blazor.AvaloniaUi

在 Linux 上的 Avalonia UI 上运行 Blazor 混合应用程序在 Linux 上的 Avalonia UI 上运行 Blazor 混合应用程序

实现

在混合环境中,Blazor 应用程序在其桌面壳程序的进程中运行。这个壳程序或窗口管理整个应用程序的生命周期,显示 Web View,并启动 Blazor 应用程序。我们将使用 Avalonia UI 创建这个窗口。

Blazor 应用程序的后端是 .NET 代码,前端是托管在 Web View 中的 Web 内容。 Web View 中的 Browser Engine 和 .NET 运行时之间没有直接连接。因此,为了前后端通信,Blazor 必须知道如何在它们之间交换数据。由于我们引入了一个新的 Web View,我们必须教会 Blazor 如何使用 DotNetBrowser 进行数据交换。

接下来,我们将带您了解 Blazor 与 Avalonia 和 DotNetBrowser 集成的关键部分。有关完整解决方案,请查看上面的模板。

创建窗口

为了托管 Blazor 混合应用程序,我们需要创建一个常规的 Avalonia 窗口,并添加一个 Web View 组件。

MainWindow.axaml

<Window ... Closed="Window_Closed"><browser:BlazorBrowserView x:Name="BrowserView" ... />...</browser:BlazorBrowserView>
</Window>

MainWindow.axaml.cs

public partial class MainWindow : Window
{public MainWindow(){InitializeComponent();...	BrowserView.Initialize();}private void Window_Closed(object sender, EventArgs e){BrowserView.Shutdown();}
}

BlazorBrowserView 是我们为了封装 DotNetBrowser 而创建的一个 Avalonia 控件。稍后,我们将在这个控件中将其与 Blazor 集成。

BlazorBrowserView.axaml

<UserControl ...>...<avaloniaUi:BrowserView x:Name="BrowserView" IsVisible="False" ... />
</UserControl>

BlazorBrowserView.axaml.cs

public partial class BlazorBrowserView : UserControl
{private IEngine engine;private IBrowser browser;public BlazorBrowserView(){InitializeComponent();}public async Task Initialize(){EngineOptions engineOptions = new EngineOptions.Builder{RenderingMode = RenderingMode.HardwareAccelerated}.Build();engine = await EngineFactory.CreateAsync(engineOptions);browser = engine.CreateBrowser();...Dispatcher.UIThread.InvokeAsync(ShowView);}public void Shutdown(){engine?.Dispose();}private void ShowView(){BrowserView.InitializeFrom(browser);BrowserView.IsVisible = true;browser?.Focus();}
}

配置 Blazor

在混合应用程序中,负责 Blazor 与环境集成的主要实体是 WebViewManager。这是一个抽象类,因此我们需要创建自己的实现,这里我们称之为 BrowserManager 并在 BlazorBrowserView 中实例化它。

BrowserManager.cs

class BrowserManager : WebViewManager
{private static readonly string AppHostAddress = "0.0.0.0";private static readonly string AppOrigin = $"https://{AppHostAddress}/";private static readonly Uri AppOriginUri = new(AppOrigin);private IBrowser Browser { get; }public BrowserManager(IBrowser browser, IServiceProvider provider,Dispatcher dispatcher,IFileProvider fileProvider,JSComponentConfigurationStore jsComponents,string hostPageRelativePath): base(provider, dispatcher, AppOriginUri, fileProvider, jsComponents,hostPageRelativePath){Browser = browser;}...
}

BlazorBrowserView.axaml.cs

public partial class BlazorBrowserView : UserControl
{private IEngine engine;private IBrowser browser;private BrowserManager browserManager;...public async Task Initialize(){EngineOptions engineOptions = new EngineOptions.Builder{RenderingMode = RenderingMode.HardwareAccelerated}.Build();engine = await EngineFactory.CreateAsync(engineOptions);browser = engine.CreateBrowser();...browserManager = new BrowserManager(browser, ...);...}...
}

一个 Blazor 应用程序需要一个或多个根组件。当 Web View 正在初始化时,我们将它们添加到 WebViewManager 中。

RootComponent.cs

public class RootComponent
{public string ComponentType { get; set; }public IDictionary<string, object> Parameters { get; set; }public string Selector { get; set; }public Task AddToWebViewManagerAsync(BrowserManager browserManager){ParameterView parameterView = Parameters == null? ParameterView.Empty: ParameterView.FromDictionary(Parameters);return browserManager?.AddRootComponentAsync(Type.GetType(ComponentType)!, Selector, parameterView);}
}

BlazorBrowserView.axaml.cs

public partial class BlazorBrowserView : UserControl
{private IEngine engine;private IBrowser browser;private BrowserManager browserManager;public ObservableCollection<RootComponent> RootComponents { get; set; } = new();...public async Task Initialize(){...engine = await EngineFactory.CreateAsync(engineOptions);browser = engine.CreateBrowser();browserManager = new BrowserManager(browser, ...);foreach (RootComponent rootComponent in RootComponents){await rootComponent.AddToWebViewManagerAsync(browserManager);}...}...
}

MainWindow.axaml

<Window ... Closed="Window_Closed"><browser:BlazorBrowserView x:Name="BrowserView" ... /><browser:BlazorBrowserView.RootComponents><browser:RootComponent Selector="..." ComponentType="..." /></browser:BlazorBrowserView.RootComponents></browser:BlazorBrowserView>
</Window>

加载静态资源

在普通的 Web 应用程序中,Browser 通过向服务器发送 HTTP 请求来加载页面和静态资源。在 Blazor 混合应用程序中,虽然原理相似,但这里并没有传统的服务器。相反,WebViewManager 提供了一个名为 TryGetResponseContent 的方法,该方法接受一个 URL 并返回数据作为类似 HTTP 的响应。

我们通过拦截 DotNetBrowser 中的 HTTPS 流量将 HTTP 请求和响应传递到此方法并返回。

BlazorBrowserView.axaml.cs

public partial class BlazorBrowserView : UserControl
{private IEngine engine;private IBrowser browser;private BrowserManager browserManager;...public async Task Initialize(){EngineOptions engineOptions = new EngineOptions.Builder{RenderingMode = RenderingMode.HardwareAccelerated,Schemes ={{Scheme.Https,new Handler<InterceptRequestParameters,InterceptRequestResponse>(OnHandleRequest)}}}.Build();engine = await EngineFactory.CreateAsync(engineOptions);browser = engine.CreateBrowser();browserManager = new BrowserManager(browser, ...);...}public InterceptRequestResponse OnHandleRequest(InterceptRequestParameters params) =>browserManager?.OnHandleRequest(params);...
}

BrowserManager.cs

internal class BrowserManager : WebViewManager
{private static readonly string AppHostAddress = "0.0.0.0";private static readonly string AppOrigin = $"https://{AppHostAddress}/";private static readonly Uri AppOriginUri = new(AppOrigin);...public InterceptRequestResponse OnHandleRequest(InterceptRequestParameters p){if (!p.UrlRequest.Url.StartsWith(AppOrigin)){// 如果请求不以 AppOrigin 开头,则允许它通过。return InterceptRequestResponse.Proceed();}ResourceType resourceType = p.UrlRequest.ResourceType;bool allowFallbackOnHostPage = resourceType is ResourceType.MainFrameor ResourceType.Faviconor ResourceType.SubResource;if (TryGetResponseContent(p.UrlRequest.Url, allowFallbackOnHostPage,out int statusCode, out string _,out Stream content,out IDictionary<string, string> headers)){UrlRequestJob urlRequestJob = p.Network.CreateUrlRequestJob(p.UrlRequest,new UrlRequestJobOptions{HttpStatusCode = (HttpStatusCode)statusCode,Headers = headers.Select(pair => new HttpHeader(pair.Key, pair.Value)).ToList()});Task.Run(() =>{using (MemoryStream memoryStream = new()){content.CopyTo(memoryStream);urlRequestJob.Write(memoryStream.ToArray());}urlRequestJob.Complete();});return InterceptRequestResponse.Intercept(urlRequestJob);}return InterceptRequestResponse.Proceed();}
}

导航

现在,当 Web View 可以导航到应用页面并加载静态资源时,我们可以加载索引页并教导 WebViewManager 如何执行导航操作。

BlazorBrowserView.axaml.cs

public partial class BlazorBrowserView : UserControl
{private IEngine engine;private IBrowser browser;private BrowserManager browserManager;...public async Task Initialize(){...engine = await EngineFactory.CreateAsync(engineOptions);browser = engine.CreateBrowser();browserManager = new BrowserManager(browser, ...);foreach (RootComponent rootComponent in RootComponents){await rootComponent.AddToWebViewManagerAsync(browserManager);}browserManager.Navigate("/");...}...
}

BrowserManager.cs

internal class BrowserManager : WebViewManager
{...private IBrowser Browser { get; }...protected override void NavigateCore(Uri absoluteUri){Browser.Navigation.LoadUrl(absoluteUri.AbsoluteUri);}
}

数据交换

与普通的 Web 应用程序不同,Blazor Hybrid 不使用 HTTP 进行数据交换。前端和后端通过字符串消息进行通信,使用的是特殊的 .NET-JavaScript 互操作机制。在 JavaScript 中,消息通过 window.external 对象发送和接收,而在 .NET 端,则通过 WebViewManager 进行。

我们使用 DotNetBrowser 的 .NET-JavaScript 桥接功能来创建 window.external 对象并传输消息。

BrowserManager.cs

internal class BrowserManager : WebViewManager
{...private IBrowser Browser { get; }private IJsFunction sendMessageToFrontEnd;public BrowserManager(IBrowser browser, IServiceProvider provider,Dispatcher dispatcher,IFileProvider fileProvider,JSComponentConfigurationStore jsComponents,string hostPageRelativePath): base(provider, dispatcher, AppOriginUri, fileProvider, jsComponents,hostPageRelativePath){Browser = browser;// 此处理程序在页面加载之后但在执行其自己的 JavaScript 之前调用。Browser.InjectJsHandler = new Handler<InjectJsParameters>(OnInjectJs);}...private void OnInjectJs(InjectJsParameters p){if (!p.Frame.IsMain){return;}dynamic window = p.Frame.ExecuteJavaScript("window").Result;window.external = p.Frame.ParseJsonString("{}");// 当页面调用这些方法时,DotNetBrowser 会将调用代理到 .NET 方法。window.external.sendMessage = (Action<dynamic>)OnMessageReceived;window.external.receiveMessage = (Action<dynamic>)SetupCallback;}private void OnMessageReceived(dynamic obj){this.MessageReceived(new Uri(Browser.Url), obj.ToString());}private void SetupCallback(dynamic callbackFunction){sendMessageToFrontEnd = callbackFunction as IJsFunction;}protected override void SendMessage(string message){sendMessageToFrontEnd?.Invoke(null, message);}
}

结论

在本文中,我们讨论了 Blazor Hybrid,这是一种用于使用 Blazor 构建桌面应用程序的 .NET 技术。

Blazor Hybrid 使用 .NET MAUI 存在两个局限性:

  • 不支持 Linux。
  • 在 Windows 和 macOS 上使用不同的 Browser Engine,使得相同的应用程序在不同平台上可能表现和外观不同。

我们建议使用 Avalonia UI + DotNetBrowser 作为替代方案。这种组合为 Windows、macOS 和 Linux 提供了全面支持,并确保在所有平台上都能保持一致的 Browser 环境。

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

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

相关文章

深度学习笔记9-实现逻辑回归

Python实现逻辑回归 1.假设函数 import math #sigmoid函数得计算 def sigmoid(z):return 1.0/(1math.exp(-z)) #逻辑回归假设函数的计算 #函数传入参数theta、样本特征向量x和特征值得个数n def hypothesis(theta,x,n):h0.0#保存预测结果for i in range(0,n1):#将theta-i和x…

数据库->事务

目录 一、事务 1.什么是事务 2.事务的ACID特性 1.Atomicity (原⼦性) 2.Consistency (⼀致性) 3.Isolation (隔离性) 4.Durability (持久性) 3.为什么要使用事务 4. 如何使⽤事务 1.查看MySQL中支持事务的存储引擎 2.⾃动/⼿动提交事务 2.1自动提交事务 2.2手动提交…

QCon演讲实录|徐广治:边缘云原生操作系统的设计与思考

10月18日&#xff0c;在 QCon 全球软件开发大会 2024&#xff08;上海站&#xff09;&#xff0c;火山引擎边缘云资深架构师徐广治围绕火山引擎边缘计算产品背后的算力底座 - 边缘云原生操作系统&#xff0c;探讨如何实现算力服务的混合部署和跨区域弹性调度&#xff0c;以及在…

高效作业之Mybatis缓存

高效作业之Mybatis缓存 引言1. MyBatis的一级缓存1.1. 代码示例一级缓存1.2. 使一级缓存失效的四种情况 2. Mybatis二级缓存2.1. 代码示例二级缓存2.2 使二级缓存失效的情况2.4. 二级缓存配置 3. MyBatis缓存查询的顺序4. 整合第三方缓存EHCache4.1. 添加依赖4.2. 创建EHCache的…

论文阅读笔记-Covariate Shift: A Review and Analysis on Classifiers

前言 标题&#xff1a;Covariate Shift: A Review and Analysis on Classifiers 原文链接&#xff1a;Link\ 我们都知道在机器学习模型中&#xff0c;训练数据和测试数据是不同的阶段&#xff0c;并且&#xff0c;通常是是假定训练数据和测试数据点遵循相同的分布。但是实际上&…

[含文档+PPT+源码等]精品基于PHP实现的会员综合管理平台的设计与实现

基于PHP实现的会员商城平台的设计与实现背景&#xff0c;可以从以下几个方面进行详细阐述&#xff1a; 一、电子商务的兴起与发展 随着Internet的广泛普及&#xff0c;电子商务迅速崛起并成为一种主流的购物趋势。通过网络&#xff0c;消费者可以足不出户地浏览和购买各种各样…

微博舆情分析:使用Python进行深度解析

目录 一、准备工作 二、基础理论知识 三、步骤详解 数据预处理 情感分析 关键词提取 四、案例分享 数据爬取 数据分析 五、优化 六、结论 在当今信息爆炸的时代&#xff0c;社交媒体平台如微博已成为公众表达意见和情感的重要渠道。微博舆情分析通过对大量微博数据进…

GPT原理;ChatGPT 等类似的问答系统工作流程如下;当用户向 ChatGPT 输入一个问题后:举例说明;ChatGPT不是通过索引搜索的传统知识库

目录 GPT原理 GPT架构 GPT 主要基于 Transformer 的解码器部分 ChatGPT 等类似的问答系统工作流程如下: 用户输入 文本预处理 模型处理 答案生成 输出回答 当用户向 ChatGPT 输入一个问题后:举例说明 文本预处理: ChatGPT不是通过索引搜索的传统知识库 GPT GPT…

【C++】C++内存管理(一):new/delete

大家好&#xff0c;我是苏貝&#xff0c;本篇博客带大家了解C的内存管理&#xff0c;如果你觉得我写的还不错的话&#xff0c;可以给我一个赞&#x1f44d;吗&#xff0c;感谢❤️ 目录 1.C/C内存分布2. C语言中动态内存管理方式&#xff1a;malloc/calloc/realloc/free3. C内…

选择适合你的报表工具,山海鲸报表与Tableau深度对比

在数据分析和报表制作的领域&#xff0c;企业往往面临着选择合适工具的难题。尤其是当市场上有很多功能强大的工具时&#xff0c;如何从中挑选出最适合自己需求的报表软件成为了一个关键问题。今天&#xff0c;我们将对比两款报表工具——山海鲸报表和Tableau&#xff0c;看看它…

网站架构知识之Ansible(day020)

1.Ansible架构 Inventory 主机清单:被管理主机的ip列表,分类 ad-hoc模式: 命令行批量管理(使用ans模块),临时任务 playbook 剧本模式: 类似于把操作写出脚本,可以重复运行这个脚本 2.修改配置 配置文件&#xff1a;/etc/ansible/ansible.cfg 修改配置文件关闭主机Host_key…

`psdparse`:解锁Photoshop PSD文件的Python密钥

文章目录 psdparse&#xff1a;解锁Photoshop PSD文件的Python密钥背景&#xff1a;为何选择psdparse&#xff1f;psdparse是什么&#xff1f;如何安装psdparse&#xff1f;简单函数使用方法应用场景常见Bug及解决方案总结 psdparse&#xff1a;解锁Photoshop PSD文件的Python密…

淘宝反爬虫机制的主要手段有哪些?

淘宝的反爬虫机制主要有以下手段&#xff1a; 一、用户身份识别与验证&#xff1a; User-Agent 识别&#xff1a;通过检测 HTTP 请求头中的 User-Agent 字段来判断请求是否来自合法的浏览器。正常用户使用不同浏览器访问时&#xff0c;User-Agent 会有所不同&#xff0c;而爬虫…

使用ssh-key免密登录服务器或免密连接git代码仓库网站

ssh登录服务器场景 假设有两台机器&#xff0c;分别是&#xff1a; 源机器&#xff1a;主机A&#xff08;hostA&#xff09;&#xff0c;ip&#xff1a;198.168.0.1 目标机器&#xff1a;主机B&#xff08;hostB&#xff09;&#xff0c;ip&#xff1a;192.168.0.2 ssh-key免…

swoole扩展安装--入门篇

对于php来说&#xff0c;swoole是个强大的补充扩展。这是我第3次写swoole扩展安装&#xff0c;这次基于opencloudos8系统&#xff0c;php使用8.2。 安装swoole扩展首先想到的是用宝塔来安装&#xff0c;毕竟安装方便&#xff0c;还能统一管理。虽然获得swoole版本不是最新的&am…

神经网络基础--什么是神经网络?? 常用激活函数是什么???

前言 本专栏更新神经网络的一些基础知识&#xff1b;案例代码基于pytorch&#xff1b;欢迎收藏 关注&#xff0c; 本人将会持续更新。 神经网络 1、什么是神经网络 人工神经网络&#xff08; Artificial Neural Network&#xff0c; 简写为ANN&#xff09;也简称为神经网络…

Pycharm,2024最新专业版下载安装配置详细教程!

先来一段官方介绍&#xff0c;PyCharm是一种PythonIDE&#xff0c;带有一整套可以帮助用户在使用Python语言开发时提高其效率的工具&#xff0c;比如调试、语法高亮、Project管理、代码跳转、智能提示、自动完成、单元测试、版本控制。此外&#xff0c;该IDE提供了一些高级功能…

Edge浏览器打开PDF无法显示电子签章

Edge浏览器打开PDF无法显示电子签章 直接说处理方式 直接说处理方式 浏览器地址栏&#xff0c;输入 edge://flags/搜索&#xff1a;pdf禁用&#xff1a;New PDF Viewer效果如下

论 ONLYOFFICE:开源办公套件的深度探索

公主请阅 引言第一部分&#xff1a;ONLYOFFICE 的历史背景1.1 开源软件的崛起1.2 ONLYOFFICE 的发展历程 第二部分&#xff1a;ONLYOFFICE 的核心功能2.1 文档处理2.2 电子表格2.3 演示文稿 第三部分&#xff1a;技术架构与兼容性3.1 技术架构3.2 兼容性 第四部分&#xff1a;部…

算法——双指针

目录 前言一、什么是双指针二、算法特点三、算法实现步骤四、常见形式五、应用场景与示例六、优势与注意事项七、双指针算法动态图解八、经典例题[1. 回文判定](https://www.lanqiao.cn/problems/1371/learning/?page1&first_category_id1&name%E5%9B%9E%E6%96%87%E5%…