'SetVirtualHostName within WebMessageReceived event handler doesn't work

I'm trying to dynamically create an image within a WebView2, using a source passed in from the WebView2's containing application.

The source image exists on an arbitrary location on the filesystem. To allow the WebView2 to access the image, I'm using SetVirtualHostNameToFolderMapping, which maps a hostname within the webview to some folder.

I want this to happen only after some interaction from inside the webview, using postMessage and the WebMessageReceived event, as described here.

If I call SetVirtualHostNameToFolderMapping outside of the WebMessageReceived handler, even after I've navigated to the HTML using NavigateToString, the webview loads the image.

But if I call SetVirtualHostNameToFolderMapping within the WebMessageReceived handler:

webview.CoreWebView2.WebMessageReceived += (s, e) => {
    webview.CoreWebView2.SetVirtualHostNameToFolderMapping(
        "assets",
        @"C:\path\to\folder",
        CoreWebView2HostResourceAccessKind.Allow
    );
    webview.CoreWebView2.PostWebMessageAsString(@"breakpoint.bmp");
};

the webview can't find the image; it fails with:

Failed to load resource: net::ERR_NAME_NOT_RESOLVED

How can I debug this? Are there any other alternatives I could use to do something similar?


XAML:

<Window x:Class="_testWebviewDialogs.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:wv="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf">
    <wv:WebView2 x:Name="webview" />
</Window>

Code-behind:

using System.IO;
using System.Threading.Tasks;
using System.Windows;
using Microsoft.Web.WebView2.Core;

namespace _testWebviewDialogs;
public partial class MainWindow : Window {
    public MainWindow() {
        InitializeComponent();
        Loaded += async (s, e) => await initializeAsync();
    }

    private async Task initializeAsync() {
        await webview.EnsureCoreWebView2Async();
        webview.CoreWebView2.WebMessageReceived += (s, e) => {
            webview.CoreWebView2.SetVirtualHostNameToFolderMapping(
                "assets",
                @"C:\path\to\folder",
                CoreWebView2HostResourceAccessKind.Allow
            );
            webview.CoreWebView2.PostWebMessageAsString(@"breakpoint.bmp");
        };

        var html = await File.ReadAllTextAsync("container.html");
        webview.NavigateToString(html);
    }
}

container.html:

<!DOCTYPE html>
<html>
<body>
    <button id="button1">Click me</button>
    <img id ="img1" src="" />
    <script>
        document.getElementById("button1").addEventListener('click', ev => {
            chrome.webview.postMessage('source');
        });
        window.chrome.webview.addEventListener('message', event => {
            document.getElementById('img1').src = `https://assets/${event.data}`;
        });
    </script>
</body>
</html>


Solution 1:[1]

I filed an issue for this. Per the response, this behavior is documented:

As the resource loaders for the current page might have already been created and running, changes to the mapping might not be applied to the current page and a reload of the page is needed to apply the new mapping. (source)

But it's possible to create (and recreate) a symbolic link to different target paths, and map the virtual host name to that symbolic link. The virtual host mapping uses the current symlink target.

var symlinkPath =
    Path.Combine(
        Path.GetDirectoryName(
            Assembly.GetExecutingAssembly().Location!
        )!,
        "assets"
    );

Directory.CreateDirectory(symlinkPath);

webview.CoreWebView2.SetVirtualHostNameToFolderMapping(
    "assets",
    symlinkPath,
    CoreWebView2HostResourceAccessKind.Allow
);

webview.CoreWebView2.WebMessageReceived += (s, e) => {
    Directory.Delete(symlinkPath);
    Directory.CreateSymbolicLink(symlinkPath, @"C:\path\to\target\directory");
    webview.CoreWebView2.PostWebMessageAsString(@"breakpoint.bmp");
};

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Zev Spitz