Blazor Hybrid lets you run Razor components natively inside a desktop or mobile app instead of in a browser, so a team that has already invested in Blazor web UI can reuse most of it in a native client without a rewrite. It is not WebAssembly running in a sandboxed browser tab: components execute as regular .NET code on the device and render into an embedded web view through a local interop channel, with full access to native APIs behind whatever abstractions you register. This guide covers how BlazorWebView works in .NET MAUI, WPF and Windows Forms, how to structure shared UI with Razor class libraries, the MAUI Blazor Hybrid and Web App template, and where Blazor Hybrid is and is not the right choice compared with a fully native UI.
What Is Blazor Hybrid?#
Blazor Hybrid is one of Blazor's four hosting models, alongside Blazor Server, Blazor WebAssembly and the newer static server rendering used by Blazor Web Apps. In a Blazor Hybrid app, Razor components run in the app's own process, not in a browser sandbox, and render into a BlazorWebView control, an embedded web view that the framework uses purely as a rendering surface. Because components run on the device itself, they have direct access to everything the host platform exposes, file system, sensors, native dialogs, without going through an HTTP API the way a browser-hosted Blazor app would need to. The trade-off is that a Blazor Hybrid app is not a web app: it ships as a real desktop or mobile app binary, one per target platform, and there is no browser tab a user can just navigate to.
Three official hosts exist today: .NET MAUI, where BlazorWebView is a first-class MAUI control (Microsoft.AspNetCore.Components.WebView.Maui.BlazorWebView), and WPF and Windows Forms, which get their own BlazorWebView implementations (Microsoft.AspNetCore.Components.WebView.Wpf.BlazorWebView and ...WindowsForms.BlazorWebView) for adding Blazor-rendered screens to an existing Windows desktop app. WinUI 3 is not one of the officially documented BlazorWebView hosts; if you need Blazor content inside a WinUI 3 app, host it through WebView2 directly or keep the Blazor Hybrid surface on MAUI, WPF or Windows Forms.
How Blazor Hybrid Works: BlazorWebView and the Native Host#
BlazorWebView wraps the OS's native web view control: WebView2 (Chromium-based Microsoft Edge) on Windows, WKWebView on iOS and Mac Catalyst, and the platform WebView on Android. The control loads a local index.html host page, boots a Blazor runtime inside the app's own process, and renders Razor components into the page through a fast, local JavaScript interop channel rather than a network connection. There is no WebAssembly involved on any platform; components run as ordinary compiled .NET code, the same as any other code in the native app.
You register component roots the same way whether the host is XAML or Windows Forms: point BlazorWebView at a host HTML page and register one or more root components against CSS selectors in that page.
<!-- MAUI: MainPage.xaml -->
<BlazorWebView HostPage="wwwroot/index.html" x:Name="blazorWebView">
<BlazorWebView.RootComponents>
<RootComponent Selector="#app" ComponentType="{x:Type local:Routes}" />
</BlazorWebView.RootComponents>
</BlazorWebView>// MauiProgram.cs
builder.Services.AddMauiBlazorWebView();
#if DEBUG
builder.Services.AddBlazorWebViewDeveloperTools();
#endifWPF and Windows Forms follow the same two-step shape: register AddWpfBlazorWebView() or AddWindowsFormsBlazorWebView() with the DI container, then drop a BlazorWebView control onto a window or form and set its HostPage and RootComponents. Both controls expose BlazorWebViewInitializing and BlazorWebViewInitialized events, so you can reach the underlying WebView2 settings, for example to disable the context menu or configure a custom user data folder, before and after the web view is created.
Sharing UI with Razor Class Libraries#
The whole point of Blazor Hybrid is reusing UI, and the supported way to do that is a Razor class library (RCL): a project that holds the shared .razor components, styles and static assets, referenced by every hosting project (a Blazor Server or WebAssembly web app, and a MAUI, WPF or Windows Forms native app). The RCL itself should stay free of platform-specific code; where a component needs a capability that differs per platform, such as geolocation, define an interface in the RCL and let each host register its own implementation:
// In the RCL
public interface ILocationService
{
Task<Location?> GetCurrentLocationAsync(CancellationToken cancellationToken);
}
// MapComponent.razor (in the RCL) depends only on the abstraction
@inject ILocationService LocationService// App.Web (Blazor Server/WebAssembly) registers a browser-based implementation
builder.Services.AddScoped<ILocationService, WebLocationService>();
// App.Desktop (MAUI/WPF/WinForms) registers a platform implementation
builder.Services.AddSingleton<ILocationService, DesktopLocationService>();MapComponent never knows which implementation it got. This is the same pattern .NET MAUI apps already use for platform services in code-behind, applied at the component level so it works whether the component ends up rendering in a browser tab or inside a native web view. Keep the RCL's own dependencies minimal, JS interop that assumes a real browser (IJSInProcessRuntime, synchronous interop) works in Blazor WebAssembly but not in Blazor Hybrid's asynchronous interop channel, so gate any browser-only optimization behind a capability check and a fallback to the universal IJSRuntime API.
The .NET MAUI Blazor Hybrid and Web App Template#
Since ASP.NET Core 9.0, dotnet new maui-blazor-web scaffolds an entire solution in one step: a .NET MAUI Blazor Hybrid app, a Blazor Web App, and a shared RCL that holds the UI both projects render.
dotnet workload install maui
dotnet new maui-blazor-web -o ContosoApp -I AutoThe -I (--InteractivityPlatform) option picks the Blazor Web App's render mode: Server produces a single ASP.NET Core project using Interactive Server rendering, while WebAssembly and Auto each produce a server project plus a .Client project that runs on WebAssembly (Auto falls back to server rendering on first load, then switches to WebAssembly once it is cached). Whichever mode you pick, the generated app forces global interactivity: MAUI's BlazorWebView always renders interactively and throws if a page explicitly opts out of a render mode, since there is no concept of static, server-rendered-only content inside a native web view. The template also wires up the same DI-abstraction pattern shown above, out of the box, as example code for swapping implementations between the hybrid app and the web app.
Accessing Native APIs from Razor Components#
Beyond the interface-per-capability pattern, BlazorWebView exposes TryDispatchAsync (available since ASP.NET Core 8.0), which lets native, non-Blazor code reach into the running component tree's scoped services, useful when a native menu command or system tray icon needs to trigger something Blazor-side, such as navigation:
private async void OnNativeMenuRefreshClicked(object sender, EventArgs e)
{
var dispatched = await blazorWebView.TryDispatchAsync(services =>
{
var navigationManager = services.GetRequiredService<NavigationManager>();
navigationManager.NavigateTo("/orders", forceLoad: false);
});
if (!dispatched)
{
logger.LogWarning("Could not dispatch to the Blazor component tree.");
}
}For the common case of a component simply needing a device capability, @inject an abstraction as shown in the RCL section rather than reaching in from native code; reserve TryDispatchAsync for native-UI-initiated actions like menu items, tray icons or OS-level notifications that have to reach into Blazor state from outside the component tree.
Security in Blazor Hybrid Apps#
Blazor Hybrid intentionally does not reuse ASP.NET Core's cookie- or token-based web authentication, because native platform authentication libraries give stronger guarantees than anything a browser sandbox can provide. Each platform integrates with its own preferred identity stack: MAUI apps typically use WebAuthenticator to drive a browser-based OAuth or OpenID Connect flow and capture the callback, while WPF and Windows Forms apps typically use the Microsoft Authentication Library (MSAL) against Microsoft Entra ID. Whichever library authenticates the user, bridge the result into Blazor with a custom AuthenticationStateProvider so <AuthorizeView> and [Authorize] work the same as in a web app:
public sealed class NativeAuthStateProvider(AuthenticatedUser user) : AuthenticationStateProvider
{
public override Task<AuthenticationState> GetAuthenticationStateAsync() =>
Task.FromResult(new AuthenticationState(user.Principal));
}Register Microsoft.AspNetCore.Components.Authorization and the provider in the host's DI container after the native sign-in flow completes, before the BlazorWebView starts rendering components, so the first render already has an authenticated ClaimsPrincipal available. Because Razor components run natively rather than in a browser sandbox, treat any unhandled exception as fatal to the app, not just the page: subscribe to AppDomain.CurrentDomain.UnhandledException in WPF and Windows Forms hosts and log or surface it deliberately, since an uncaught exception inside a Blazor Hybrid component can otherwise crash the whole process.
AppDomain.CurrentDomain.UnhandledException += (sender, args) =>
{
logger.LogCritical(args.ExceptionObject as Exception, "Unhandled exception in Blazor Hybrid app");
#if !DEBUG
MessageBox.Show("An unexpected error occurred and the app needs to close.", "Error");
#endif
};Performance and Trade-offs vs Native UI#
A BlazorWebView pays two costs a fully native window does not: booting a small web runtime inside the process, and rendering through a web view's DOM instead of native controls directly. In practice this shows up as a visible, if short, delay on first render while the host page loads and the Blazor runtime initializes, and as HTML/CSS layout and paint costs instead of native control rendering. On Windows this cost is smaller than it used to be because WebView2 is Chromium-based and preinstalled on Windows 11 and most Windows 10 devices, but it is still not free. Interop calls between native code and Razor components are asynchronous by design, so avoid chatty, per-frame native calls from a component; batch data across the boundary instead of calling into native code in a tight loop.
Where Blazor Hybrid wins is total cost of ownership: one set of Razor components, one design system, one team skill set (C#, Razor, CSS) shared across a web app and every native target, instead of maintaining native XAML or WinForms UI per platform on top of the same backend. Where fully native UI wins is UI fidelity and raw rendering performance for animation-heavy or extremely data-dense screens, plus access to platform UI idioms (native menus, accessibility tooling tuned to native controls) that a web-rendered UI approximates rather than truly is.
Best Practices#
- Keep platform-specific code out of Razor class libraries. Define interfaces in the RCL and register platform implementations per host, exactly as the
ILocationServiceexample does. - Start from
dotnet new maui-blazor-webfor a new MAUI-plus-web project instead of wiring the RCL sharing pattern up by hand. - Bridge native authentication into a custom
AuthenticationStateProviderrather than trying to reuse ASP.NET Core cookie or bearer-token auth inside the web view. - Subscribe to
AppDomain.CurrentDomain.UnhandledExceptionin WPF and Windows Forms hosts so a component-level exception cannot silently crash the app. - Batch native interop calls instead of calling into platform code per frame or per keystroke from a component.
- Test on real devices for MAUI targets.
WKWebViewand Android'sWebViewhave real rendering and JS engine differences fromWebView2that only show up outside the Windows development machine.
Common Pitfalls#
- Assuming synchronous JS interop works.
IJSInProcessRuntimeis a Blazor WebAssembly optimization; Blazor Hybrid's interop channel is asynchronous only, so code written against the synchronous API fails at runtime in a hybrid host. - Forgetting global interactivity in the MAUI Blazor Web App template. A page that explicitly sets a non-global render mode throws inside the MAUI host, even though the same page works fine in the web project.
- Putting native platform code directly in the RCL instead of behind an interface, which breaks the web-hosted build of the same components.
- Reusing web-only authentication flows. Cookie-based ASP.NET Core Identity as configured for a web app does not carry over to a native
BlazorWebViewwithout the native-auth bridge described above. - Not handling
AppDomain.UnhandledException, leaving users with a silent crash instead of a diagnosable error.
Blazor Hybrid vs Other UI Approaches#
| Approach | Code reuse with an existing Blazor web app | Native look and feel | Best fit |
|---|---|---|---|
Blazor Hybrid (BlazorWebView) | Very high (RCL components render as-is) | Approximated via CSS/HTML | Teams with an existing Blazor app adding a desktop or mobile client |
| Native .NET MAUI XAML | Low (view models and services only) | Native | New cross-platform app with no existing web UI to reuse |
| WPF or WinUI 3 native UI | None | Native, most control | Windows-only apps prioritizing UI fidelity over reuse |
| Blazor WebAssembly in a browser | Full (same hosting model) | N/A, browser only | Public web app with no native distribution requirement |
Frequently Asked Questions#
Does Blazor Hybrid use WebAssembly?#
No. Razor components run as regular .NET code in the app's own process on every platform; BlazorWebView is used purely to render HTML and CSS, and interop between native code and components happens over a local channel, not through WebAssembly. This is different from Blazor WebAssembly, which does run .NET compiled to WebAssembly inside a browser sandbox.
Which app types officially support BlazorWebView?#
.NET MAUI, WPF and Windows Forms each ship an official BlazorWebView control. WinUI 3 is not one of the documented hosts; a WinUI 3 app that wants Blazor content should host WebView2 directly rather than expecting a first-party BlazorWebView package for it.
How do I share UI between a Blazor web app and a Blazor Hybrid app?#
Put the shared Razor components, along with any styling and static assets, in a Razor class library referenced by both projects. Anything that behaves differently per platform, such as geolocation or camera access, should be an interface defined in the RCL with separate implementations registered by the web project and the native project, not platform-specific code inside the shared components themselves.
Can I call native platform APIs directly from a Razor component?#
Yes, by injecting a service whose implementation was registered by the native host, the same dependency-injection pattern used everywhere else in .NET. Reserve TryDispatchAsync for the reverse direction, native code (a menu click, a tray icon, an OS notification) needing to reach into the running component tree, rather than for everyday native API access from components.
Is Blazor Hybrid slower than a fully native UI?#
It has real costs a native UI does not: a short web-runtime boot on first render and DOM-based rendering instead of native controls. For most line-of-business screens the difference is not noticeable to users, but very animation-heavy or extremely data-dense UI still renders faster and more predictably as fully native XAML or Windows Forms controls.
How do I authenticate users in a Blazor Hybrid app?#
Authenticate with the platform's native identity library first, typically WebAuthenticator on .NET MAUI or MSAL against Microsoft Entra ID on WPF and Windows Forms, then bridge the resulting identity into Blazor with a custom AuthenticationStateProvider so <AuthorizeView> and [Authorize] see the authenticated user.
Summary#
- Blazor Hybrid runs Razor components natively on the device and renders them into a
BlazorWebView, with no WebAssembly involved; MAUI, WPF and Windows Forms are the officially supported hosts. - Share UI through Razor class libraries, keep platform-specific behavior behind interfaces, and register a different implementation per host.
dotnet new maui-blazor-webscaffolds a MAUI Blazor Hybrid app, a Blazor Web App and a shared RCL together, including the DI-abstraction pattern.- Bridge native authentication into a custom
AuthenticationStateProvider, and handleAppDomain.CurrentDomain.UnhandledExceptionso component errors do not silently crash the app. - Expect a small first-render cost and DOM-based rendering compared with fully native UI; the payoff is one shared UI codebase across web and native targets.