翻譯|使用教程|編輯:吉煒煒|2024-11-27 11:55:34.443|閱讀 154 次
概述:在本文中,我們將探討如何使用 Avalonia UI 和 DotNetBrowser 作為 Web View 來創(chuàng)建 Blazor 混合應(yīng)用程序。
# 界面/圖表報表/文檔/IDE等千款熱門軟控件火熱銷售中 >>
DotNetBrowser是一個.NET庫,允許將基于Chromium的WPF和WinForms組件嵌入到.NET應(yīng)用程序中,以顯示使用HTML5,CSS3,JavaScript,Silverlight等構(gòu)建的現(xiàn)代網(wǎng)頁。
Blazor 是一個 .NET 前端框架,用于僅使用 .NET 技術(shù)構(gòu)建 Web 應(yīng)用程序。2021 年,Blazor 擴展到桌面端,推出了 Blazor Hybrid(混合),使開發(fā)者可以在桌面平臺上使用已有的技能。
Blazor 混合應(yīng)用程序是傳統(tǒng)的桌面應(yīng)用程序,它們在一個 Web View 控件中托管實際的 Blazor Web 應(yīng)用程序。雖然這些應(yīng)用程序使用 .NET MAUI 作為桌面端技術(shù),但如果不符合需求,也可以使用其他框架。
MAUI 的局限性在于它缺乏對 Linux 的支持,并且在 Windows 和 macOS 上使用不同的 Browser Engine。Microsoft Edge 和 Safari 在實現(xiàn) Web 標準、執(zhí)行 JavaScript 以及頁面渲染方面存在差異。這些差異在高級應(yīng)用程序中可能會導(dǎo)致 bug 并需要額外的測試。
如果 MAUI 不符合您的要求,可以考慮選擇 Avalonia UI,它是一個跨平臺的 UI 庫,其生態(tài)系統(tǒng)中包含多個基于 Chromium 的 Web View。
在本文中,我們將探討如何使用 Avalonia UI 和 DotNetBrowser 作為 Web View 來創(chuàng)建 Blazor 混合應(yīng)用程序。
使用模板快速入門
要使用 DotNetBrowser 和 Avalonia UI 創(chuàng)建一個基本的 Blazor 混合應(yīng)用程序,請使用我們的模板:
dotnet new install DotNetBrowser.Templates
從模板創(chuàng)建一個 Blazor 混合應(yīng)用程序,并將您的許可證密鑰作為參數(shù)傳遞:
dotnet new dotnetbrowser.blazor.avalonia.app -o Blazor.AvaloniaUi -li <your_license_key>
然后運行應(yīng)用程序:
dotnet run --project Blazor.AvaloniaUi
在 Linux 上的 Avalonia UI 上運行 Blazor 混合應(yīng)用程序。
實現(xiàn)
在混合環(huán)境中,Blazor 應(yīng)用程序在其桌面殼程序的進程中運行。這個殼程序或窗口管理整個應(yīng)用程序的生命周期,顯示 Web View,并啟動 Blazor 應(yīng)用程序。我們將使用 Avalonia UI 創(chuàng)建這個窗口。
Blazor 應(yīng)用程序的后端是 .NET 代碼,前端是托管在 Web View 中的 Web 內(nèi)容。 Web View 中的 Browser Engine 和 .NET 運行時之間沒有直接連接。因此,為了前后端通信,Blazor 必須知道如何在它們之間交換數(shù)據(jù)。由于我們引入了一個新的 Web View,我們必須教會 Blazor 如何使用 DotNetBrowser 進行數(shù)據(jù)交換。
接下來,我們將帶您了解 Blazor 與 Avalonia 和 DotNetBrowser 集成的關(guān)鍵部分。有關(guān)完整解決方案,請查看上面的模板。
創(chuàng)建窗口
為了托管 Blazor 混合應(yīng)用程序,我們需要創(chuàng)建一個常規(guī)的 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 是我們?yōu)榱朔庋b DotNetBrowser 而創(chuàng)建的一個 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
在混合應(yīng)用程序中,負責 Blazor 與環(huán)境集成的主要實體是 WebViewManager。這是一個抽象類,因此我們需要創(chuàng)建自己的實現(xiàn),這里我們稱之為 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 應(yīng)用程序需要一個或多個根組件。當 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>
加載靜態(tài)資源
在普通的 Web 應(yīng)用程序中,Browser 通過向服務(wù)器發(fā)送 HTTP 請求來加載頁面和靜態(tài)資源。在 Blazor 混合應(yīng)用程序中,雖然原理相似,但這里并沒有傳統(tǒng)的服務(wù)器。相反,WebViewManager 提供了一個名為 TryGetResponseContent 的方法,該方法接受一個 URL 并返回數(shù)據(jù)作為類似 HTTP 的響應(yīng)。
我們通過攔截 DotNetBrowser 中的 HTTPS 流量將 HTTP 請求和響應(yīng)傳遞到此方法并返回。
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(); } }
導(dǎo)航
現(xiàn)在,當 Web View 可以導(dǎo)航到應(yīng)用頁面并加載靜態(tài)資源時,我們可以加載索引頁并教導(dǎo) WebViewManager 如何執(zhí)行導(dǎo)航操作。
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); } }
數(shù)據(jù)交換
與普通的 Web 應(yīng)用程序不同,Blazor Hybrid 不使用 HTTP 進行數(shù)據(jù)交換。前端和后端通過字符串消息進行通信,使用的是特殊的 .NET-JavaScript 互操作機制。在 JavaScript 中,消息通過 window.external 對象發(fā)送和接收,而在 .NET 端,則通過 WebViewManager 進行。
我們使用 DotNetBrowser 的 .NET-JavaScript 橋接功能來創(chuàng)建 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; // 此處理程序在頁面加載之后但在執(zhí)行其自己的 JavaScript 之前調(diào)用。 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("{}"); // 當頁面調(diào)用這些方法時,DotNetBrowser 會將調(diào)用代理到 .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); } }
結(jié)論
在本文中,我們討論了 Blazor Hybrid,這是一種用于使用 Blazor 構(gòu)建桌面應(yīng)用程序的 .NET 技術(shù)。
Blazor Hybrid 使用 .NET MAUI 存在兩個局限性:
我們建議使用 Avalonia UI + DotNetBrowser 作為替代方案。這種組合為 Windows、macOS 和 Linux 提供了全面支持,并確保在所有平臺上都能保持一致的 Browser 環(huán)境。
本站文章除注明轉(zhuǎn)載外,均為本站原創(chuàng)或翻譯。歡迎任何形式的轉(zhuǎn)載,但請務(wù)必注明出處、不得修改原文相關(guān)鏈接,如果存在內(nèi)容上的異議請郵件反饋至chenjj@fc6vip.cn
文章轉(zhuǎn)載自:慧都網(wǎng)