In this detailed post, Rick Strahl discusses common challenges and streamlined solutions for handling document load events in WebView2 controls within .NET and WPF applications. He introduces the Westwind.WebView library, focusing on its WaitForDocumentLoaded() helper for more linear, maintainable workflows.

WebView2: Waiting for Document Loaded

Author: Rick Strahl


When building hybrid web applications where .NET code interacts with embedded HTML content using WebView2, one of the most persistent challenges is handling the asynchronous nature of document loading and subsequent interaction via script code. This article demonstrates practical methods for managing this complexity, including the use of event handlers and a streamlined async helper available in the Westwind.WebView library.


Understanding the Challenge: Asynchronous Loading in WebView2

Modern UI frameworks like WPF offer the WebView2 control for rendering web content. Loading documents in WebView2 is inherently asynchronous, leading to possible timing issues when code tries to access the DOM or execute scripts before the document is fully loaded.

A basic example illustrates the problem:

WebView.Source = "http://localhost:5200/MyPage";
// BOOM! await WebView.ExecuteAsync("alert('Document has loaded.')");

This sequence will fail—ExecuteAsync targets a document that might not be available yet.

The Usual Fix: Event Handlers

The standard approach involves handling the CoreWebView2.DOMContentLoaded event:

string envPath = Path.Combine(Path.GetTempPath(), "WpfSample_WebView");
var environment = await CoreWebView2Environment.CreateAsync(envPath);
await webBrowser.EnsureCoreWebView2Async(environment);

WebView.Source = "http://localhost:5200/MyPage";
WebBrowser.CoreWebView2.DOMContentLoaded += async (s, args) => {
    await WebView.ExecuteAsync("alert('Document has loaded.')");
};

Tip: Always specify a WebView Environment Folder, preferably under a temp location. By default, some locations may be non-writable on user systems, resulting in subtle failures.

However, this pattern is:

  • Verbose
  • Requires explicit event handler registration/deregistration
  • Difficult to manage for multiple WebViews or documents
  • Tends to fragment application logic along awkward event boundaries

A Cleaner Alternative: WebView2Handler.WaitForDocumentLoaded()

The Westwind.WebView library offers enhancements for WebView2, most notably the WaitForDocumentLoaded() async helper.

Key Features

  • Automatic (optional) WebView environment management
  • Shared environments among multiple controls
  • JavaScript interop helpers
  • Easy navigation modes
  • Simple linear async helper: WaitForDocumentLoaded()
  • Detection for compatible runtime installs

Installing the Package

dotnet add package Westwind.WebView

Using WebViewHandler and WaitForDocumentLoaded

  1. Declare a property for the handler in your control/window:

     public WebViewHandler WebViewHandler { get; set; }
     WebViewHandler = new WebViewHandler(WebView); // inside your constructor
    
  2. Handling navigation and waiting for the document:

     private async void EmojiWindow_Loaded(object sender, RoutedEventArgs e) {
         WebViewHandler.Navigate("http://localhost:5200/MyPage");
         if(!await WebViewHandler.WaitForDocumentLoaded(5000))
             throw new ApplicationException("Webpage failed to load in time...");
         await WebBrowser.ExecuteScriptAsync("alert('You have arrived')");
     }
    

Alternatively, you may use the built-in JS interop:

await WebViewHandler.JsInterop.Invoke("alert","You have arrived");

This approach ensures correct parameter encoding and easier maintenance for repeated JS interaction.


How WaitForDocumentLoaded() Works

The helper leverages a TaskCompletionSource to bridge between the CoreWebView2.DOMContentLoaded event and async/await patterns. Here’s the core logic (abridged):

public async Task<bool> WaitForDocumentLoaded(int msTimeout = 5000){
    if (IsLoaded) return true;
    if (IsLoadedTaskCompletionSource == null)
        IsLoadedTaskCompletionSource = new TaskCompletionSource();
    var task = IsLoadedTaskCompletionSource.Task;
    if (task == null) return false;
    if (task.IsCompleted) return true;
    var timeoutTask = Task.Delay(msTimeout);
    var completedTask = await Task.WhenAny(task, timeoutTask);
    return completedTask == task;
}
  • The event handler for CoreWebView2.DOMContentLoaded marks the load as complete:

      protected virtual async void OnDomContentLoaded(object sender, CoreWebView2DOMContentLoadedEventArgs e) {
          IsLoaded = true;
          if (IsLoadedTaskCompletionSource?.Task is { IsCompleted: false })
              IsLoadedTaskCompletionSource?.SetResult();
      }
    
  • New navigations reset the TCS:

      protected virtual void OnNavigationStarting(object sender, CoreWebView2NavigationStartingEventArgs e) {
          IsLoaded = false;
          IsLoadedTaskCompletionSource = new TaskCompletionSource();
      }
    

This pattern supports repeated navigations and tight coordination between UI actions and web content readiness.


Real-World Scenarios

  • User Interactivity:
    • In applications like Markdown Monster, dialogs using WebView might encounter user input before the window is fully initialized. Wrapping event handlers with await WebViewHandler.WaitForDocumentLoaded(1000) avoids referencing uninitialized objects, preventing crashes and null reference faults.
  • Multiple Coordinated Loads:
    • When loading editors and previews simultaneously—such as in markdown authoring tools—it’s critical to ensure each WebView is loaded in sequence before querying or updating their content, improving correctness and reliability.

Example:

// Wait for the editor WebView to be loaded before accessing its content
if( !await editor.EditorHandler.WaitForLoaded(1000) ) {
    await Window.CloseTab(tab);
    WindowsNotifications.ShowInAppNotifications(...);
    return;
}
topic.Body = await editor.GetMarkdown();

Summary

While setting up custom event handlers for every content load is possible, it fragments application logic and invites errors. The async helper WaitForLoaded() from the Westwind.WebView library makes it easier to coordinate UI actions, prevent null-reference races, and maintain consistency. It reduces guesswork, replacing brittle workarounds with clear, reusable patterns—and is especially effective in complex UI applications relying on dynamic web content.


Resources

Related posts:


If you found this article useful, consider supporting the author with a small donation.

This post appeared first on “Rick Strahl’s Blog”. Read the entire article here