翻譯|使用教程|編輯:吉煒煒|2024-11-27 11:55:34.443|閱讀 154 次
概述:在本文中,我們將探討如何使用 Avalonia UI 和 DotNetBrowser 作為 Web View 來創建 Blazor 混合應用程序。
# 界面/圖表報表/文檔/IDE等千款熱門軟控件火熱銷售中 >>
DotNetBrowser是一個.NET庫,允許將基于Chromium的WPF和WinForms組件嵌入到.NET應用程序中,以顯示使用HTML5,CSS3,JavaScript,Silverlight等構建的現代網頁。
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
在 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 = $"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
文章轉載自:慧都網