2

My C# application uses WebView2.

It is required that multiple instances are open at the same time which do not share sessions. According to this explanation of the WebView2 process model, this is achieved by using different UserDataFolders, passed at creation of the CoreWebView2Environment.

The app is currently loaded from a read-only network share, so the default setting to create the user data folders alongside the exe is not eligible, so my implementation creates different UserDataFolders in the user temp directory.

To clean up, I would like to delete the created directories when the application is closed. The documentation suggests the BrowserProcessExited Event which should be called when all resources taken by the WebView2 are released.

But the BrowserProcessExited event never gets called.

In the Page where the WebView2 is used, I do:

public void MyApp_Closing(object sender, CancelEventArgs e)
{
    glucoTabWebcontrol2.CoreWebView2.Environment.BrowserProcessExited += Environment_BrowserProcessExited;
}

// This is never called
private void Environment_BrowserProcessExited(object sender, CoreWebView2BrowserProcessExitedEventArgs e)
{
    try
    {
        System.IO.Directory.Delete(((CoreWebView2Environment)sender).UserDataFolder);
    } catch (Exception ex)
    {
        ... handle exception
    }
}

My guess is that the application is closed before the event gets fired. What is necessary to achieve that the BrowserProcessExited event is received?

Peter_B
  • 23
  • 3
  • 2
    Just for kicks, let's try hooking up the event handler sooner, before the Closing event fires. – rfmodulator Jan 18 '22 at 23:11
  • As @rfmodulator says. It should be safe to hook up that event in the `Form_Load` event handler. – Poul Bak Jan 19 '22 at 13:40
  • 1
    Thanks @rfmodulator and @poul-bak, but that did not yet solve it. I tried different locations for attaching the event handler. The environment is described asynchronously, and I attached the event handler right after creation, after ```await glucoTabWebcontrol2.EnsureCoreWebView2Async(env);```, as it is done [here](https://learn.microsoft.com/en-us/microsoft-edge/webview2/get-started/winforms). I also tried immediately after first navigation of the WebView2, but still the code in the event handler is never reached. – Peter_B Jan 19 '22 at 20:21
  • With what you've described you can delete your data folders right before the app exits, so no event is needed. Put them all under a common temporary folder and delete that folder. – rfmodulator Jan 19 '22 at 20:40
  • I tried from Application_Exit and from Window_Closing, in both cases I get "The access to the path \"BrowserMetrics-61E98C1E-4994.pma\" was denied." – Peter_B Jan 20 '22 at 16:23
  • I did some testing in one of my projects. `BrowserProcessExited` fires every time, however `((CoreWebView2Environment)sender).UserDataFolder` isn't valid: `'CoreWebView2Environment' does not contain a definition for 'UserDataFolder'...`. What versions are you using? I am referencing the NuGet Package `Microsoft.Web.WebView2, v1.0.1020.30`, and the installed runtime version is `97.0.1072.62`. – rfmodulator Jan 20 '22 at 17:41
  • Maybe the UserDataFolder is only set when you use it for creation of the environment. In my case it is valid and set. I updated to the most current NuGet version of ```Microsoft.Web.WebView2, v1.0.1020.30``` 1.0.1072.54 and use the same runtime version as you. Maybe I should start with a clean project and give it a try again. – Peter_B Jan 20 '22 at 18:11
  • "`BrowserProcessExited` fires every time..." I'm doing a lot of weird things in this project, after further testing it seems `BrowserProcessExited` is only fired when I call `Dispose()` on the `WebView2`. But `UserDataFolder` still makes me question your versions. – rfmodulator Jan 20 '22 at 18:11
  • It's not that `UserDataFolder` isn't set, it's not defined in the `CoreWebView2Environment` class, the code doesn't build. – rfmodulator Jan 20 '22 at 18:16
  • I did some debugging: `CoreWebView2Environment env = await CoreWebView2Environment.CreateAsync();` `env.BrowserProcessExited += Environment_BrowserProcessExited;` debug result: env.BrowserProcessExited is assigned a value {Method = {Void Environment_BrowserProcessExited ...} `await webView2.EnsureCoreWebView2Async(env);` debug result: webView2.CoreWebView2.Environment.BrowserProcessExited = null Also later, no assignments to `webView2.CoreWebView2.Environment.BrowserProcessExited += ...` have an effect. So, what's the correct way to attach the event handler, or is that a bug? – Peter_B Jan 20 '22 at 21:52
  • I'm able to set the event handler on the `CoreWebView2Environment` object both before and after `EnsureCoreWebView2Async(...)` is called, as well as after it's called directly through `webView2.CoreWebView2.Environment`. In all three cases the event fires after I call `webView2.Dispose()`. – rfmodulator Jan 20 '22 at 23:21
  • What is your target framework? – rfmodulator Jan 24 '22 at 19:48
  • I tried with .NET framework 4.7.2 and 4.8 – Peter_B Jan 25 '22 at 23:04
  • 4.7.2 is what I'm using as well. – rfmodulator Jan 29 '22 at 18:36

2 Answers2

1

In the MyApp_Closing method, retrieve the pid of WebView2 browser, then dispose the control.

Once dispose, wait the process to be exited (timeout at 2 seconds)

When the the process is effectively stopped, then delete the folder :

try
{
    uint wvProcessId = glucoTabWebcontrol2.CoreWebView2.BrowserProcessId;
    string userDataFolder = glucoTabWebcontrol2.CoreWebView2.Environment.UserDataFolder;
    int timeout = 10;

    glucoTabWebcontrol2.Dispose();
    try
    {
        while (System.Diagnostics.Process.GetProcessById(Convert.ToInt32(wvProcessId)) != null && timeout < 2000)
        {
            System.Threading.Thread.Sleep(10);
            timeout += 10;
        }
    }
    catch { }
    Directory.Delete(userDataFolder, true)
}
catch
{
    // Eventually show error deletion here
}
CFou
  • 978
  • 3
  • 13
1

You must wait for the process to exit before shutting down the application. From the docs:

To delete a user data folder (UDF), you must first end the WebView2 session. You cannot delete a UDF if the WebView2 session is currently active.

If files are still in use after your WebView2 host app closes, wait for browser processes to exit before deleting the user data folder (UDF).

Files in UDFs might still be in use after the WebView2 app is closed. In this situation, wait for the browser process and all child processes to exit before deleting the UDF. To monitor processes to wait for them to exit, retrieve the process ID of the browser process by using the BrowserProcessId property of the WebView2 app instance.

I did it this way, which is similar to CFou's answer:

Application.Current.Exit += OnAppExit;
private void OnAppExit(object sender, ExitEventArgs e)
{
    try
    {
        // Delete WebView2 user data before application exits
        string? webViewCacheDir = Browser.CoreWebView2.Environment.UserDataFolder;
        var webViewProcessId = Convert.ToInt32(Browser.CoreWebView2.BrowserProcessId);
        var webViewProcess = Process.GetProcessById(webViewProcessId);

        // Shutdown browser with Dispose, and wait for process to exit
        Browser.Dispose();
        webViewProcess.WaitForExit(3000);

        Directory.Delete(webViewCacheDir, true);
    }
    catch (Exception ex)
    {
        // log warning
    }
}

I set the timeout to 3 seconds, which should be plenty. Beware not to use the async version of WaitForExit because async void methods cannot be awaited, so WPF will happily continue shutting down the application.

Shahin Dohan
  • 6,149
  • 3
  • 41
  • 58
  • That's the correct answer. It works as you described it. Thank you! The answer from @CFou below is similar and probably works as well, but the process.WaitForExit(...) is cleaner. – Peter_B Jun 14 '22 at 17:28