翻譯|使用教程|編輯:吉煒煒|2024-11-08 11:48:28.480|閱讀 212 次
概述:在本文中,我們將探討如何使用 Avalonia UI 和 DotNetBrowser 作為 Web View 來創建 Blazor 混合應用程序。
# 界面/圖表報表/文檔/IDE等千款熱門軟控件火熱銷售中 >>
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
從模板創建一個 Blazor 混合應用程序,并將您的許可證密鑰作為參數傳遞:
dotnet new dotnetbrowser.blazor.avalonia.app -o Blazor.AvaloniaUi -li <your_license_key>
然后運行應用程序:
dotnet run --project Blazor.AvaloniaUi
實現
在混合環境中,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 = $"http://{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 = $"http://{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 存在兩個局限性:
我們建議使用 Avalonia UI + DotNetBrowser 作為替代方案。這種組合為 Windows、macOS 和 Linux 提供了全面支持,并確保在所有平臺上都能保持一致的 Browser 環境。
本站文章除注明轉載外,均為本站原創或翻譯。歡迎任何形式的轉載,但請務必注明出處、不得修改原文相關鏈接,如果存在內容上的異議請郵件反饋至chenjj@fc6vip.cn
文章轉載自:慧都網