利用 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.mzph.cn/diannao/59550.shtml

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

相关文章

ELK-ELK基本概念_ElasticSearch的配置

文章目录 一、什么是ELK&#xff1f;有什么用&#xff1f;ELK是什么&#xff1f;ElasticsearchLogstashKibana ELK的作用 二、ElasticSearch的安装与基本配置为何需要依赖JDK&#xff1f;使用yum install java-11-openjdk和使用Oracle官网提供的jdk的rpm包安装JDK的区别 参考资…

怎么查看navicat的数据库密码

步骤1:打开navicat连接数据库工具&#xff0c;顶部的文件栏-导出结果-勾选导出密码-导出 步骤2&#xff1a;导出结果使用NotePad或文本打开&#xff0c;找到&#xff0c;数据库对应的的Password"995E66F64A15F6776“”的值复制下来 <Connection ConnectionName"…

linux驱动-i2c子系统框架学习(1)

可以将整个 I2C 子系统用下面的框图来描述&#xff1a; 可以将上面这一 I2C 子系统划分为三个层次&#xff0c;分别为用户空间、内核空间和硬件层&#xff0c;内核空间就包括 I2C 设备驱动层、I2C 核心层和 I2C 适配器驱动层&#xff0c; 本篇主要内容就是介绍 I2C 子系统框架中…

基于SSM的企业管理系统(源码+lw+调试+技术指导)

项目描述 临近学期结束&#xff0c;还是毕业设计&#xff0c;你还在做java程序网络编程&#xff0c;期末作业&#xff0c;老师的作业要求觉得大了吗?不知道毕业设计该怎么办?网页功能的数量是否太多?没有合适的类型或系统?等等。这里根据疫情当下&#xff0c;你想解决的问…

【金融风控】相关业务介绍及代码详解

金融风控相关业务介绍 【了解】项目整体介绍 1.风控业务和风控报表</span> 零售金融产品 相关的指标 风控建模流程 ​ #2.特征工程 特征构造 特征筛选 ​ 3.评分卡模型构建 逻辑回归 集成学习 XGBoost LightGBM 模型评估 ​ #4.样本不均衡问题/异常点检测 【了解】今日…

Late Chunking×Milvus:如何提高RAG准确率

01. 背景 在RAG应用开发中&#xff0c;第一步就是对于文档进行chunking&#xff08;分块&#xff09;&#xff0c;高效的文档分块&#xff0c;可以有效的提高后续的召回内容的准确性。而对于如何高效的分块是个讨论的热点&#xff0c;有诸如固定大小分块&#xff0c;随机大小分…

蓝桥杯备赛(持续更新)

16届蓝桥杯算法类知识图谱.pdf 1. 格式打印 %03d&#xff1a;如果是两位数&#xff0c;将会在前面添上一位0 %.2f&#xff1a;会保留两位小数 如果是long&#xff0c;必须在数字后面加上L。 2. 进制转化 2.1. 十进制转任意进制&#xff1a; 十进制转任意进制时&#xff…

责任链模式 Chain of Responsibility

1 意图 使多个对象都有机会处理请求&#xff0c;从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链&#xff0c;并沿着这条链传递该请求&#xff0c;直到有一个对象处理它为止。 2 结构 Handler 定义一个处理请求的接口;(可选)实现后继链。 ConcreteHandler …

SQL Server 2008 R2 详细安装教程及错误解决教程

SQL Server 2008 R2 详细安装教程及错误解决教程 文章目录 SQL Server 2008 R2 详细安装教程及错误解决教程1.装载或解压ISO文件2. 运行setup程序3. 下载并安装.NET Framework3.54.选择全新安装或向现有安装添加功能5.输入秘钥同意条款6.选择安装类型7.设置角色8.功能选择9.实例…

国际版JAVA同城打车源码同城服务线下结账系统源码适配PAD支持Android+IOS+H5

架构分析 导航栏&#xff1a;位于界面上方&#xff0c;包含了“数据中心”、“消息”、“用户中心”等主要功能模块的入口&#xff0c;方便用户快速访问。左侧功能模块&#xff1a;在界面的左侧&#xff0c;以列表形式展示了多个功能模块&#xff0c;如“数据中心”、“消息中…

营销页面设计:精准触达目标群体的艺术

在当今数字化的商业世界中&#xff0c;营销页面设计扮演着至关重要的角色。成功的营销页面设计不仅仅是美观那么简单&#xff0c;它需要结合场景设计、精准定位目标群体、巧妙运用设计元素以及精心策划的色彩搭配&#xff0c;共同编织出一张引人入胜的视觉网络。 今天就以洋河…

gdb和make工具

gdb工具&#xff1a; GDB的主要功能 断点设置&#xff1a;允许开发者在特定的代码行设置断点&#xff0c;当程序执行到该行时会自动暂停&#xff0c;方便开发者进行调试和分析。 变量查看与修改&#xff1a;在程序运行过程中&#xff0c;可以查看和修改变量的值&#xff0c;以…

爬虫-------字体反爬

目录 一、了解什么是字体加密 二. 定位字体位置 三. python处理字体 1. 工具库 2. 字体读取 3. 处理字体 案例1:起点 案例2:字符偏移: 5请求数据 - 发现偏移量 5.4 多套字体替换 套用模板 版本1 版本2 四.项目实战 1. 采集目标 2. 逆向结果 一、了解什么是…

web实操3——servlet

课程链接b站&#xff1a;第12课 https://www.bilibili.com/video/BV1qv4y1o79t?spm_id_from333.788.videopod.episodes&vd_source05a3c1275b87b47507d869e9349ee3cd&p233 为什么只要写一个实现Servlet的类就可以被调用 tomcat根据url去web.xml里定位到我们写的类后&…

HTML 基础标签——分组标签 <div>、<span> 和基础语义容器

文章目录 1. `<div>` 标签特点用途示例2. `<span>` 标签特点用途示例3. `<fieldset>` 标签特点用途示例4. `<section>` 标签特点用途示例5. `<article>` 标签特点用途示例总结HTML中的分组(容器)标签用于结构化内容,将页面元素组织成逻辑区域…

NPU 可不可以代替 GPU

结论 先说结论&#xff0c;GPU分为可以做图形处理的传统意义上的真GPU&#xff0c;做HPC计算的GPGPU和做AI加速计算的GPGPU&#xff0c;所以下面分别说&#xff1a; 对于做图形处理的GPU&#xff0c;这个就和NPU 一样&#xff0c;属于DSA&#xff0c;没有替代性。当然&#xf…

2024年无线领夹麦克风十大品牌推荐,衣领麦克风哪个品牌好

声音&#xff0c;是沟通的桥梁&#xff0c;是信息的载体。在信息爆炸的时代&#xff0c;如何让自己的声音脱颖而出&#xff0c;成为了每个人都需要思考的问题。无线领夹麦克风&#xff0c;以其小巧便携、无线传输的特点&#xff0c;成为了众多声音爱好者的首选。市场上无线领夹…

杨传辉:云+AI 时代的一体化数据库|OceanBase发布会实录

在 2024 OceanBase 年度发布会 上&#xff0c; OceanBase CTO 杨传辉进行了主题为《云和 AI 时代的一体化数据库战略思考》的演讲&#xff0c;本文为演讲实录&#xff0c;欢迎阅读。 视频观看可点击&#xff1a;https://www.oceanbase.com/video/9001825 各位 OceanBase 的客…

[大模型]视频生成-Sora简析

参考资料&#xff1a; Sora技术报告https://openai.com/index/video-generation-models-as-world-simulators/4分钟详细揭密&#xff01;Sora视频生成模型原理https://www.bilibili.com/video/BV1AW421K7Ut 一、概述 相较于Gen-2、Stable Diffusion、Pika等生成模型的前辈&am…

【docker入门】docker的安装

目录 Centos 7 添加docker 官方仓库到yum源 将 Docker 的官方镜像源替换为国内可以的 Docker 镜像源 安装docker 配置docker加速源 Ubuntu 创建 gpg key 目录 下载 gpg key 添加国内可用镜像源到 系统的 APT 仓库中 安装docker 配置加速源 Centos 7 添加docker 官方仓…