HybridWebView in .NET MAUI
Overview
HybridWebView hosts HTML/JS/CSS content inside a .NET MAUI app and provides bidirectional communication between JavaScript and C#. It is not a general browser control—it is designed for local web content shipped with the app.
Project layout
Place web assets under Resources/Raw/wwwroot (the default root). Set a different root with the HybridRootComponent property if needed.
Resources/Raw/wwwroot/ index.html ← entry point (default) scripts/app.js styles/app.css
XAML setup
<HybridWebView x:Name="myHybridWebView" DefaultFile="index.html" RawMessageReceived="OnRawMessageReceived" HorizontalOptions="Fill" VerticalOptions="Fill" />
DefaultFile sets the HTML page loaded on start (defaults to index.html ).
index.html structure
The page must include the bridge script before any app scripts:
<!DOCTYPE html> <html lang="en"> <head><meta charset="utf-8" /></head> <body> <!-- app markup --> <script src="_hwv/HybridWebView.js"></script> <script src="scripts/app.js"></script> </body> </html>
C# → JavaScript (InvokeJavaScriptAsync)
Call a JS function from C# and receive a typed result:
// JS: function addNumbers(a, b) { return a + b; } var result = await myHybridWebView.InvokeJavaScriptAsync<int>( "addNumbers", MyJsonContext.Default.Int32, // return type info [2, 3], // parameters [MyJsonContext.Default.Int32, // param 1 type info MyJsonContext.Default.Int32]); // param 2 type info
For complex types:
var person = await myHybridWebView.InvokeJavaScriptAsync<Person>( "getPerson", MyJsonContext.Default.Person, [id], [MyJsonContext.Default.Int32]);
Every parameter type and the return type must have a JsonTypeInfo
registered in a JsonSerializerContext .
JavaScript → C# (InvokeDotNet)
From JS, call a C# method exposed on the invoke target:
const result = await window.HybridWebView.InvokeDotNet('MethodName', [param1, param2]); window.HybridWebView.InvokeDotNet('LogEvent', ['click', 'button1']); // fire-and-forget
Setting the invoke target
Register the object whose public methods JS can call:
myHybridWebView.SetInvokeJavaScriptTarget(new MyJsBridge());
public class MyJsBridge { public string Greet(string name) => $"Hello, {name}!"; public Person GetPerson(int id) => new Person { Id = id, Name = "Ada" }; }
Method parameters and return values are serialized as JSON.
Raw messages
For unstructured string communication use raw messages instead of typed interop.
C# → JS:
myHybridWebView.SendRawMessage("payload string");
JS → C#:
window.HybridWebView.SendRawMessage('payload string');
Receiving in C#:
void OnRawMessageReceived(object sender, HybridWebViewRawMessageReceivedEventArgs e) { var message = e.Message; }
Receiving in JS:
window.addEventListener('HybridWebViewMessageReceived', e => { const message = e.detail.message; });
JSON serialization setup
Use source-generated JSON serialization. Define a partial context covering every type exchanged between JS and C#:
[JsonSourceGenerationOptions( WriteIndented = false, PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)] [JsonSerializable(typeof(int))] [JsonSerializable(typeof(string))] [JsonSerializable(typeof(Person))] internal partial class MyJsonContext : JsonSerializerContext { }
public class Person { public int Id { get; set; } public string Name { get; set; } = string.Empty; }
Rule: If you add a new type to the interop surface, you must add a [JsonSerializable(typeof(T))] attribute to the context.
JS exception forwarding (.NET 9+)
JavaScript exceptions thrown during InvokeJavaScriptAsync are automatically forwarded to .NET as managed exceptions. Wrap calls in try/catch:
try { var result = await myHybridWebView.InvokeJavaScriptAsync<string>( "riskyFunction", MyJsonContext.Default.String); } catch (Exception ex) { Debug.WriteLine($"JS error: {ex.Message}"); }
Trimming and NativeAOT
Trimming and NativeAOT are disabled by default in MAUI projects. If you enable them, ensure JSON source generators are used:
<PropertyGroup> <PublishTrimmed>true</PublishTrimmed> <JsonSerializerIsReflectionEnabledByDefault>false</JsonSerializerIsReflectionEnabledByDefault> </PropertyGroup>
Using JsonSerializerContext (source generation) as shown above is the recommended pattern regardless of trimming settings.
Quick checklist
-
Web content is under Resources/Raw/wwwroot .
-
index.html includes <script src="_hwv/HybridWebView.js"></script> .
-
DefaultFile is set (or defaults to index.html ).
-
Every interop type has a [JsonSerializable] entry in a JsonSerializerContext .
-
SetInvokeJavaScriptTarget is called before JS invokes C# methods.
-
Wrap InvokeJavaScriptAsync in try/catch to handle JS exceptions (.NET 9+).