Resolved: Embedding WebView2 DLL into wpf portable executable

Question:

I am trying to embed WebView2 DLL in a C# project. I have added the 3 DLLs :
Microsoft.Web.WebView2.Wpf.dll
Microsoft.Web.WebView2.Core.dll
WebView2Loader.dll
as embedded resource to my project. Since it is a WPF project I have no need of Microsoft.Web.WebView2.Forms.dll.
I have already Newtonsoft.Json.dll correctly incorporated.
In debug mode, there is no problem because all DLL are copied to the output directory.
As I want that the executable to be portable, I want only a single file (I know that copying the DLL with the exe will work, but I need a single file). When I move the main executable only to another folder, the application crashes when I use the webview2 control (the control is not loaded at start).
I have tried to figure out if all the DLL were involved. Microsoft.Web.WebView2.Wpf.dll is correctly embedded and I have no need of it in the destination folder.
However, the last two ones are still required. I think this is probably because they are called by Microsoft.Web.WebView2.Wpf.dll and not by the main assemby.
How can I load the last two ones from the main assembly to have a single execution file ?
EDIT: All DLL are added to the project as embedded resource

EDIT2


Thanks to @mm8 to give me ideas to partialy (any suggestions for complete solution are welcomed) achieve this.
So, It is possible to embed the 3 WebView2 DLL into you project by adding them and setting them as embedded resource. Microsoft.Web.WebView2.Wpf.dll (or Microsoft.Web.WebView2.WinForms.dll, depending on your choice) can be loaded directly into memory. But the last two ones (Microsoft.Web.WebView2.Core.dll and WebView2Loader.dll) need to be saved as files :
public MainWindow()
{
   // For Newtonsoft.Json.dll, Microsoft.Web.WebView2.Wpf.dll, Microsoft.Web.WebView2.Core.dll and WebView2Loader.dll
   // Incorporation as embedded resource
   // To avoid having multiple files, only one executable
   AppDomain.CurrentDomain.AssemblyResolve += GetEmbeddedDll;
   ExtractWebDLLFromAssembly();

   InitializeComponent();
}

private byte[] GetAssemblyBytes(string assemblyResource)
{
    using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(assemblyResource))
    {
        return new BinaryReader(stream).ReadBytes((int)stream.Length);
    }
}

// For Newtonsoft.Json.dll and Microsoft.Web.WebView2.Wpf.dll
private Assembly GetEmbeddedDll(object sender, ResolveEventArgs args)
{
    string keyName = new AssemblyName(args.Name).Name;

    // If DLL is loaded then don't load it again just return
    if (_libs.ContainsKey(keyName))
    {
        return _libs[keyName];
    }

    Assembly assembly = Assembly.Load(GetAssemblyBytes(Assembly.GetExecutingAssembly().GetName().Name + "." + keyName + ".dll"));
    _libs[keyName] = assembly;
    return assembly;
}

// For Microsoft.Web.WebView2.Core.dll and WebView2Loader.dll
// Incorporation as embedded resource but need to be extracted
private void ExtractWebDLLFromAssembly()
{
    string[] dllNames = new string[] { "Microsoft.Web.WebView2.Core.dll", "WebView2Loader.dll" };
    string thisAssemblyName = Assembly.GetExecutingAssembly().GetName().Name;
    foreach (string dllName in dllNames)
    {
        string dllFullPath = Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, dllName);
        try
        {
            File.WriteAllBytes(dllFullPath, GetAssemblyBytes(thisAssemblyName + "." + dllName));
        }
        catch
        {
            // Depending on what you want to do, continue or exit
        }
    }
}
The ExtractWebDLLFromAssembly method is based from this post @nawfal‘s answer
As the ExtractWebDLLFromAssembly method is called only once, its code can also be directly integrated in MainWindow.

One thing is still missing :


I am facing issues when I try to delete these files in the MainWindow_OnClosing event saying that files are in use by another process (in fact, the main process), even if the control is not loaded or is disposed. I am using this code to be sure that CoreWebView2 was unloaded inside the control :
int maxTimeout = 2000;
uint wvProcessId = wv_ctrl.CoreWebView2.BrowserProcessId;
string userDataFolder = wv_ctrl.CoreWebView2.Environment.UserDataFolder;
int timeout = 10;
wv_ctrl.Dispose();
try
{
    while (System.Diagnostics.Process.GetProcessById(Convert.ToInt32(wvProcessId)) != null && timeout < maxTimeout)
    {
        System.Threading.Thread.Sleep(10);
        timeout += 10;
    }
}
catch { }
This code allows me to correctly delete the UserDataFolder, but the DLL are still in use.
After further inspection, I know that Microsoft.Web.WebView2.Core.dll is still loaded as an assembly in the CurrentAppDomain which is obviously a problem.
And WebView2Loader.dll is loaded as a module of the Process. If I try to force to unload it with
 [DllImport("kernel32.dll", SetLastError = true)]
 public static extern void FreeLibrary(IntPtr module)
this does not work. I think this is because Microsoft.Web.WebView2.Core.dll use it. And this should be the root problem left for me.

EDIT 3


I have tried to load the WebView control in an other thread. I don’t need to retrieve values from the control. I only need to set the source on code behind. I have tried several things without any success, the source of the threaded control is not updated (the EnsureCoreWebView2Async method called after the control Loaded event stays awaited indefinitly). The main idea to achieve this is inspired from here

EDIT 4


Ok, I manage one file :). WebView2Loader.dll can be unloaded with FreeLibrary contrary that what I said in edit 2, I was trying to unload it after the Dispose() method, while you need to wait the dispose is completed. So you need to call it after the catch block
int maxTimeout = 2000;
uint wvProcessId = wv_ctrl.CoreWebView2.BrowserProcessId;
string userDataFolder = wv_ctrl.CoreWebView2.Environment.UserDataFolder;
int timeout = 10;
wv_ctrl.Dispose();
try
{
    while (System.Diagnostics.Process.GetProcessById(Convert.ToInt32(wvProcessId)) != null && timeout < maxTimeout)
    {
        System.Threading.Thread.Sleep(10);
        timeout += 10;
    }
}
catch { }
UnloadModule("WebView2Loader.dll");
if (Settings.Default.DeleteBrowserNavigationData)
{
    try
    {
        Directory.Delete(userDataFolder, true);
    }
    catch { }
}
// Delete WebView2 DLLs (2nd line throws an error)
try
{
    File.Delete(Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, "WebView2Loader.dll"));
    File.Delete(Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, "Microsoft.Web.WebView2.Core.dll"));
 }
 catch { }
The UnloadModule method is taken from this, @SI4’s answer
The last dll, Microsoft.Web.WebView2.Core.dll is opened within the CurrentAppDomain and cannot unloaded this way nor be deleted. I should probably load it within a new AppDomain so that I can unload it. However, I don’t know how to achieve this with the ability to change the Source property of the control. May be the whole code should be loaded in a new AppDomain ?

EDIT 5


I have found a new option that could be the solution : WPF Add-In I have created the files from the example, the user control contains WebView2 instead of a button. However, for both AddInAdapter and HostAdapter, FrameworkElementAdapters is marked with an error “The name does not exist in the current context” ??? (References has been added to the project which is targetting .Net 4.6.2). Any clue ? But, it seems that application needs to have different folders. If this is the case, this is a wrong turn. I still need one executable and nothing more.
Thanks

Answer:

If your application does not need to target a particular framework which does not yet integrate this functionality, you have the option of using the Single File Application available since .NET Core 3.0.
I have tried with a WPF Application using a WebView2, and with this method you can publish the application and the references in the same .exe file.
Since WebView2 works with native libraries, you just need to add the following line in your PropertyGroup of your .csproj file.:
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>

If you have better answer, please add a comment about this, thank you!