Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using WebView2 in a WPF Assembly #730

Closed
AndrewMore1 opened this issue Dec 10, 2020 · 42 comments
Closed

Using WebView2 in a WPF Assembly #730

AndrewMore1 opened this issue Dec 10, 2020 · 42 comments
Assignees
Labels
bug Something isn't working tracked We are tracking this work internally.

Comments

@AndrewMore1
Copy link

AndrewMore1 commented Dec 10, 2020

Description
I'm having some issues with integrating the Webview2 WPF control into a larger control packaged as an assembly. That assembly is then integrated into other applications.

Ex:
Application - Exe
+- Control Assembly - .NET Dll
+- WebView2 control - Microsoft.Web.Webview2.Wpf
+- WebView2Loader.dll

While I can build and run the application successfully, the control containing the WPF WebView2 control comes up blank, and does not load the website hard coded in the SRC element. Looking into this further, I believe this is related to how the WebView2Loader is handled. I've seen other people reporting issues with this when the WebView2Loader dll is not correctly integrated.

I can get the sample applications, and stand alone apps to work correctly, this seems to be related to packaging this in an assembly DLL.

I'm guessing there's some place the application is expected to load this DLL from. I also know there are multiple versions, depending on architecture.

Version
SDK: .NET Framework 4.6.2. Webview2 ver 1.0.664.37
DLL Output: Class Library
Framework: WPF
OS: Windows 10

Other stuff: the application this control is currently being integrated into is an Office VSTO add-in. Therefore the target application for the .NET runtime is x86. We will potentially have other applications using the library in the future.

We set the compiler flag CPU=any such that our add-in can run in a 32-bit or 64-bit process. It's also possible that future users of the control assembly will be using x64.

AB#31148967

@AndrewMore1 AndrewMore1 added the bug Something isn't working label Dec 10, 2020
@dwatkins-FNA
Copy link

I think I'm also having the same problem. It is a class library that is an add-in for another app with a Windows Form user control that has Webview2 in it but it does the same thing "control comes up blank, and does not load the website hard coded in the SRC element."

Pulling the form out into a standalone Windows Form App everything works.

@lingamy lingamy added the tracked We are tracking this work internally. label Dec 22, 2020
@lingamy
Copy link
Collaborator

lingamy commented Dec 22, 2020

@AndrewMore1 @dwatkins-FNA, Thanks for reporting the issue. We'll try to repro the issue from our side and investigate.

@dmcgloin
Copy link

dmcgloin commented Jan 5, 2021

This may be affecting our WPF assembly as well. Using 1.0.721-prerelease, I see a grey screen load about 1 in 6 times. If I resize the parent window, the web page redraws correctly.

@ingemarson
Copy link

  • When having the webview in an user control which is packed in a dll => it works when I add that user control then in the main window of the wpf application.
  • When having the webview in a main window which is packed in a dll => it does not work when I try to run it with Application app = new Application(); app.Run(new Window1()); in a e.g. console application

@m-sterspace
Copy link

Yeah, I believe I'm having the same issue trying to use this in a .Net Framework class library.

It's a plugin for Autodesk software, built in .Net Framework 4.7, and I can get nothing but a blank grey screen when trying to launch the window contained in my dll. I can get it working if I build my own .Net Framework application, but since this is a plugin for third party software that is not an option for us unless we were to use IPC, at which point we may as well just use Electron.

@champnic
Copy link
Member

Hey all - it's a bit tough to parse this thread as there seem to be many examples of this, but it's unclear if they all stem from the same problem.

I tried the following repro:

  • Create a WPF User Control (.NET Framework), install WebView2 nuget package, and add <wv2:WebView2 Source="https://bing.com" /> to UserControl1.xaml.
  • Create a WPF App (.NET Framework), add a reference to my user control assembly, and add <uc:UserControl1 />
    When I tried to build and run the app, it first complained about a XAML parser error because it didn't have a reference to the WebView2 assemblies. Installing the WebView2 nuget to the app project made everything build and run correctly - WebView2 launched and navigated to Bing fine.

For everyone - is anyone getting a crash or stack during launch? Are you adding the WebView2 nuget package to the project that is also consuming the WPF/WebView2 control assembly?

@AndrewMore1 - If the WebView2Loader.dll is not deployed with the app, WebView2 will not load properly. Typically, WebView2Loader.dll is deployed by building with the WebView2 nuget package .targets file, which specifies where it should be placed. How are you doing this in your scenario? If your control is being deployed as a nuget package, are you specifying WebView2 nuget as a dependency? Or are you just including references to the WebView2 assemblies (Microsoft.Web.WebView2.Core/WPF.dll)?

@dwatkins-FNA - When you say "pull the form out in a standalone Windows Form App" are you saying take your WebView2 code and put it directly into the app instead of using a separate control assembly?

@dmcgloin - This sounds like it may be a separate issue if it's transient and inconsistent. Can you try with the latest prerelease package to see if the issue persists, and open a new issue if it does?

@ingemarson - Your example makes it sound like the core scenario is working (adding a WebView2 in a separate control assembly to an app), but it's not working only when included in a console application. Is that right? Does the console application work if you try to include the WebView2 control directly, rather than using a separate assembly? May be related to #970.

@m-sterspace - If I understand correctly your scenario is also broken similar to @ingemarson's and #970, in that it works fine if you include your assembly in a normal .NET Framework app, but when trying to launch differently it fails. Can you share the code you are using to launch the WebView2 in your assembly that's failing?

Thanks all!

@champnic
Copy link
Member

I've been able to repro using the following steps:

  1. Create Window in a WPF .NET Framework assembly that includes WebView2
<wv2:WebView2 x:Name="webView2" Source="https://bing.com"/>
  1. Create .NET Framework Console App
  2. Add WebView2 nuget package and references to my new assembly
  3. Launch Application with my WebView2 Window from the assembly
using System.Windows;
using WpfUserControl;

namespace ConsoleApp
{
    class Program
    {
        [STAThread]
        static void Main(string[] args)
        {
            Application app = new Application();
            app.Run(new WV2Window());
        }
    }
}

However, if I switch to using EnsureCoreWebView2Async() to initialize the WebView2 in my Window instead of the Source property, it works fine:

        private async void Window_Loaded(object sender, RoutedEventArgs e)
        {
            var env = await CoreWebView2Environment.CreateAsync();
            await webView2.EnsureCoreWebView2Async(env);
            webView2.CoreWebView2.Navigate("https://bing.com");
        }

image

For those of you running into this issue, can you try that workaround?

@dwatkins-FNA
Copy link

@dwatkins-FNA - When you say "pull the form out in a standalone Windows Form App" are you saying take your WebView2 code and put it directly into the app instead of using a separate control assembly?

@champnic Yes, if I remove the WebView2 code from the class library and put it into a test Windows Form App it worked. When it is part of the class library it did not.

I tried testing your workaround and at var env = await CoreWebView2Environment.CreateAsync(); Getting an error

System.DllNotFoundException: Unable to load DLL 'WebView2Loader.dll': The specified module could not be found. (Exception from HRESULT: 0x8007007E)
at Microsoft.Web.WebView2.Core.CoreWebView2Environment.CreateCoreWebView2EnvironmentWithOptions(String browserExecutableFolder, String userDataFolder, ICoreWebView2EnvironmentOptions options, ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler environment_created_handler)
at Microsoft.Web.WebView2.Core.CoreWebView2Environment.d__3.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
at FNA_Group_pdm_Add_In.PDF_Tab.d__9.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()

@champnic
Copy link
Member

How are you including WebView2 in your app that's consuming the library? Are you including the WebView2 nuget package?

@dwatkins-FNA
Copy link

Yes, NuGet Microsoft.Web.WebView2 1.0.774.44, it shows WebView2.Core, .WinForm & .Wpf in the referneces.

@champnic
Copy link
Member

Do you have a repro you would be able to share that I could debug?

@AndrewMore1
Copy link
Author

@champnic I have tried a couple of different things to distribute the WebView2 control. First I tried just the nuget package on the assembly that was using the WebView2 control. Then I added it to the application as well. This seemed to work for a period, and then stopped working for no apparent reason. I have also tried copying the WebView2.dll and the platform specific dlls into various directories manually, which also failed.

I just tried your suggested work around, and I don't see any change. I'll see if I can simplify the code I'm using down far enough for a demo of the problem.

@dwatkins-FNA
Copy link

@champnic my class library is used as an add-in for SolidWorks PDM. I don't think there is a way to run it without SolidWorks PDM.

@AndrewMore1
Copy link
Author

FWIW, mine is used as an add in to an Office Client VTSO plug-in.

@AndrewMore1
Copy link
Author

@champnic I've added a sample application which demonstrates the problem. Note I've changed the normal background of the control to Rose so that you can clearly see the control has loaded, but it has not started the WebView2 correctly.

WebView2Sample.zip

@AndyT-MS
Copy link

AndyT-MS commented Mar 19, 2021

Hi folks,

I've discovered a bug which is a probable culprit for at least some of the troubles that are being described on this thread.

Let me start by describing how using WebView2 in a .NET library project should work. Say you've got two projects, MyLibrary and MyApp. MyLibrary uses WebView2, and MyApp uses MyLibrary. For our purposes here MyLibrary is always a .NET project, and MyApp is usually a .NET project but isn't necessarily required to be. Here's some ASCII art showing the general dependency relationships, where => reads as "depends on" or "uses":

MyLibrary => WebView2
MyApp => MyLibrary

The nature of each of those dependencies might vary. Let's talk about them one at a time, with the easy one first. MyLibrary => WebView2 should basically always be a build-time dependency, where MyLibrary's build system (e.g. Visual Studio / MSBuild) references the WebView2 NuGet package.

The MyApp => MyLibrary dependency could be a build-time dependency or a runtime dependency. If it's a build-time dependency then MyApp's project file should directly reference MyLibrary's project file. That's the easiest case to configure because the build system should have all of the information it needs to automatically do everything else for you. On the other hand, if it's a runtime dependency then it has to be possible for a running MyApp process to be instructed to find and load MyLibrary's DLL and interact with it using some pre-defined plugin-style interface that was agreed on in advance (for example, MyApp might expect its plugins to expose a well-known function that displays a window, and MyLibrary implements this function by creating a window which happens to display a WebView2). In this case the build systems for the two projects are completely distinct and have no knowledge of each other; the two projects might even be created and published by entirely different people/companies (as would be the case for third-party Office plugins, for example). Critically, this means that MyApp's build cannot be forced to know or understand anything about MyLibrary's build-time dependencies (i.e. WebView2). Therefore, even if it's the case where MyApp and MyLibrary are created by the same person and could know about each other's builds, we cannot require that as a general solution.

Now let's start to zero in on the case at hand. Here are the two exemplar scenarios:

A) You're a developer responsible for creating/publishing both projects as part of a single system (e.g. you're developing an app but have factored the portion of it that uses WebView2 out into a library for whatever reason). This maps to the case where the MyApp => MyLibrary dependency is at build-time.

B) You're a developer responsible for creating/publishing MyLibrary and expect your customers to use it with a MyApp that's created by someone else (e.g. you're developing an Office plugin). This maps to the case where the MyApp => MyLibrary dependency is at runtime.

To setup scenario A, here's what we expect that you should need to do:

  1. Create a MyLibrary project and configure it to reference the WebView2 NuGet package.
  2. Create a MyApp project and configure it to reference MyLibrary. Because your build system knows everything about both projects at the time they are built, it can automatically make sure that MyLibrary and all of its dependencies land in the output for MyApp. If you build/run MyApp it should just work. In other words, this scenario should not require configuring MyApp to directly reference the WebView2 NuGet package; if doing so happens to resolve your current problem then that's a workaround, not an actual solution.

To setup scenario B, here's what we expect that you should need to do:

  1. Create a MyLibrary project and configure it to reference the WebView2 NuGet package.
  2. Anytime you publish/distribute your project, your package must include both your project's direct output (e.g. MyLibrary.dll) as well as its WebView2 runtime dependencies (e.g. Microsoft.Web.WebView2.Core.dll, etc). When any app tries to load MyLibrary.dll, it must also be able to find the WebView2 DLLs, which in practice means that the WebView2 DLLs should be in specific locations relative to MyLibrary.dll. These are the details that the build system can automatically handle for you in scenario A.

So, if you need to package WebView2's runtime dependencies with your .NET library, then what are those dependencies exactly? (Scenario A people, this will be relevant to you later as well, so keep reading.) At the moment, they are:

  • Microsoft.Web.WebView2.Wpf.dll
  • Microsoft.Web.WebView2.WinForms.dll
  • Microsoft.Web.WebView2.Core.dll
  • WebView2Loader.dll

If you build a MyLibrary project which is configured to reference the WebView2 NuGet package then in general you should find those files automatically copied into the output folder of your library's build (e.g. its bin/Debug folder). However, unfortunately it's not entirely that simple, and here is where we finally start to uncover at least one bug. In particular, the bug I've uncovered which I believe is causing some trouble has to do with WebView2Loader.dll.

WebView2Loader.dll is tricky for a .NET project to use because, unlike the others, it's a native DLL rather than a managed one. Native DLLs can't be built to target "Any CPU" the same way that managed DLLs like MyLibrary can (and do, by default). So, although a customer running an x86 process and a customer running an x64 process can both use the same MyLibrary.dll, they cannot both use the same WebView2Loader.dll. The customer running an x86 process needs a WebView2Loader.dll which was built for x86, and the customer running an x64 process needs a WebView2Loader.dll which was built for x64. This means that if we want to distribute a MyLibrary which is built for AnyCPU then we effectively need to include a WebView2Loader.dll for every possible architecture that MyLibrary will be used in. Now, in practice WebView2 only currently supports three architectures (x86, x64, and arm64), so we're basically talking about including three copies of WebView2Loader.dll when we distribute an AnyCPU MyLibrary. When MyApp is running on the customer's machine and loads MyLibrary.dll, it in turn loads one or more of the Microsoft.Web.WebView2.*.dll files, which then find and use the WebView2Loader.dll that matches the architecture of MyApp.

So how do we keep track of the various separate copies of WebView2Loader.dll? If you build your MyLibrary you'll notice that your output folder doesn't actually directly contain WebView2Loader.dll, but it does contain a folder called "runtimes". If you dig into that folder you'll find various subfolders for various architectures (e.g. "win-x86" for x86). If you dig into the folder for a specific architecture then you should find a "native" folder which contains the WebView2Loader.dll for that architecture. Different architectures will be available in the "runtimes" folder depending on whether your build targeted AnyCPU or some specific architecture like x64 or x86.

From the perspective of distributing MyLibrary.dll, basically you should just consider the whole "runtimes" folder to be a WebView2 dependency which needs to be included in your distribution in the same place as MyLibrary.dll and the other WebView2 DLLs listed above. It is additionally worth pointing out that the name of the "runtimes" folder as well as its internal structure is not designed by the WebView2 team; I believe that comes from the implementation of .NET Core, but I'm not completely certain.

With all of that context, it's finally possible to understand the bug that I've uncovered and I believe is affecting at least some of you on this thread. When you build your .NET library to target AnyCPU, the WebView2 SDK is supposed to include all three versions of WebView2Loader.dll in the "runtimes" folder, but in certain cases (which I'll explain later) it is only including the x86 version. So, to determine if you're affected by this bug, look in your library's output folder for AnyCPU (by default that's the project's "bin/Debug" or "bin/Release" folder, but you may have configured it to be elsewhere), open the "runtimes" folder you find there, and examine the contents. If you only see a "win-x86" subfolder then you're affected; if you see three folders ("win-x86", "win-x64", and "win-arm64") then you're not affected.

You may recall above where we considered two separate scenarios depending on whether MyApp has a build-time dependency or a runtime dependency on MyLibrary. This bug obviously affects the runtime scenario (B), but it also affects the build-time scenario (A). Even if one build system has all of the knowledge necessary to automatically copy all of the correct files all the way to MyApp's output folder, the bug in MyLibrary's build effectively propagates to MyApp. When you build MyApp, the build system automatically builds its dependency (MyLibrary) first and copies its output to MyApp's output. However, MyLibrary's output files are incorrect/bugged, so MyApp's build simply ends up copying the incorrect set of files to its own output, where they continue to be incorrect.

Why does the missing runtime cause the symptoms described here where the WebView2 is simply blank? The code that attempts to find and load WebView2Loader.dll is executed when a CoreWebView2Environment is created. In the typical case, this occurs during initialization of the WebView2 control, which happens either when you set the Source property for the first time or when you call EnsureCoreWebView2Async() (in advanced cases you might explicitly create your own CoreWebView2Environment instance before initializing the control, as @dwatkins-FNA tried above). If WebView2Loader.dll can't be found, which will be the case here, then that code will throw a DllNotFoundException, and obviously the WebView2 will fail to initialize and so continue to appear blank/uninitialized. A complicating factor in the typical case is that the initialization code runs asynchronously in a background Task, which means that you might not automatically be alerted to the exception, and in that case it can obviously be mystifying as to what's going wrong. There are two ways that you can find the exception. First, the Task in question is returned by EnsureCoreWebView2Async, so you can simply await it, at which point the exception will be re-thrown in your code. Second, you can handle the CoreWebView2InitializationCompleted event and examine its arguments, which should show that initialization failed and provide the exception to you.

Now, let's dive into why this bug happens and I'll show you a workaround that you can use until we're able to publish a fixed WebView2 SDK. Basically, the logic in the WebView2 SDK about which architectures to copy into the "runtimes" folder when the dependent project is built for AnyCPU is flawed. The specific case where it is flawed is when the project does not define PlatformTarget. PlatformTarget is the setting which determines which architecture your project's output is built for. At the compiler level the default value for PlatformTarget is AnyCPU, so if your project doesn't define PlatformTarget when you build it then you'll get the compiler's default AnyCPU output. However, the logic in the WebView2 SDK doesn't specifically handle the case where PlatformTarget isn't defined, and the logic which does exist will in that case end up deciding that only the x86 runtime is required. The result is that MyLibrary.dll targets AnyCPU, but only the x86 version of the WebView2Loader.dll is available, which means that when MyLibrary.dll attempts to use WebView2 it will fail if the running app/process is anything other than x86 because no matching version of WebView2Loader.dll will be found.

A reasonable question to ask at this point is how this bug only affects libraries. Turns out that it doesn't only affect libraries, but there's a good reason that it's mostly popping up there. Many of the common types of .NET Framework projects created by Visual Studio explicitly define PlatformTarget in all cases, so this bug doesn't affect those projects. It just turns out that the project types/templates which do not define PlatformTarget happen to generally be library projects, which is why the bug seems to be specific to libraries. Though I have not exhaustively tried all types of .NET Framework projects in Visual Studio, I have found this to be the case with the "Class Library", "Windows Forms Control Library", "WPF Custom Control Library", and "WPF User Control Library" project types. If your library project started off as one of those types then you have a good chance of being affected by this bug (although of course that's not the only possible way for PlatformTarget to end up not being defined).

The workaround is hopefully relatively obvious: update your library's project to explicitly define PlatformTarget to be "AnyCPU" when you build your project under the "Any CPU" configuration. Unfortunately we can't use the Project Properties UI in Visual Studio for this because Visual Studio is smart enough to know the default compiler behavior, so if your project doesn't have PlatformTarget defined then the UI will still go ahead and show you that it has a value of "Any CPU". The only way to be sure here is to examine and edit the project file manually.

You'll need to locate the PropertyGroups in your project which apply to the "AnyCPU" Platform (for both Debug and Release, and any other Configurations you might have created). As an example, here is the relevant portion of the csproj file created by the "Class Library (.NET Framework)" project type/template in Visual Studio.

  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
    <DebugSymbols>true</DebugSymbols>
    <DebugType>full</DebugType>
    <Optimize>false</Optimize>
    <OutputPath>bin\Debug\</OutputPath>
    <DefineConstants>DEBUG;TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
    <DebugType>pdbonly</DebugType>
    <Optimize>true</Optimize>
    <OutputPath>bin\Release\</OutputPath>
    <DefineConstants>TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>

All you need to do is add <PlatformTarget>AnyCPU</PlatformTarget> inside both/all such PropertyGroups. Here's a corrected version of the above snippet (note that the order of the children in each PropertyGroup doesn't matter, I just arbitrarily chose to insert PlatformTarget at the top of each group):

  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
    <PlatformTarget>AnyCPU</PlatformTarget>
    <DebugSymbols>true</DebugSymbols>
    <DebugType>full</DebugType>
    <Optimize>false</Optimize>
    <OutputPath>bin\Debug\</OutputPath>
    <DefineConstants>DEBUG;TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
    <PlatformTarget>AnyCPU</PlatformTarget>
    <DebugType>pdbonly</DebugType>
    <Optimize>true</Optimize>
    <OutputPath>bin\Release\</OutputPath>
    <DefineConstants>TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>

After you make this change and build your library again (maybe Rebuild for good measure) you should find that all three architectures exist in the output "runtimes" folder. If you have an application which references that library (i.e. scenario A above) and you build the application again then you should also see that all of those "runtimes" subfolders are copied to the application's output as well. Now that you have all of the architectures in the "runtimes" folder the app/library should be able to find and load the appropriate WebView2Loader.dll, resolving the problem.

I hope that this helps you understand a current bug, diagnose whether it's affecting you, and if so work around it until we can publish a fixed version of the WebView2 SDK. If you believe you're affected but the workaround isn't resolving the issue then please reply back and provide any additional information you can discover. If you've been reporting a problem in this thread but turn out to not be affected by the exact bug I described, then I hope that this information is still helpful in investigating your problem (especially the part about how to find exceptions that occur during initialization). In that case, please report anything new that you discover about your issue and we'll try to help get that sorted out as well, though if it turns out that you have all of the expected WebView2 files in the expected locations in your project's output folder as described above -- i.e. your issue appears to be unrelated to missing WebView2 files in your project's output -- then please consider starting a new thread about it, perhaps referencing this one as appropriate.

@dwatkins-FNA
Copy link

@AndyT-MS thanks for the explanation! I do only have the X86 in the runtimes folder as you said. I will try the workaround next week and report back.

@AndrewMore1
Copy link
Author

@AndyT-MS Thank you, that makes a lot of sense. It also explains why I was sometimes able to get a sample application to work correctly and then break, I more than likely changed the arch and didn't realize it. Your explanation of the directory structure also explains why my copying the DLLs into the runtime folder for my app wasn't sufficient either.

Finally while I appreciate your explanation for why the exception was hidden, I'm curious why the solution doesn't involve making sure the exception is propagated further up the stack. The lack of ANY feedback as to what was failing is one of the most frustrating parts of dealing with this defect. I saw no logs, no error messages, no exceptions in the console, no messages anywhere. This seems like such a fundamental and fatal exception I would expect a much more visible display of the problem, even if it only occurs in the debugger.

@dwatkins-FNA
Copy link

I made the change to the platform and it did make all of the runtimes and I did not get the missing dll error but I did get a new one.
1st I tried just setting the source but that still does not work. Then I tried the CoreWebView2Environment.CreateAsync() workaround and get this error now.

System.UnauthorizedAccessException: Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED)) at System.Runtime.InteropServices.Marshal.ThrowExceptionForHRInternal(Int32 errorCode, IntPtr errorInfo) at System.Runtime.InteropServices.Marshal.ThrowExceptionForHR(Int32 errorCode) at Microsoft.Web.WebView2.Core.CoreWebView2Environment.<CreateAsync>d__3.MoveNext()

This is the code and exception in debug.
image

@AndyT-MS
Copy link

@dwatkins-FNA Access denied errors are usually the result of the User Data Folder being in a location where the browser/runtime process(es) doesn't have the necessary permissions to create/access it (the User Data Folder is where the browser/runtime stores caches, history, cookies, etc). If you don't specify a location for the User Data Folder then one will automatically be created in the same folder as your EXE. In development environments that often works fine, but maybe not always. You can specify the location to use for the User Data Folder as one of the arguments to CoreWebView2Environment.CreateAsync; for troubleshooting, try specifying a location that is for certain accessible, like maybe a folder in your Desktop or Documents. For production, you'll want to consider where that folder lives on your end user's machines and whether/how your deployment mechanism interacts with it. For more info about User Data Folder:

@AndrewMore1 I'm really glad that my explanation was helpful. Absolutely fair criticism regarding the lack of notification about the failure. We'd definitely like to improve that, and knowing what sorts of things you looked for is helpful, thanks!

Regarding why the exception doesn't propagate further up the stack into your code, the trouble is that initialization is a long and expensive operation so we don't want setting the Source property to block and wait for it to finish. Therefore, setting Source simply triggers the operation asynchronously, and that operation might not even have started running yet (much less caused an error) by the time the Source setter returns, and we obviously can't return a corresponding Task from the Source setter either (like we can from the EnsureCoreWebView2Async method). So we use the CoreWebView2InitializationCompleted event to inform you about the results of the operation. This actually mirrors navigation, which is also an asynchronous operation and also generally reports its status and results via events (and also happens to be triggered by setting Source). The lack of error propagation is effectively a downside/tradeoff of having async operations like initialization and navigation wrapped up in a property, though the upside is that the property can be set via XAML and used with data binding and such. Anyway, none of this is meant to say that we can't somehow do better about notifying callers of initialization errors, but you were curious about the lack of exception propagation, so I wanted to explain a little more. If you or anyone has suggestions for a way to propagate exceptions from Source without blocking or returning a Task then I'd be happy to hear them. :)

@AndrewMore1
Copy link
Author

@AndyT-MS I appreciate your explaination, and I think you've probably got a better handle on the ins and outs of C# than I do. I've worked with it off and on over the years, but I'm far from a subject matter expert and my primary focus in recent years has been javascript. So I believe you're probably correct for why the error doesn't move further up the stack. I also appreciate the suggestion of checking the CoreWebView2InitializationCompleted for more information, that's helpful.

However, I must admit I don't understand why a debug message for the catastrophic failure cannot be output to the debugger Output window.

https://docs.microsoft.com/en-us/visualstudio/debugger/diagnostic-messages-in-the-output-window?view=vs-2019

@AndyT-MS
Copy link

@AndrewMore1 Writing to the debugger output window is definitely a possibility, and one that I'm certain we'll consider when we try to improve the reporting of initialization failures like these.

@amaitland
Copy link

There are a few other possibilities that might be contributing to problems loading WebView2Loader.dll

  • Issue Default platform target in Properties window is incorrect dotnet/sdk#1560 where PlatformTarget defaults to x86 for newer SDK Style projects

    • I expect your .targets file will have problems here as it's evaluated on project load (before build)
    • Adding a <PlatformTarget>AnyCPU</PlatformTarget> to the project file is an easy fix for this one, same fix as above.
    • If you add an Before and After build targets and output PlatformTarget in each you'll see that is starts out being x86 and after build is AnyCPU and the resulting exe being AnyCPU (In some cases, if an x86 dependency is detected it'll generate an x86 exe).
  • Issue Building project with CefSharp fails to build until I restart visual studio dotnet/project-system#4158 which isn't actually CefSharp specific, any project including Content files using a .targets file when using packages.config may have this problem if the project file is loaded before the Nuget restore has created the files on disk. I'd be testing with VS2017 with a clean checkout.

  • The Nuget package doesn't appear to support buildTransitive so the assets don't flow through to consuming projects as some might expect (at least the nupkg file I looked at didn't appear to have a buildTransitive folder)

Full disclaimer this is a passing comment and I've done exactly no testing in the context of WebView2, it may work perfectly in all scenarios. Feel free to ignore this comment 😄

@jdaless
Copy link

jdaless commented Apr 8, 2021

I believe this is a problem I am having as well, but it is only happening to some of the test users we're deploying to. Like @m-sterspace I'm building an add-in to an Autodesk app so I am unable to test it as a standalone application. I have attempted many of the suggestions here (resizing the window, using Navigate instead of setting the Source, and making sure all three platforms had the loader .dlls) but am still having no luck. I am also unable to isolate why it's only happening to some of my users. They're all using the evergreen bootstrapper, so that shouldn't be an issue. Are there other environment scenarios that could be throwing this off?

@champnic
Copy link
Member

champnic commented Apr 8, 2021

@jdaless That sounds like it may be a separate issue from this one. Do you want to open a separate issue with more details so we can try to help? Off the top of my head I would check where your user data folder is getting created, and if that folder has write permissions: https://docs.microsoft.com/en-us/microsoft-edge/webview2/concepts/userdatafolder

@AndrewMore1
Copy link
Author

@AndyT-MS I've done some more digging based on your detailed analysis above. I have implemented the changes suggested in the project file, and built another sample application to illustrate the problem. From what I can tell, your suggests does result in the runtime libraries being correctly placed into the working/build directory, but it does not resolve the issue of the control showing the correct information.

I've attached a copy of the sample code showing this problem. In this case as a sanity test I've included two WebView2 controls side by side, one of which is directly created in the application, the second in a library. The library based version still fails to work, while the simpler example works correctly.

Webview2LibraryProblem.zip

@AndyT-MS
Copy link

AndyT-MS commented May 3, 2021

@AndrewMore1 Thanks for the easy repro app, that helps a lot. Here's how I dug into it and what I found.

At first, the app didn't work at all: neither WebView2 appeared, and the Output window showed exceptions loading the WebView2 DLLs as well as an error about an invalid binding. In MainWindow.xaml the library control is added to the page like this:

<hst:WV2Ctrl x:Name="WebCtl" Height="{Binding ElementName=Holder, Path=ActualHeight, Mode=OneWay}" Margin="0,0,382,0" ></hst:WV2Ctrl>

However, there's no element named "Holder" for that binding to bind to, so I just removed the Height property from that control and let it use the default. That not only fixed the invalid binding error, but also the DLL loading exceptions.

After that, when I ran the app it was as you described: the WebView2 referenced directly from the app correctly showed Bing, but the WebView2 inside the library control didn't appear at all. Next thing I did was to set a breakpoint to go off when I resized the window so that I could easily examine the state of both WebView2 controls. Somewhat unexpectedly, even the WebView2 inside of the library control had a valid CoreWebView2 property (and CoreWebView2Controller property, which is private), which means that it had initialized successfully. After that I subscribed to some initialization, navigation, etc events on the WebView2 inside the library control to examine what it was doing, and I saw initialization and navigation events all happening correctly. So, the WebView2 itself seemed to actually be working, but why wasn't it rendering?

I figured that out by poking around in the live WPF visual tree. I noticed that the automatically calculated height of the WV2Ctrl was reasonable (the full height of the window), but the height of the WebView2 control inside of it was zero, which would certainly explain why it wasn't rendering anything. The problem turned out to be the StackPanel that the WV2Ctrl is using for its internal layout. Looks like StackPanel only gives its child controls the minimum amount of space necessary in the stacking direction, and WebView2 doesn't have any minimum size requirement, so the StackPanel allocated zero room for it in the stacking direction (which was vertical/height in this case). Here are a few of the StackOverflow questions that I referenced to figure that out, and their answer always seems to be to use some other layout mechanism (usually Grid or DockPanel).

When I followed that advice and changed the StackPanel to a Grid then the app worked as expected, showing the two WebView2 controls side-by-side (with some overlap due to the specified margins). So I think this is just a problem in the XAML used by the library control in your repro app. If Visual Studio's template for a new UserControl uses StackPanel by default then this is definitely a potential "gotcha" that would be easy for anyone trying to place a WebView2 inside of a UserControl to run into. There's potentially an argument to be made that WebView2 should have a non-zero minimum size in order to prevent this kind of thing, but I suspect that there are good arguments against that as well, and I haven't yet done any research or thinking on it either way.

I hope this resolves your problem and unblocks your work!

@m-sterspace
Copy link

I believe this is a problem I am having as well, but it is only happening to some of the test users we're deploying to. Like @m-sterspace I'm building an add-in to an Autodesk app so I am unable to test it as a standalone application. I have attempted many of the suggestions here (resizing the window, using Navigate instead of setting the Source, and making sure all three platforms had the loader .dlls) but am still having no luck. I am also unable to isolate why it's only happening to some of my users. They're all using the evergreen bootstrapper, so that shouldn't be an issue. Are there other environment scenarios that could be throwing this off?

For the record since I couldn't get WebView2 working, I switched to Electron and just bundled an Electron application and communicated back and forth with it using IPC. If you want to go down a similar path, let me know and I can share some code for it.

@jdaless
Copy link

jdaless commented May 4, 2021

@m-sterspace, sorry for not updating here, but @champnic was correct, the issue was with user data. Instead of keeping user data next to the executable, which is the default, we keep it with the plugin where we know the user has permission to write since they installed the plugin.

@AndrewMore1
Copy link
Author

@AndyT-MS Thank you again Andy. I made the changes you suggested and it worked correctly. I am puzzled that the sample app worked for me with the erroneous binding element, but it worked before I uploaded it.

As you might guess this is my attempt to provide a simplified version of a much larger, more complex app, which was based on the original WebBrowser control which hosted IE11. The use of the element StackPanel works correctly with that control, in pretty much the same layout as the sample app, while it does not with the replacement control. I also don't claim to know what the correct setting should be for this panel, but I suspect you're going to get a lot of people doing something similar to what I've done, do a drop in replacement, and not understanding why it's not working.

Also thanks for the recommendation of using the WPF Tree visualizer. As I said before, C# is something I haven't used much for about 10 years, getting pulled off into SWT/Jave and then Angularjs/Javascript, so it's nice to learn some of the tricks along the way.

@AndyT-MS
Copy link

AndyT-MS commented May 4, 2021

@AndrewMore1 I'm glad that my changes worked for you as well, and I'm happy that I could provide some tips along the way. Thanks for the extra context that this StackPanel-based layout worked as expected with WebBrowser. I agree that there's probably a lot of potential for folks to run into this when they try to switch from WebBrowser to WebView2, which will likely make it worth some investigation to understand why/how WebBrowser works fine in that situation, and whether or not WebView2 could be updated accordingly.

@AndrewMore1
Copy link
Author

@AndyT-MS One other fact that might be helpful. The large sample app I've been trying to get working with WebView2 but haven't posted was working correctly with an earlier version of the WebView2 control. This was in the October/November time frame, so not only do I believe the StackPanel 0 height to be a bug, I believe it's also a regression from previous versions of the WebView2 control for WPF.

@tkolb-recom
Copy link

@AndyT-MS

The workaround is hopefully relatively obvious: update your library's project to explicitly define PlatformTarget to be "AnyCPU" when you build your project under the "Any CPU" configuration.

That's exactly what helped to overcome the situation for me too. I can't thank you enough.

@pmaytak
Copy link

pmaytak commented Jul 1, 2021

@AndyT-MS
In my case, the workaround doesn't work only for net472 projects, when console test app consumes library as a NuGet package.

Repro: WebView2LoaderRepro.zip

  • WebView2Library references WebView2 SDK NuGet.
  • WebView2Library does specify AnyCPU PlatformTarget explicitly.
  • WebView2Library is packed as a NuGet.
  • WebView2App references WebView2Library as a NuGet.

If App targets net472, I see different behavior depending on the PlatformTarget element in the App csproj.

  • None specified - BadImageFormatException
  • AnyCPU - DllNotFoundException
  • x86 or x64 - everything works

If App targets netcoreapp3.1, then everything works, even if the app doesn't explicitly specify PlatformTarget.

Runtime: 91.0.864.59
SDK: 1.0.864.35

@sidb89
Copy link

sidb89 commented Aug 3, 2021

@AndyT-MS
My situation is scenario B (runtime)
MyLibrary (.NET) => WebView2 Nuget
App (.exe) => MyLibrary

My App does not know about MyLibrary's existence.

I get the following error:

image

Following is the stack trace:

image

I have tried the steps mentioned:

  1. For App, show the paths for the WebView2 dlls
  2. Add platform target for AnyCPU, x86 and x64

They do not work. Moreover, the user data folder is not being generated in the default location.

Would be very grateful for any insight on the matter.

Refer also: #1601 (comment)

Thank you

@AndyT-MS
Copy link

@pmaytak - sorry that it has taken a while to find the time to follow up. Thanks much for the easy repro! I was able to replicate your results, and after some additional investigation there seem to be two additional considerations happening which differentiate that case from my original work on this bug.

First, as you point out your case is using the library as a NuGet package. The second extra consideration is that your app and library are targeting .NET Framework (e.g. net472) from a new SDK-style csproj. Until I investigated your repro I didn't think that either of those considerations should matter, but it turns out that they both have an effect on how WebView2 creates files in the app's build output. It also unfortunately turns out that my eventual fix from my original investigation (which should be in the next release) doesn't solve the problem with regard to either of these new considerations.

I think that #1418 is another example of the bug caused by using WebView2 in an sdk-style csproj which targets .NET Framework. To make the tracking easier, I'm going to use that bug for comments and investigation regarding both of these new considerations. I've posted a comment there with some additional detail regarding my current findings.

Thanks for your help in finding these problems. I'm sorry that I don't have an easy fix or workaround for them yet, but hopefully the additional information will still be helpful for you in the meantime.

@champnic
Copy link
Member

The main issues here are fixed in SDK package 1.0.1056-prerelease, and should be available in the upcoming release SDK. Thanks!

@gileli121
Copy link

I found out that when the program path contains non-English characters, you will get this error

@gileli121
Copy link

@champnic
And the error not fixed also in 1.0.1056-prerelease when running the program under folder with path that contains non-English characters

@AndyT-MS
Copy link

@gileli121 Could you please post that as a new issue? That will make it a lot easier for us to track since this one is closed and it sounds like your new one has a very different cause despite having similar symptoms. It would also be helpful if you could include more information about the error or incorrect behavior that you're seeing because this thread includes reports of several different errors so I'm not sure which one you're referring to.

@Avdam
Copy link

Avdam commented Jan 17, 2024

I got related problems:

  1. Wpf getting started example worked ok with .net5
  2. Implementing same code in my .net 4.8 => BadImageFormatException
  3. followed solution in this thread (although I didn't use a library but an exe) I got also the x64 binaries
  4. Now I got the problem that await webView.EnsureCoreWebView2Async does not complete
  5. Tried to use the CoreWebView2InitializationCompleted event and Yes it worked!!!

Remaining question why did the await webView.EnsureCoreWebView2Async not work?

@champnic
Copy link
Member

@Avdam It might be easiest to open a new issue for this problem, including version info, repro steps, sample app, etc.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working tracked We are tracking this work internally.
Projects
None yet
Development

No branches or pull requests