|
2 | 2 | title: HybridWebView
|
3 | 3 | description: Learn how to use a HybridWebView to host HTML/JS/CSS content in a WebView, and communicate between that content and .NET.
|
4 | 4 | ms.topic: concept-article
|
5 |
| -ms.date: 05/13/2025 |
| 5 | +ms.date: 08/20/2025 |
6 | 6 | monikerRange: ">=net-maui-9.0"
|
7 | 7 |
|
8 | 8 | #customer intent: As a developer, I want to host HTML/JS/CSS content in a web view so that I can publish the web app as a mobile app.
|
@@ -46,6 +46,7 @@ To create a .NET MAUI app with a <xref:Microsoft.Maui.Controls.HybridWebView>:
|
46 | 46 | A simple app might have the following files and contents:
|
47 | 47 |
|
48 | 48 | - *Resources\Raw\wwwroot\index.html* with content for the main UI:
|
| 49 | + ::: moniker range="<=net-maui-9.0" |
49 | 50 |
|
50 | 51 | ```html
|
51 | 52 | <!DOCTYPE html>
|
@@ -173,6 +174,137 @@ To create a .NET MAUI app with a <xref:Microsoft.Maui.Controls.HybridWebView>:
|
173 | 174 | </html>
|
174 | 175 | ```
|
175 | 176 |
|
| 177 | + ::: moniker-end |
| 178 | + ::: moniker range=">=net-maui-10.0" |
| 179 | + |
| 180 | + ```html |
| 181 | + <!DOCTYPE html> |
| 182 | + |
| 183 | + <html lang="en" xmlns="http://www.w3.org/1999/xhtml"> |
| 184 | + <head> |
| 185 | + <meta charset="utf-8" /> |
| 186 | + <title></title> |
| 187 | + <link rel="icon" href="data:,"> |
| 188 | + <link rel="stylesheet" href="styles/app.css"> |
| 189 | + <script src="_framework/hybridwebview.js"></script> |
| 190 | + <script> |
| 191 | + function LogMessage(msg) { |
| 192 | + var messageLog = document.getElementById("messageLog"); |
| 193 | + messageLog.value += '\r\n' + msg; |
| 194 | + } |
| 195 | +
|
| 196 | + window.addEventListener( |
| 197 | + "HybridWebViewMessageReceived", |
| 198 | + function (e) { |
| 199 | + LogMessage("Raw message: " + e.detail.message); |
| 200 | + }); |
| 201 | +
|
| 202 | + function AddNumbers(a, b) { |
| 203 | + var result = { |
| 204 | + "result": a + b, |
| 205 | + "operationName": "Addition" |
| 206 | + }; |
| 207 | + return result; |
| 208 | + } |
| 209 | +
|
| 210 | + var count = 0; |
| 211 | +
|
| 212 | + async function EvaluateMeWithParamsAndAsyncReturn(s1, s2) { |
| 213 | + const response = await fetch("/asyncdata.txt"); |
| 214 | + if (!response.ok) { |
| 215 | + throw new Error(`HTTP error: ${response.status}`); |
| 216 | + } |
| 217 | + var jsonData = await response.json(); |
| 218 | +
|
| 219 | + jsonData[s1] = s2; |
| 220 | +
|
| 221 | + const msg = 'JSON data is available: ' + JSON.stringify(jsonData); |
| 222 | + window.HybridWebView.SendRawMessage(msg) |
| 223 | +
|
| 224 | + return jsonData; |
| 225 | + } |
| 226 | +
|
| 227 | + async function InvokeDoSyncWork() { |
| 228 | + LogMessage("Invoking DoSyncWork"); |
| 229 | + await window.HybridWebView.InvokeDotNet('DoSyncWork'); |
| 230 | + LogMessage("Invoked DoSyncWork"); |
| 231 | + } |
| 232 | +
|
| 233 | + async function InvokeDoSyncWorkParams() { |
| 234 | + LogMessage("Invoking DoSyncWorkParams"); |
| 235 | + await window.HybridWebView.InvokeDotNet('DoSyncWorkParams', [123, 'hello']); |
| 236 | + LogMessage("Invoked DoSyncWorkParams"); |
| 237 | + } |
| 238 | +
|
| 239 | + async function InvokeDoSyncWorkReturn() { |
| 240 | + LogMessage("Invoking DoSyncWorkReturn"); |
| 241 | + const retValue = await window.HybridWebView.InvokeDotNet('DoSyncWorkReturn'); |
| 242 | + LogMessage("Invoked DoSyncWorkReturn, return value: " + retValue); |
| 243 | + } |
| 244 | +
|
| 245 | + async function InvokeDoSyncWorkParamsReturn() { |
| 246 | + LogMessage("Invoking DoSyncWorkParamsReturn"); |
| 247 | + const retValue = await window.HybridWebView.InvokeDotNet('DoSyncWorkParamsReturn', [123, 'hello']); |
| 248 | + LogMessage("Invoked DoSyncWorkParamsReturn, return value: message=" + retValue.Message + ", value=" + retValue.Value); |
| 249 | + } |
| 250 | +
|
| 251 | + async function InvokeDoAsyncWork() { |
| 252 | + LogMessage("Invoking DoAsyncWork"); |
| 253 | + await window.HybridWebView.InvokeDotNet('DoAsyncWork'); |
| 254 | + LogMessage("Invoked DoAsyncWork"); |
| 255 | + } |
| 256 | +
|
| 257 | + async function InvokeDoAsyncWorkParams() { |
| 258 | + LogMessage("Invoking DoAsyncWorkParams"); |
| 259 | + await window.HybridWebView.InvokeDotNet('DoAsyncWorkParams', [123, 'hello']); |
| 260 | + LogMessage("Invoked DoAsyncWorkParams"); |
| 261 | + } |
| 262 | +
|
| 263 | + async function InvokeDoAsyncWorkReturn() { |
| 264 | + LogMessage("Invoking DoAsyncWorkReturn"); |
| 265 | + const retValue = await window.HybridWebView.InvokeDotNet('DoAsyncWorkReturn'); |
| 266 | + LogMessage("Invoked DoAsyncWorkReturn, return value: " + retValue); |
| 267 | + } |
| 268 | +
|
| 269 | + async function InvokeDoAsyncWorkParamsReturn() { |
| 270 | + LogMessage("Invoking DoAsyncWorkParamsReturn"); |
| 271 | + const retValue = await window.HybridWebView.InvokeDotNet('DoAsyncWorkParamsReturn', [123, 'hello']); |
| 272 | + LogMessage("Invoked DoAsyncWorkParamsReturn, return value: message=" + retValue.Message + ", value=" + retValue.Value); |
| 273 | + } |
| 274 | +
|
| 275 | + </script> |
| 276 | + </head> |
| 277 | + <body> |
| 278 | + <div> |
| 279 | + Hybrid sample! |
| 280 | + </div> |
| 281 | + <div> |
| 282 | + <button onclick="window.HybridWebView.SendRawMessage('Message from JS! ' + (count++))">Send message to C#</button> |
| 283 | + </div> |
| 284 | + <div> |
| 285 | + <button onclick="InvokeDoSyncWork()">Call C# sync method (no params)</button> |
| 286 | + <button onclick="InvokeDoSyncWorkParams()">Call C# sync method (params)</button> |
| 287 | + <button onclick="InvokeDoSyncWorkReturn()">Call C# method (no params) and get simple return value</button> |
| 288 | + <button onclick="InvokeDoSyncWorkParamsReturn()">Call C# method (params) and get complex return value</button> |
| 289 | + </div> |
| 290 | + <div> |
| 291 | + <button onclick="InvokeDoAsyncWork()">Call C# async method (no params)</button> |
| 292 | + <button onclick="InvokeDoAsyncWorkParams()">Call C# async method (params)</button> |
| 293 | + <button onclick="InvokeDoAsyncWorkReturn()">Call C# async method (no params) and get simple return value</button> |
| 294 | + <button onclick="InvokeDoAsyncWorkParamsReturn()">Call C# async method (params) and get complex return value</button> |
| 295 | + </div> |
| 296 | + <div> |
| 297 | + Log: <textarea readonly id="messageLog" style="width: 80%; height: 10em;"></textarea> |
| 298 | + </div> |
| 299 | + <div> |
| 300 | + Consider checking out this PDF: <a href="docs/sample.pdf">sample.pdf</a> |
| 301 | + </div> |
| 302 | + </body> |
| 303 | + </html> |
| 304 | + ``` |
| 305 | + |
| 306 | + ::: moniker-end |
| 307 | + ::: moniker range="<=net-maui-9.0" |
176 | 308 | - *Resources\Raw\wwwroot\scripts\HybridWebView.js* with the standard <xref:Microsoft.Maui.Controls.HybridWebView> JavaScript library:
|
177 | 309 |
|
178 | 310 | ```js
|
@@ -322,6 +454,7 @@ To create a .NET MAUI app with a <xref:Microsoft.Maui.Controls.HybridWebView>:
|
322 | 454 | window.HybridWebView.Init();
|
323 | 455 | ```
|
324 | 456 |
|
| 457 | + ::: moniker-end |
325 | 458 | Then, add any additional web content to your project.
|
326 | 459 |
|
327 | 460 | > [!WARNING]
|
@@ -681,6 +814,100 @@ The `window.HybridWebView.InvokeDotNet` JavaScript function invokes a specified
|
681 | 814 |
|
682 | 815 | ::: moniker range=">=net-maui-10.0"
|
683 | 816 |
|
| 817 | +## Customize initialization and access platform web views |
| 818 | + |
| 819 | +While <xref:Microsoft.Maui.Controls.HybridWebView> doesn’t expose app-facing initializing/initialized events like <xref:Microsoft.AspNetCore.Components.WebView.Maui.BlazorWebView>, you can still customize the underlying platform web views and run code after they’re ready: |
| 820 | + |
| 821 | +- Windows (WebView2): the platform view is <xref:Microsoft.Maui.Controls.HybridWebView>, which inherits `WebView2` and adds `RunAfterInitialize(Action)` so you can safely access `CoreWebView2` once it’s ready. |
| 822 | +- Android (android.webkit.WebView): access and configure the platform `WebView` via the handler once it’s created. |
| 823 | +- iOS/Mac Catalyst (WKWebView): access and configure the platform `WKWebView` after creation. Some options (such as certain `WKWebViewConfiguration` settings) must be set at creation time; .NET MAUI sets sensible defaults for these. |
| 824 | + |
| 825 | +### Access the platform view after handler creation |
| 826 | + |
| 827 | +Handle `HandlerChanged` (or override `OnHandlerChanged` in a custom control) and branch by platform: |
| 828 | + |
| 829 | +```csharp |
| 830 | +using Microsoft.Maui.Platform; // For MauiHybridWebView on Windows |
| 831 | +
|
| 832 | +void HybridWebView_HandlerChanged(object? sender, EventArgs e) |
| 833 | +{ |
| 834 | + if (sender is not HybridWebView hv || hv.Handler?.PlatformView is null) |
| 835 | + return; |
| 836 | + |
| 837 | +#if WINDOWS |
| 838 | + if (hv.Handler.PlatformView is MauiHybridWebView winView) |
| 839 | + { |
| 840 | + winView.RunAfterInitialize(() => |
| 841 | + { |
| 842 | + // CoreWebView2 is guaranteed to be initialized here |
| 843 | + winView.CoreWebView2.Settings.IsZoomControlEnabled = false; |
| 844 | + winView.CoreWebView2.Settings.AreDefaultContextMenusEnabled = false; |
| 845 | + }); |
| 846 | + } |
| 847 | +#elif ANDROID |
| 848 | + if (hv.Handler.PlatformView is Android.Webkit.WebView androidView) |
| 849 | + { |
| 850 | + // Safe to tweak most settings after creation |
| 851 | + androidView.Settings.BuiltInZoomControls = false; |
| 852 | + androidView.Settings.DisplayZoomControls = false; |
| 853 | + } |
| 854 | +#elif IOS || MACCATALYST |
| 855 | + if (hv.Handler.PlatformView is WebKit.WKWebView wk) |
| 856 | + { |
| 857 | + wk.AllowsBackForwardNavigationGestures = true; |
| 858 | + // Many WKWebViewConfiguration options can’t be changed now – see note below |
| 859 | + } |
| 860 | +#endif |
| 861 | +} |
| 862 | +``` |
| 863 | + |
| 864 | +Wire this up once, for example in XAML code-behind: |
| 865 | + |
| 866 | +```csharp |
| 867 | +public MainPage() |
| 868 | +{ |
| 869 | + InitializeComponent(); |
| 870 | + hybridWebView.HandlerChanged += HybridWebView_HandlerChanged; |
| 871 | +} |
| 872 | +``` |
| 873 | + |
| 874 | +> [!IMPORTANT] |
| 875 | +> On iOS/Mac Catalyst, some `WKWebViewConfiguration` options must be set before the view is created. .NET MAUI enables common options by default (inline media playback, autoplay, JavaScript, etc.) so typical scenarios work without extra code. If you need different creation-time options, use the advanced approach below. |
| 876 | +
|
| 877 | +### Advanced: provide creation-time configuration with a custom handler |
| 878 | + |
| 879 | +If you need to alter creation-time options (for example, to change `WKWebViewConfiguration` on iOS/Mac Catalyst), register a custom handler and override `CreatePlatformView`: |
| 880 | + |
| 881 | +```csharp |
| 882 | +// In MauiProgram.cs |
| 883 | +builder.ConfigureMauiHandlers(handlers => |
| 884 | +{ |
| 885 | + handlers.AddHandler<HybridWebView, MyHybridWebViewHandler>(); |
| 886 | +}); |
| 887 | + |
| 888 | +// Custom handler (iOS/Mac Catalyst shown; similar ideas apply for other platforms) |
| 889 | +public class MyHybridWebViewHandler : HybridWebViewHandler |
| 890 | +{ |
| 891 | +#if IOS || MACCATALYST |
| 892 | + protected override WebKit.WKWebView CreatePlatformView() |
| 893 | + { |
| 894 | + var config = new WebKit.WKWebViewConfiguration |
| 895 | + { |
| 896 | + // Example: change defaults established by MAUI |
| 897 | + AllowsInlineMediaPlayback = false, |
| 898 | + }; |
| 899 | + |
| 900 | + // Recreate the platform view with your configuration |
| 901 | + var webview = new MauiHybridWebView(this, CoreGraphics.CGRect.Empty, config); |
| 902 | + return webview; |
| 903 | + } |
| 904 | +#endif |
| 905 | +} |
| 906 | +``` |
| 907 | + |
| 908 | +> [!CAUTION] |
| 909 | +> Creation-time configuration is an advanced scenario. Validate behavior on each platform, and prefer post-initialization tweaks when possible. |
| 910 | +
|
684 | 911 | ## Intercept web requests
|
685 | 912 |
|
686 | 913 | <xref:Microsoft.Maui.Controls.HybridWebView> can intercept and respond to web requests that originate from within the hosted web content. This enables scenarios such as modifying headers, redirecting requests, or supplying local responses.
|
|
0 commit comments