利用 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.MainFrame
                                           or ResourceType.Favicon
                                           or 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 环境。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

MFC中Excel的导入以及使用步骤

参考地址 在需要对EXCEL表进行操作的类中添加以下头文件&#xff1a;若出现大量错误将其放入stdafx.h中 #include "resource.h" // 主符号 #include "CWorkbook.h" //单个工作簿 #include "CRange.h" //区域类&#xff0c;对Excel大…

【深入浅出】Linux进程(三)

&#x1f4c3;博客主页&#xff1a; 小镇敲码人 &#x1f49a;代码仓库&#xff0c;欢迎访问 &#x1f680; 欢迎关注&#xff1a;&#x1f44d;点赞 &#x1f442;&#x1f3fd;留言 &#x1f60d;收藏 &#x1f30f; 任尔江湖满血骨&#xff0c;我自踏雪寻梅香。 万千浮云遮碧…

2024 第五次周赛

A: 直接遍历即可 #include<bits/stdc.h> using namespace std;typedef long long ll; typedef pair<ll, ll>PII; const int N 2e6 10; const int MOD 998244353; const int INF 0X3F3F3F3F;int n, m; int main() {cin >> n;int cnt 0;for(int i 0; i …

gitlab无法创建合并请求是所有分支都不显示

点击Merge Requests ------> New merge request 创建新的合并请求时&#xff0c;在Source branch和Target branch中一个分支都不显示 排查思路&#xff1a; 1.怀疑是权限问题。 发现只有我的一个账号出现&#xff0c;检查了账号的权限&#xff0c;尝试了master、develop角色…

Linux中给普通账户一次性提权

我在以前文章中Linux常见指令大全&#xff08;必要知识点&#xff09;-CSDN博客 写过sudo的概念与用法。其实本质就是提权用的但是在某些场景下就算提权了也不能使用。 例如&#xff1a;打开主工作目录 他不相信你这个用户&#xff0c;虽然你是erman 解决方法 使用root账号打开…

【C++】—掌握STL string类:string的模拟实现

文章目录 &#x1f49e;1.浅拷贝&#x1f49e;2.深拷贝&#x1f49e;3. string类的模拟实现&#x1f49e;3.1 string的构造函数&#x1f49e;3.2 string的析构函数&#x1f49e;3.3 string的拷贝构造&#x1f49e;3.4 string的size&#x1f49e;3.5 string的operator[]&#x1…

详解基于C#开发Windows API的SendMessage方法的鼠标键盘消息发送

在C#中&#xff0c;SendMessage方法是一个强大的工具&#xff0c;它允许我们与Windows API交互&#xff0c;模拟键盘和鼠标事件。本文将详细介绍如何使用SendMessage方法来发送鼠标和键盘消息。 1. SendMessage方法概述 SendMessage是Windows API中的一个函数&#xff0c;它用…

15.UE5等级、经验、血条,魔法恢复和消耗制作

2-17 等级、经验、血条、魔法消耗_哔哩哔哩_bilibili 目录 1.制作UI&#xff0c;等级&#xff0c;经验&#xff0c;血条 ​2.为属性面板绑定角色真实的属性&#xff0c;实现动态更新 3.魔法的消耗和恢复 1.制作UI&#xff0c;等级&#xff0c;经验&#xff0c;血条 创建控…

<项目代码>YOLOv8 玉米地杂草识别<目标检测>

YOLOv8是一种单阶段&#xff08;one-stage&#xff09;检测算法&#xff0c;它将目标检测问题转化为一个回归问题&#xff0c;能够在一次前向传播过程中同时完成目标的分类和定位任务。相较于两阶段检测算法&#xff08;如Faster R-CNN&#xff09;&#xff0c;YOLOv8具有更高的…

现场工程师日记-MSYS2迅速部署PostgreSQL主从备份数据库

文章目录 一、概要二、整体架构流程1. 安装 MSYS2 环境2. 安装postgresql 三、技术名词解释1.MSYS22.postgresql 四、技术细节1. 创建主数据库2.添加从数据库复制权限3. 按需修改参数&#xff08;1&#xff09;WAL保留空间&#xff08;2&#xff09;监听地址 4. 启动主服务器5.…

堆排序与链式二叉树:数据结构与排序算法的双重探索

大家好&#xff0c;我是小卡皮巴拉 文章目录 目录 引言 一.堆排序 1.1 版本一 核心概念 堆排序过程 1.2 版本二 堆排序函数 HeapSort 向下调整算法 AdjustDown 向上调整算法 AdjustUp 二.链式二叉树 2.1 前中后序遍历 链式二叉树的结构 创建链式二叉树 前序遍历…

【LinuxC编程】06 - 守护进程,线程

进程组和会话 概念和特性 进程组&#xff0c;也称之为作业。BSD于1980年前后向Unix中增加的一个新特性。代表一个或多个进程的集合。每个进程都属于一个进程组。在waitpid函数和kill函数的参数中都曾使用到。操作系统设计的进程组的概念&#xff0c;是为了简化对多个进程的管…

【MongoDB】MongoDB的聚合(Aggregate、Map Reduce)与管道(Pipline) 及索引详解(附详细案例)

文章目录 MongoDB的聚合操作&#xff08;Aggregate&#xff09;MongoDB的管道&#xff08;Pipline操作&#xff09;MongoDB的聚合&#xff08;Map Reduce&#xff09;MongoDB的索引 更多相关内容可查看 MongoDB的聚合操作&#xff08;Aggregate&#xff09; 简单理解&#xff…

Python的函数(补充浅拷贝和深拷贝)

一、定义 函数的定义&#xff1a;实现【特定功能】的代码块。 形参&#xff1a;函数定义时的参数&#xff0c;没有实际意义 实参&#xff1a;函数调用/使用时的参数&#xff0c;有实际意义 函数的作用&#xff1a; 简化代码提高代码重用性便于维护和修改提高代码的可扩展性…

Unity常见问题合集(一)

PS&#xff1a;不定期更新...... 目录 &#xff08;1&#xff09;无法关闭自动编译&#xff08;Edit — Preference — General — Auto Refresh&#xff09; &#xff08;1&#xff09;无法关闭自动编译&#xff08;Edit — Preference — General — Auto Refresh&#xff0…

HTB:Sightless[WriteUP]

目录 连接至HTB服务器并启动靶机 使用nmap对靶机TCP端口进行开放扫描 继续使用nmap对靶机开放的TCP端口进行脚本、服务扫描 首先尝试对靶机FTP服务进行匿名登录 使用curl访问靶机80端口 使用浏览器可以直接访问该域名 使用浏览器直接访问该子域 Getshell 横向移动 查…

深度学习-神经网络基础-网络搭建-损失函数-网络优化-正则化方法

一. 神经网络搭建和参数计算 一个继承(nn.model), 两个方法(init, forward) 简介 在pytorch中定义深度神经网络其实就是层堆叠的过程&#xff0c;继承自nn.Module&#xff0c;实现两个方法&#xff1a; init方法中定义网络中的层结构&#xff0c;主要是全连接层&#xff0c;…

全彩LED显示屏有几种安装方式?

全彩LED显示屏的安装方式多样&#xff0c;根据不同的使用场景和安装环境&#xff0c;可以选择最合适的安装方法。以下是一些常见的全彩LED显示屏安装方式&#xff1a; 室内显示屏安装方式 吊装&#xff1a;适用于室内承重混凝土顶&#xff0c;可以使用标准吊件&#xff0c;吊杆…

ZISUOJ 2024算法基础公选课练习二

一、前言 先把代码丢上来&#xff0c;后续再补上思路 二、题目总览 三、具体题目 3.1 问题 A: 成绩排序1 参考代码 简单排序 #include <bits/stdc.h> using i64 long long;int main() {std::cin.tie(nullptr)->sync_with_stdio(false);int t 1;std::cin >&g…

阿里云智能语音交互产品试用,基于语音识别、语音合成、自然语言理解

VER&#xff1a;2024年1月25日 17:29:33 智能语音交互产品基于语音识别、语音合成、自然语言理解 新开通智能语音交互服务用户&#xff0c;可享有3个月免费试用期&#xff0c;试用期间将不会产生费用 智能语音交互产品基于语音识别、语音合成、自然语言理解等技术&#xff0c…