If you are building modern desktop applications using the Windows App SDK and WinUI 3, you likely rely on the WebView2 control to embed web content and execute JavaScript seamlessly. However, one of the most common exceptions encountered by WinUI 3 developers is a NullReferenceException thrown because CoreWebView2 is null when attempting to call ExecuteScriptAsync.
In this comprehensive guide, we will break down why this error occurs, analyze the underlying architecture of Microsoft Edge WebView2, and walk through multiple proven solutions to fix the issue permanently.
Understanding the Problem: Why Is CoreWebView2 Null?
In WinUI 3, the WebView2 element in your XAML file is merely a wrapper framework control. It hosts the underlying Chromium-based Microsoft Edge engine represented by the CoreWebView2 property.
Unlike traditional XAML controls (such as Button or TextBox), the internal browser engine initialization is an asynchronous, out-of-process operation. When your page or window loads:
- The XAML UI framework instantiates the
WebView2container. - The underlying browser process (
msedgewebview2.exe) initializes independently in the background. - Until that background initialization finishes,
MyWebView.CoreWebView2remainsnull.
If you invoke MyWebView.ExecuteScriptAsync(...) in your constructor, Loaded event, or a button click handler before the browser engine has initialized, your application crashes immediately with a NullReferenceException.
Method 1: Explicit Initialization via EnsureCoreWebView2Async() (Recommended)
The most robust and direct way to resolve this issue is to explicitly await the initialization using EnsureCoreWebView2Async() before running any JavaScript.
Step-by-Step Implementation
Here is how to properly structure your C# code-behind:
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System;
using System.Threading.Tasks;
namespace WinUI3WebViewDemo
{
public sealed partial class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
}
private async void RunScriptButton_Click(object sender, RoutedEventArgs e)
{
await ExecuteCustomScriptAsync();
}
private async Task ExecuteCustomScriptAsync()
{
try
{
// 1. Ensure the underlying CoreWebView2 engine is initialized
if (MyWebView.CoreWebView2 == null)
{
await MyWebView.EnsureCoreWebView2Async();
}
// 2. Now it is safe to execute JavaScript
string script = "document.title;";
string result = await MyWebView.ExecuteScriptAsync(script);
System.Diagnostics.Debug.WriteLine($"Document Title: {result}");
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Error executing script: {ex.Message}");
}
}
}
}
Why This Works
EnsureCoreWebView2Async() triggers the background process creation if it has not yet begun, and returns an awaitable task that completes only when CoreWebView2 is non-null and ready for interaction.
Method 2: Listening to CoreWebView2Initialized Event
If you prefer an event-driven architecture rather than inline asynchronous calls, you can listen to the CoreWebView2Initialized event provided by the WebView2 control.
XAML Setup
<Window
x:Class="WinUI3WebViewDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<WebView2 x:Name="MyWebView"
Source="https://example.com"
CoreWebView2Initialized="MyWebView_CoreWebView2Initialized" />
</Grid>
</Window>
C# Code-Behind
private async void MyWebView_CoreWebView2Initialized(WebView2 sender, CoreWebView2InitializedEventArgs args)
{
if (args.Exception != null)
{
// Handle initialization failure (e.g., missing runtime)
System.Diagnostics.Debug.WriteLine($"Initialization failed: {args.Exception.Message}");
return;
}
// CoreWebView2 is guaranteed to be non-null here
await sender.ExecuteScriptAsync("console.log('WebView2 Initialized Successfully!');");
}
Method 3: Executing Scripts After DOM / Page Navigation Completes
Even after CoreWebView2 is initialized, running JavaScript that interacts with specific DOM elements too early might return null or undefined because the page content is still downloading.
To ensure both CoreWebView2 is valid and the web document is fully rendered, attach a handler to the NavigationCompleted event:
public MainWindow()
{
this.InitializeComponent();
MyWebView.NavigationCompleted += MyWebView_NavigationCompleted;
}
private async void MyWebView_NavigationCompleted(WebView2 sender, Microsoft.Web.WebView2.Core.CoreWebView2NavigationCompletedEventArgs args)
{
if (args.IsSuccess)
{
// CoreWebView2 is available and the page has finished loading
string pageHeight = await sender.ExecuteScriptAsync("document.body.scrollHeight;");
System.Diagnostics.Debug.WriteLine($"Rendered Page Height: {pageHeight}");
}
}
Common Pitfalls and Best Practices
To keep your WinUI 3 WebView2 integration smooth and crash-free, keep these tips in mind:
1. Avoid Calling ExecuteScriptAsync in Window Constructors
Never call ExecuteScriptAsync directly in your MainWindow() or Page constructor. The visual tree is not yet loaded, and the web view initialization pipeline has not started.
2. Handle Custom Environments and User Data Folders
If you need custom flags or a specific storage path for browser cache and cookies, create a custom CoreWebView2Environment before calling EnsureCoreWebView2Async:
var customEnv = await Microsoft.Web.WebView2.Core.CoreWebView2Environment.CreateWithOptionsAsync(
browserExecutableFolder: null,
userDataFolder: System.IO.Path.Combine(Windows.Storage.ApplicationData.Current.LocalFolder.Path, "WebView2Data"),
options: new Microsoft.Web.WebView2.Core.CoreWebView2EnvironmentOptions()
);
await MyWebView.EnsureCoreWebView2Async(customEnv);
3. Verify Thread Affinity
WebView2 APIs must be called on the UI thread. If you are dispatching work from background tasks or thread pool threads, ensure you use DispatcherQueue.TryEnqueue() to return execution to the UI thread before calling ExecuteScriptAsync.
Summary
The CoreWebView2 is null error in WinUI 3 is not a bug; it is an expected characteristic of asynchronous browser runtime initialization. By explicitly using await MyWebView.EnsureCoreWebView2Async(), listening to CoreWebView2Initialized, or awaiting NavigationCompleted, you can safely execute scripts and build reliable, high-performance desktop hybrid apps.
Comments
Post a Comment