Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
Markdown Monster - The Markdown Editor for Windows

Taking the new Chromium WebView2 Control for a Spin in .NET - Part 1


:P
On this page:

This is Part 1 of a 3 part post series:

The Microsoft Edge WebView2 control is a new Chromium based Web Browser control that aims to replace the Internet Explorer based WebBrowser control that's commonly used in WPF and WinForms. Third party Chromium based controls (like CefSharp) have been available for a while, but for me these complex controls have been a struggle to integrate and operate efficiently and reliably in the past especially in WPF. So I was exited to hear that Microsoft was creating a WebView control in tandem with the new Edge Chromium browser that recently was released, even if these developments took a lot longer than originally anticipated.

The WebView2 control is Microsoft's entry in this space and they are throwing their weight behind the Edge Chromium browser with application level features for C++, WinForms and WPF applications. Although the .NET bits are still in preview, performance and stability are very promising at this point.

After some initial experimenting with features and performance, I decided to integrate the WebView2 initially in Markdown Monster as an optional preview browser addin to support Chromium rendering. While this process took some effort and finding out a lot about how the control works and what doesn't, overall the end result was very encouraging as I managed - with a bit of effort - to duplicate most of the interop functionality that the preview interface required. I'll talk about how I went about this integration and some of the challenges I ran into in Part 2.

In this installment I'll talk about the control's features, and how to use the control to display content and how to get it installed with your applications. Part 2 focuses on interaction with content and .NET → JavaScript interop integration.

Note that the WebView2 is a very different control than the original WebView control, which existed briefly and was based on the classic Edge engine. The original WebView control is now obsolete and longer supported.

Do you need the WebView2?

So coming from a WebBrowser control, the WebView2 control seems like a shoo-in especially for new applications. It provides fast, modern rendering that's (optionally) always updated to the latest Chromium features, which is the browser engine that most mainstream browsers now all use for HTML rendering - the big exception being FireFox.

Before we dive into the WebView, first a little history on where we are coming from...

The old Internet Explorer based Web Browser Control

The WebBrowser control in WPF and WinForms is a built in control, and although it's very old and based on Internet Explorer, it can render most HTML 5 content and many relatively modern JavaScript libraries.

For example, in Markdown Monster I use the IE WebBrowser control very effectively to render the JavaScript based editor and Web preview browsers and that just fine with excellent performance for example. Both of these web pages/applications are fairly complex, and use JavaScript to .NET Interop and the old control actually works just fine for both of these scenarios.

But, where the old control starts failing is for content that uses modern JavaScript that is ES2015 or newer which the IE JavaScript engine doesn't support. This used to be no big deal because until very recently even most Chromium browsers hadn't implemented many ES2015 features. But as these features have become common in browsers, the tide is slowly turning and more and more JavaScript libraries are now embracing the new JavaScript features which Internet Explorer has never and will never support.

The writing's on the wall - eventually most newer JavaScript content won't run in the old WebBrowser control.

Still, if your application only renders common HTML5 content - especially if you're appliocation generates it - and doesn't rely on ES2015+ features you can continue to use the WebBrowser control as a low impact solution to displaying HTML content. If the WebBrowser control works with your content, it's much easier to distribute in your application than the WebView2 control.

WebView2 to the Rescue?

The WebView2 control (and also others like CefSharp) solves the modern feature problems because it provides:

  • A Chromium based Web browser control
  • Evergreen Chromium features that are always up to date

Chromium is the open source core engine used in most modern Web Browsers - Chrome, Edge (Chromium), Brave, Vivaldi, Opera and many other small browsers all use the same core Chromium based browser engine in their desktop browsers. And now you too can easily use it in your .NET Desktop applications. And the WebView2 uses that very same base Chromium Browser engine plus some Edge specific shell embellishments.

Like the WebBrowser control, the WebView2 works both in .NET Framework (which is what I'm using in MM) and for .NET Core 3.x and 5.0.

On the downside, the WebView2 requires a separate WebView2 Browser Runtime that has to be explicitly installed on a target machine. Even if the new Microsoft Edge Chromium browser is already installed on the machine, a WebView2 Runtime has to be separately installed. You as the application developer has to make sure it gets onto a target machine. This means either bloating your installation, or downloading and installing as part of your installation or application startup, none of which are pleasant solutions.

There are also significant differences and a few complications getting host/control interop to work. Having worked with an application that has deep Web Browser integration, switching to the WebView2 control brought up a few issues:

  • Much more limited host->JavaScript interopability
  • No Keyboard forwarding from browser to the host container
  • Some (pre-release) instability around interop calls (mostly back to .NET)

I'll talk more about these specific issues in Part 2. It's not clear whether some of these are just related to the pre-release blues, or whether they are by design. Discussions on some of these topics in the Github Issues are not always clear on direction.

WebBrowser Control vs. WebView2

To summarize: Here's a comparison overview of pros and cons summary for the WebBrowser and WebView:

WebBrowser Control

Pros

  • Works with most HTML 5 content
  • Native controls for WPF/WinForms
  • Low resource usage
  • Excellent performance
  • Built-in - no additional files to distribute
  • Excellent interop support

Cons

  • No JavaScript ES2015+ Support
  • No support for newer CSS features (post CSS 3.0)
  • Dev Tools not available
  • Very difficult to debug HTML/JavaScript content

WebView Control

Pros

  • Supports latest Features
  • Excellent performance
  • Evergreen browser support (updates to latest)
  • Easy debugging using Browser DevTools

Cons

  • Requires a runtime installation
  • High memory usage of Chromium Processes
  • Setting up Interop takes some work
  • Interop type limitations (JSON messages required)
  • No Browser → Host Accellerator Key Mapping
  • Some pre-release Interop quirks

Using the WebView2 Control

Let's take a look on how to use the WebView2 control. I'll use WPF here, but usage in Windows Forms is going to be similar other than the control host embedding.

Using the control involves a few steps:

  • Ensure the WebView2 Runtime is installed
  • Add the Microsoft.Web.WebView2 NuGet Package
  • Add the WebView Control to your Form, UserControl or other Container Control
  • Set up the WebView Environment during Startup
  • Set the webView.Source property to navigate
  • Or use webView.NavigateToString() to display HTML content from string

The WebView2 Runtime

Before you can use the WebView2 control you need to make sure the WebView Runtime is installed. This runtime is separate from the Microsoft Edge browser, and can in theory run completely independent of Edge and if Edge is not installed at all.

This means you have to either:

At the end of the article I'll discuss some strategies for distributing the runtime with your application. But for now, for experimenting just install the Runtime or Canary build to play with the control.

Add the NuGet Package to your Project

The WebView2 .NET integration that provides the WPF and WinForm controls, is shipped as a NuGet package that you can add to your WPF or WinForms project. The NuGet Package supports both WPF and WinForms versions of the control so regardless which you use and it works both in Full .NET Framework (NETFX) as well as .NET Core.

To install the package:

> Install-Package Microsoft.Web.WebView2

I'll use WPF here to demonstrate but the process in WinForms will be similar except for adding the control to the Form/UserControl/Container - the control interface is the same.

Add to your Form or Container

Once in your project the control shows up in the Control toolbox and you can drag it into the WPF or WinForms designer onto your form/user control/container.

In WPF the result looks like this if you want to do it with XAML only:

<Window
	xmlns:wv2="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf"
/>
...
<DockPanel x:Name="DockWrapper" Grid.Row="2" Panel.ZIndex="2" >
	
    <wv2:WebView2 Name="webView"  Visibility="Collapsed" 
                  Source="{Binding Url}"
    />
</DockPanel>

Initializing the WebBrowser

Next we need to initialize the WebView by explicitly setting up the WebView environment.

The WebView environment controls all the related browser state that is typically stored on disk as a folder structure containing things like cookies, browser history, localstorage etc. The key thing is to ensure that this folder is created somewhere where the application has rights to write files. By default the folder is off application root folder, which may not actually have rights to write data if it sits in say \Program Files\My App.

So it's important to specify a specific folder that has read/write/create access so files and folders can be created and written. If the folder doesn't exist it's automatically created during the control initialization. If it does exist the existing data is re-used, so that the environment data persists across multiple browser control launches.

I use an application named temp folder sub-folder for example c:\temp\tmpFiles\MarkdownMonster_Browser. Here's what that looks like in the configuration code:

// called from Window Constructor after InitializeComponent()
// note: the `async void` signature is required for environment init
async void InitializeAsync()
{
   // must create a data folder if running out of a secured folder that can't write like Program Files
   var env = await  CoreWebView2Environment.CreateAsync(userDataFolder: 	Path.Combine(Path.GetTempPath(),"MarkdownMonster_Browser"));
   
    // NOTE: this waits until the first page is navigated - then continues
    //       executing the next line of code!
    await webView.EnsureCoreWebView2Async(env);

   if (Model.Options.AutoOpenDevTools)
       webView.CoreWebView2.OpenDevToolsWindow();
   
   // Almost always need this event for something    
   webView.NavigationCompleted += WebView_NavigationCompleted;
   
   // set the initial URL
   webView.Source = new Uri("https://test.editor/editor.htm");
}

private void WebView_NavigationCompleted(object sender, Microsoft.Web.WebView2.Core.CoreWebView2NavigationCompletedEventArgs e)
{
    if (e.IsSuccess)
        Model.Url = webView.Source.ToString();

    if (firstload)
    {
        firstload = false;
        webView.Visibility = Visibility.Visible;
    }
}

This InitializeAsync() method is a method I created and call from the non-async form constructor:

// in ctor
_ = InitializeAsync()

This gives me a method with an async signature even though the method is actually not initiated of an initial async request, but rather is of the fire and forget variety. This is kind of an odd method - the call to:

// NOTE: this waits until the first page is navigated - then continues
//       executing the next line of code!
await webView.EnsureCoreWebView2Async(env);

waits until the first page is navigated before continuing execution. IOW, this code is truly non-sequential so when debugging you get what feels like strange behavior where the code doesn't execute the next line but continues on another thread until the WebView is internally navigated and the WebView is 'ready'.

This behavior is to ensure you can safely configure server settings that rely on the browser having properly initialized and that the CoreWebView2 property is available to be accessed. Use this method to set any environment settings like the render path, or a virtual local folder mapping (more on that later).

Common Navigation and Content Events

As you'd expect the WebView2 browser has a full compliment of events, but I want to highlight 3 of the more common events that you need to work with, especially if you interact with the content inside of the browser control:

  • NavigationStarting - useful for intercepting and re-routing links and resources
  • NavigationCompleted - useful for initializing Interop components and interaction with DOM
  • CoreWebView2.DOMContentLoaded - determines when the document is ready for inteaction
  • ContentLoading - potentially taking over content

Probably the most common events you need to capture have to do with determining when a new page has been loaded and when the DOM is ready to be accessed. NavigationCompleted and (in the latest previews of WV2) CoreWebView2.DOMContentLoaded which serves similar but slightly differently timed notification in the Web page load cycle.

NavigationCompleted fires once the main page has been navigated meaning the URL is established and the document has started loading. This is good place to hook up things like Interop objects and potentially set up interop operations. In most cases this event also works as a DOM ready handler even though CoreWebView2.DOMContentLoaded is more specifically designed for that scenario.

The difference between the two is one of timing in that DOMContentLoaded fires once the main page document has completed loading and has been parsed (but resources like CSS/async Script/images may have not loaded yet). NavigationCompleted fires basically as the document starts loading.

DOMContentLoaded has been added only in recent previews ( .720+), so I've been using NavigationCompleted for all my document and interop intitialization since that's all that was available and that has worked just fine. However, going forward with v.720 and later it's probably safer to CoreWebView2.DOMContentLoaded to ensure the document is fully loaded.

I'll continue to use NavigationCompleted in these examples here, because that's the only thing that available in the current downloadable release WebView2 Runtime.

The following example from MM demonstrates calling an initialization JavaScript function that initializes the preview browser in MM for example:

private async void WebBrowser_NavigationCompleted(object sender, CoreWebView2NavigationCompletedEventArgs e)
{
    DotnetInterop = new WebViewPreviewDotnetInterop(Model, WebBrowser);
    JsInterop = DotnetInterop.JsInterop;

    // Make Dotnet Interop available in loaded document
    WebBrowser.CoreWebView2.AddHostObjectToScript("mm", DotnetInterop);

    // Call JavaScript method to initialize previewer using above object
    await JsInterop.InitializeInterop();
}

There are many additional operational and shell events on the WebView2 control, and many more document centric events that map to HTML DOM events on the CoreWebView2 control. I'll talk more about these in Part2.

UI and Browser shell events as well that you can trap, but the above 3 events are critical for working with browser navigation and accessing browser content from .NET.

At this point the WebView is ready for navigation and there are a few ways you can do that now:

  • Set or bind the .Source Property
  • Call .NavigateToString()

Source Property Assignment and Binding

The webView.Source property is of type Uri, so in code:

// online url
webView.Source = new Uri("https://markdownmonster.west-wind.com");

// local url
webView.Source = new Uri(Path.Combine(Environment.CurrentDirectory, "Editor/editor.htm"));

Or you can bind a string from a Model

public class MainWindowModel : INotifyPropertyChanged
{

	public string Url
	{
	    get { return _Url; }
	    set
	    {
	        if (value == _Url) return;
	        _Url = value;
	        OnPropertyChanged(nameof(Url));
	    }
	}
	private string _Url = "about:blank";
}

...
// assign model value as string for binding
this.Model.Url = Path.Combine(Environment.CurrentDirectory, "Editor/editor.htm");

and then bind the Url:

<wv2:WebView2 Name="webView" Source="{Binding Url}" />

Using a string tends to be more convenient as you also bind the value into a text box.

My example page is a subset of the Markdown Monster editor because it has a rich object model that I can interact with for some of the examples here.

Here's what the rendered sample form looks like:

Url navigation will open the page from the specified local file and all resources are rendered in related format.

String Navigation

You can also navigate the browser directly to string content. This work best if the content to render is fully self contained and doesn't have external references to related resources.

You can specify a full HTML document or a fragment like this:

private void NavigateToString_Click(object sender, RoutedEventArgs e)
{

    // To map related content use webView.CoreWebView2.SetVirtualHostNameToFolderMapping()
    // to map a domain name to a path that's part of the installation folder.
    webView.NavigateToString(@"<h1>String Navigation</h1>
<p>
This content generated to display as a string.
</p>
<p>
This works only if you have self-contained content, or you link Web Resources or you explicitly implement
</p>
");

}

The render behavior is the same as you'd see in an actual browser - a fragment basically gets an emtpy <html><body> wrapper around the document.

But be aware that in order to render strings you either need a self-contained HTML document that has no external links and references or that uses external HTTP URLs for external links and references. This means Web based https:// links in its simplest form.

Tip: Domain to Folder Mapping

One of the most common use cases for using a Web control in a desktop application is to use generated content from local files, or map a local folder and treat it as a 'web site' in the browser.

To help with linking local file resources, the CoreWebView2 API includes a thoughtful mechanism to map a folder to a virtual domain name:

// During initialization
wwebView.CoreWebView2.SetVirtualHostNameToFolderMapping(
                    "test.editor", "Editor",
                    CoreWebView2HostResourceAccessKind.Allow);

This allows you to assign a local folder to a domain name, which you can then use as a base URL for referenced resources or links.

Once you've set up this domain to folder mapping you can now use a 'real' URL to navigate to a page that lives in the Editor folder relative to the application's startup folder. Using the test.editor virtual domain you can then navigate like this:

webView.Url = new Uri("https://test.editor/editor.html");

The document loaded then appears to load from that domain and all relative links also load from that virtual domain.

But more importantly once the virtual mapping exists you can reference resources in the mapped folder from within any HTML document. For example to link a related image you can use:

<img src="https://test.editor/fileicons/csharp.png" />

This works both in a document loaded from the same domain or for a file loaded from a local file or another domain.

This is obviously very useful if you're rendering content from a string, but it also works if you are rendering HTML templates to local files and then load them into the browser. It also works to mix local content into Web content without running into browser security zone issues.

Note that you can specify the CORS access rules for the resource - although I was only to get this to work with CoreWebView2HostResourceAccessKind.Allow - anything else would refuse to load the resource.

Kudos to Microsoft for this feature! This is pretty thoughtful and powerful and I plan on using that in the future once I upgrade my code base to the WebView2. I can foresee mapping multiple file locations (templates and resources for example) locally out of different folders and more importantly treating all content in the browser as HTTP content. Nice!

WebView2 Runtime Installation Options

As mentioned at the beginning, Runtime installation is a concern with the WebView2 control because there's no guarantee that a WebView2 runtime is installed - even if the latest version of Microsoft Edge is installed. A separate WebView2 runtime is required and it has to be explicitly installed on a machine and the runtimes are large at about a 100 meg footprint.

If you run an application that has a WebView2 control and you don't have the WebView2 Runtime installed you get this runtime error:

Notice that this error occurs even though Edge is installed - you need a separate WebView2 Runtime that is installed either explicitly or as part of an Edge Canary install.

Not only do you need to have a runtime installed, but you have to have the right version that is compatible with the control/library that you are using. The runtimes are backwards compatible so you need a current or newer version of the Runtime relative to the SDK/Controls.

What this means is that you have to ensure that library and the runtimes stay in sync over updates of your application. It also means that you probably want to check as part of your application that you have a runtime installed that is compatible with your controls (see example below).

Downloading the Runtimes

Before you can worry about installing you need to be able to get the runtimes installed even on your dev machine. The runtimes are available for download from here:

Microsoft Edge WebView2 Download Page

There are three different download options.

  • Evergreen Bootstrapper (~2mb)
    A relatively small executable that checks for the latest version of the runtime and downloads the full runtime and installs it. It needs an Internet connection to work. The bootstrapper is small at ~2mb, so you can add it to your application's installer or ship it as part of an application and invoke it at runtime. Evergreen means the browser installed is always the latest version.

  • Evergreen Full Installer (~80mb)
    Same as the bootstrapper above except the installer files are self-contained in the installer, so no Internet connection is required. This installer is obviously much larger and not so easily packaged with an (otherwise small) application.

  • Fixed Version (~120mb)
    This package contains all the files needed that you can ship with your application and install side by side with your application, either in a sub-folder or somewhere else. This allows you to make a fully self-contained application with the runtime pre-installed.

Canary Build Preview WebView2 Runtimes

You can also 'get' a WebView2 runtime by installing any of the Edge Canary Previews builds which include the latest WebView2 Runtime. If you install both Canary and an explicit runtime, the installed WebView2 Runtime takes precedence over the Canary WV2 runtime. The only way to get preview runtimes installed is by running Canary Builds.

Installing with an Installer

One approach you can take is to distribute your application with one of the two EverGreen installers as part of your Installation program. You can launch the installer as part of your setup program and let it check whether a runtime is already installed and update it if the version is newer.

When you start your application you need to specify where the WebViewRuntime files can be found and where the User Environment is stored for common browser state like cookies, localstorage, user profiles etc. If you don't specify a location for the latter, the WebView environment folder is created as sub-folder of the application's start directory. If you're installing into Program Files that'll be a problem because the current user likely won't have write access in the Program Files file tree.

So you'll want to make sure that during startup you specify a WebView2 Runtime Environment location:

 var env = await CoreWebView2Environment.CreateAsync(
    browserExecutableFolder: <locationOfRuntime>,
    userDataFolder: <locationOfSavedState>
);

The userDataFolder ends up containing things like browser history, cookies, local browser storage etc. - things that the browser stores as part of a session. The folder is reused if it exists, and if deleted it automatically re-creates itself. The browserExecutable folder is optional but it lets you point at a specific runtime in a folder. By default (if not set or set to null) the system installed runtime is used (if any). And again, the Edge install does not contain a WebView2 Runtime so you can't point at that folder.

If you're installing via an Install program you can distribute the runtime installer as part of your application and then fire the installer.

For Markdown Monster I use Inno Setup and in order to install the WebView2 runtime as part of the install I can add the following to the [Files] and [Run] sections:

[Files]
Source: ".\MicrosoftEdgeWebview2Setup.exe"; DestDir: "{tmp}"; Flags: deleteafterinstall

[Run]
Filename: "{tmp}\MicrosoftEdgeWebview2Setup.exe";

I use the Web Bootstrapper, which runs on every install, but only installs a runtime if a newer one is available.

In-App Checks and Installation

In Markdown Monster currently the WebView2 support is an optional feature that's distributed as a built-in addin. By default the addin is disabled so by default the application doesn't use the WebView2 control (yet).

So rather than forcing users to install a runtime they may not actually use, I delay install the runtime when users activate the addin or start using the WebView2 control. I can do this by checking for an installed version and comparing it to active WebView2 runtime assembly version (the build number specificially) and if the build number of the assembly is newer force a download.

If the version check fails I display a dialog that prompts the user to download and install the runtime:

The application performs the following steps:

  • Compares WebView assembly version vs. the CoreWebView2 version
  • If installed and newer continue
  • If not installed or older show the dialog box
  • Let the user install the runtime

The following code demonstrates how to compare the WebView2 library assembly version vs the installed CoreWebView2 runtime that's installed, displaying the dialog if needed and taking the user to the download and install page.

private bool IsWebViewVersionInstalled(bool showDownloadUi = false)
{
    string versionNo = null;
    Version asmVersion = null;
    Version ver = null;

    try
    {
        versionNo = CoreWebView2Environment.GetAvailableBrowserVersionString();

        // strip off 'canary' or 'stable' verison
        versionNo = StringUtils.ExtractString(versionNo, "", " ", allowMissingEndDelimiter: true)?.Trim();
        ver = new Version(versionNo);

        asmVersion = typeof(CoreWebView2Environment).Assembly.GetName().Version;

        if (ver.Build >= asmVersion.Build)
            return true;
    }
    catch {}

    IsActive = false;

    if (!showDownloadUi)
        return false;


    var form = new BrowserMessageBox() {
        Owner = mmApp.Model.Window,
        Width = 600,
        Height= 440,
        Title="WebView Runtime Installation",
    };

    form.Dispatcher.Invoke(() => form.Icon = new ImageSourceConverter()
        .ConvertFromString("pack://application:,,,/WebViewPreviewerAddin;component/icon_32.png") as ImageSource);

    var markdown = $@"
### WebView Runtime not installed or out of Date
The Microsoft Edge WebView Runtime is
{ ( !string.IsNullOrEmpty(versionNo) ?
        "out of date\n\nYour Build: " + ver.Build +
        "   -   Required Build: " + asmVersion.Build :
        "not installed")  }.

In order to use the Chromium preview you need to install this runtime by downloading from the [Microsoft Download Site](https://developer.microsoft.com/en-us/microsoft-edge/webview2/).

**Do you want to download and install the Edge WebView Runtime?**

*<small>clicking **Yes** sends you to the Microsoft download site.  
choose the **Evergreen Bootstrapper** download.</small>*";

        form.ClearButtons();
        var yesButton = form.AddButton("Yes", FontAwesomeIcon.CheckCircle, Brushes.Green);
        yesButton.Width = 90;
        var noButton = form.AddButton("No", FontAwesomeIcon.TimesCircle, Brushes.Firebrick);
        noButton.Width = 90;
        form.ShowMarkdown(markdown);
    

        form.ShowDialog();
        if (form.ButtonResult == yesButton)
        {
            mmFileUtils.OpenBrowser("https://developer.microsoft.com/en-us/microsoft-edge/webview2/");
        }

        return false;
}

This code is called by the startup and toggle routines:

// addin loader of Preview Browser
public override IPreviewBrowser GetPreviewBrowserUserControl()
{
    if (!IsActive || !IsWebViewVersionInstalled())
        return null;  // use the default IE control
    
    return new WebViewPreviewControl();
}

// toggle between IE and WebPreview2
public override void OnExecute(object sender)
{
    if (Model.Window == null || !IsWebViewVersionInstalled(true))
        return;

    IsActive = !IsActive;
    Model.Window.LoadPreviewBrowser();

    if (IsActive)
        Model.Window.ShowStatusSuccess("Switched to Chromium based Preview Browser.");
    else
        Model.Window.ShowStatusSuccess("Switched to Internet Explorer based Preview Browser.");
}        

And that's it - this works great to install the runtime at the point of use and that's what I currently run in Markdown Monster. Once the time comes to switch all Web interfaces to the WebView2 I probably switch to the installer approach as that ensures the application can start without a secondary install on startup.

Summary

The new WebView2 control provides a nice Chromium based browser experience for .NET (and C++) application on Windows using the same Chromium engine that is used by Microsoft Edge. Microsoft is building out a complete API for interacting with this control.

The control is very easy to use and easily integrated with .NET and in my case WPF. It works well, and even though the .NET implementations are officially still in pre-release the control has been very reliable and performance has been excellence. In fact, other than obvious developer errors on my part I have yet to see the WebView2 crash or cause any runtime issues, which frankly is surprising as even the WebBrowser control will crash from time to time.

This is exciting because over the years I've tried to use other Chromium based libraries and have found a number of stability and performance issues that ultimately made me stick with the WebBrowser control. With this control the integration so far has been smooth. I've integrated it into production in Markdown Monster so it's getting a lot of use and so far I don't see any problems with the control showing up in logs or user error reports. Yay!

It's not all roses and rainbows though - there are still a few rough edges. Runtime installation is a pain at the moment, with confusing installation options, and the fact that a runtime needs to be installed at all, even if a full Microsoft Edge install exists. There are also a few interop issues and the keyboard mapping from control to host which has me holding off implementing the editor portion of Markdown Monster using the WebView2. I'll talk more about Interop and some of these related issues in Part 2 of this article.

This concludes Part 1 of this article which is an overview of features and basic behavior. In Part 2 - to be released shortly - I'll take a look at interacting with content in the WebView2 and using .NET and JavaScript to interop between the host and Web Browser interfaces.

Resources

this post created and published with the Markdown Monster Editor
Posted in Webview  .NET  WPF  Windows  

The Voices of Reason


 

Cory
January 15, 2021

# re: Taking the new Chromium WebView2 Control for a Spin in .NET - Part 1

I presume the WebView2 control is only a separate runtime install during preview...it will eventually be installed with Edge once it's released, right?


Rick Strahl
January 15, 2021

# re: Taking the new Chromium WebView2 Control for a Spin in .NET - Part 1

@Cory - Unclear at this point. I've raised various issues around this and I haven't gotten a clear answer. I think they are still trying to figure out how the runtimes will eventually be distributed, but in the near term they have to be distributed explicitly as described in the post unfortunately.

According to the info I've gotten they won't be shipping with Edge because of size (imagine that) as it essentially doubles the install size. The discussion mentioned potentially as part of Windows updates though, but then you run the risk of out of date runtimes (which would also be a problem with Edge installs although less likely since those auto-updated frequently).


Byron Adams
January 17, 2021

# re: Taking the new Chromium WebView2 Control for a Spin in .NET - Part 1

Nice writeup. From what I understand as of Nov. 20,2020, version 1.0.664.37 is done and does not need the Canary build. However, the development continues, that's why you see new Previews.


Rick Strahl
January 18, 2021

# re: Taking the new Chromium WebView2 Control for a Spin in .NET - Part 1

@Byron - the runtimes are updated supposedly on a 6 week cycle, but they're late with the latest updates it seems.

The runtimes are always available, but latest features are only available via Canary builds and any runtime install overrides the Canary build Runtimes. So yeah, it's all confusing as hell.


Shaun
January 22, 2021

# re: Taking the new Chromium WebView2 Control for a Spin in .NET - Part 1

Great write-up. I wish this had been available earlier when I started tinkering with WebView2; it will be helpful to folks just getting started.

Regarding setting the browserExecutableFolder when calling CoreWebView2Environment.CreateAsync(), you mention (in the Installing with an Installer section):

This folder ends up containing things like browser history, cookies, local browser storage etc.

I thought it was the userDataFolder (link) that contained those items?


Rick Strahl
January 23, 2021

# re: Taking the new Chromium WebView2 Control for a Spin in .NET - Part 1

@Shaun - thanks for catching that. The text is unclear and I forgot to add the userData parameter in the call signature. Updated and clarified that block to make it clear the runtime and user folders are two separate configurable settings. Thanks.


Rolf Keller
January 25, 2021

# re: Taking the new Chromium WebView2 Control for a Spin in .NET - Part 1

From my experience it is NOT necessary to install the Runtime package if you have Chromium Edge installed. If Webview2 during its initialization is not provided with a path to the Runtime, Webview2 looks for an Edge installation and, if found, uses that. (See MS' documentation, BTW.)

You can verify this by programmatically checking the version info. If you remove the Runtime version X from the PC, your application can run quite normally but the version number shown is that of the installed Edge which can differ from X.


Rick Strahl
January 26, 2021

# re: Taking the new Chromium WebView2 Control for a Spin in .NET - Part 1

@Rolf - you can try this out:

  • Uninstall any WebView2 Runtimes you have installed
  • Uninstall any Edge Canary Builds

If I do this the WebView2 control no longer loads for me - get an error that no runtime was found.

Note: If you have Canary Builds installed they install a Runtime SDK. So if you have a Canary build you don't need a runtime. But you can't rely on that with a distributed application obviously, so you need to check (or install with app install).


Rolf Keller
January 27, 2021

# re: Taking the new Chromium WebView2 Control for a Spin in .NET - Part 1

Rick, I'm not convinced yet. 😉

I've just tested it again. The most recent Runtime version I've ever installed is 88.0.705.50. I can verify this version number via CoreWebView2Environment.GetAvailableBrowserVersionString().

If I hide this Runtime by moving the complete folder temporarily to a different drive, WebView2 throws a Not-Found exception. BUT if I then change my app to CoreWebView2Environment.CreateAsync(null, ...) and recompile, it runs perfectly and gives the version numer 88.0.705.53. This is the version number of my Edge. According to the documentation the "null" forces WebView2 to look at other places, but obviously it did not find the moved version on the other drive. So, in this situation, what is used by WebView2 if not Windows' "own" Edge?

I've never ever used other things than the "fixed" Runtime. No live/evergreen update version, no Canary, no nothing. 😃 FWIW, I use the Nuget version 1.0.705.50 of WebView2 for C# x64.


Rick Strahl
January 27, 2021

# re: Taking the new Chromium WebView2 Control for a Spin in .NET - Part 1

@Rolf - I experimented with this quite a bit, and I couldn't get it to run if both of these were true:

  • The WebView2 Runtime is not installed
  • There is no Canary install

I get a runtime not found error in that case. Install Canary or the Runtime and it works again. So not sure. I leave the binary folder at default which would use a system install.

If you distribute your own full runtime then it will obviously work, but without that - I don't see it.


Rick Strahl
January 27, 2021

# re: Taking the new Chromium WebView2 Control for a Spin in .NET - Part 1

@Rolf - I just double checked this one more time by explicitly uninstalling my runtime and Canary builds. Here's what I get when I initialize the control:

Note also in the screen shot that Edge is installed with a recent build including the actual 705 build which is the same as the Runtime. I also tried explicitly setting the executable folder to the Edge install and that also did not work.

I had several discussions on this issue with folks on the WebView2 Feedback Issues and Microsofties have confirmed that you have to have a runtime installed one way or the other. Edge alone does not provide it.

And yes I 100% agree with you that this shit is confusing and I think seriously needs to be fixed.

Ideally the Runtime should ship with Edge installs, but the rationale is that it bloats the distributable for Edge and at least for now the SDK seems to lag behind the RTM Edge releases. In the discussions there was talk of the Runtime shipping with Windows Update which seems a strange way to me but would be better than manual installs. Once the SDKs have a stable base (currently still lots of things changing) I think picking an older runtime that installs with Windows Update and updating it occasionally to more recent versions would be better than nothing.

Leaving version management up to manual installs as it is now is bound to end up a train wreck I'm afraid.

For now, I use a runtime check approach (as WebView2 is optional) with install as needed, but for a dedicated app I would run it the Web installer as part of my setup routine and install the latest version.


Ralph
February 11, 2021

# re: Taking the new Chromium WebView2 Control for a Spin in .NET - Part 1

Thank you, Rick, for your very instructive posts! There is still a lot of room for improvement in Microsoft documentation ...

Do you know a way to handle breakpoints (e.g. set by means of a debugger statement) in JS code via DevTools called by MyWebView2.CoreWebView2.OpenDevToolsWindow()?

Thanks in advance!


Rick Strahl
February 11, 2021

# re: Taking the new Chromium WebView2 Control for a Spin in .NET - Part 1

@Ralf - sure you can set breakpoints using the Dev Tools debugger. Open the source file from the left pane, then click in the margin to set a break point.

You can also put a debugger; statement into your JavaScript to break under code control.


Ralph
February 12, 2021

# re: Taking the new Chromium WebView2 Control for a Spin in .NET - Part 1

@Rick: Update: Navigating to the script via myWebView.Source = ... ignores all debugger; statements in the script. In my case the breakpoints are only activated when a JS function (with a breakpoint inside) is called via myWebView.ExecuteScriptAsync (...) after control again has been returned to .NET. A bit of getting used to, but at least a workaround.


Rick Strahl
February 12, 2021

# re: Taking the new Chromium WebView2 Control for a Spin in .NET - Part 1

Ralph - yeah that makes sense I guess. If you load the HTML dynamically via NavigateToString() there's no 'file' that the debugger can load (ie. there's no file list). The code might be inline in the HTML document which should show up.


Ralph
February 14, 2021

# re: Taking the new Chromium WebView2 Control for a Spin in .NET - Part 1

@Rick: It works neither via inline script in the HTML file nor via the "Source" property, although the file is explicitly linked there:

 Private Async Sub InitializeWebView()

        Dim m_Options As New CoreWebView2EnvironmentOptions With {
            .AdditionalBrowserArguments = "--allow-file-access-from-files"}
        Dim m_EnvironmentFolder As CoreWebView2Environment =
            Await CoreWebView2Environment.CreateAsync(userDataFolder:=
            IO.Path.Combine(IO.Path.GetTempPath, "TEST"), options:=m_Options)
        m_WebView.Source = New Uri(IO.Path.Combine("file://", Environment.CurrentDirectory, "test.html"))
        m_WebView.CoreWebView2.OpenDevToolsWindow()

    End Sub

I think RedMoon still has room for improvement.


Rick Strahl
February 15, 2021

# re: Taking the new Chromium WebView2 Control for a Spin in .NET - Part 1

@Ralph - It works fine with files - that's what I do in Markdown Monster. Your URI is invalid: file:/// should be the prefix, but you can pass just the file name the URI ctor.

The debugger attaches asynchronously, so it may not fire in the load of the page. You may want to open the debugger as one of the first things you do before setting the source to minimize that lag. And you should be able to reload the page to fire any startup code debugger statements.


Ralph
February 15, 2021

# re: Taking the new Chromium WebView2 Control for a Spin in .NET - Part 1

@ Rick: You are right - the file prefix can be omitted. Unfortunately, opening the debugger before setting the source and / or reloading the entire page does not change the situation. Thank you anyway!


Ralph
February 18, 2021

# re: Taking the new Chromium WebView2 Control for a Spin in .NET - Part 1

@Rick: Programmatically reloading the page does not solve the problem. However, CTRL+R after the page loads is a workaround.


Freddy Mora Silva
March 24, 2021

# re: Taking the new Chromium WebView2 Control for a Spin in .NET - Part 1

How can you pass credentials to the webview2 control when loading a web page. The idea is to go to the specific page without presenting the login page. Of course we are using valid credentials


Michel LAPLANE
March 30, 2021

# re: Taking the new Chromium WebView2 Control for a Spin in .NET - Part 1

Hi Rick, thank you for these series. I was previously working with EO.WebBrowser for .NET from "Essentials Object" but I definitely use Microsoft stuff (no license needed) and specially WebView2 control. Usage of "SetVirtualHostNameToFolderMapping" is very useful and also "AddHostObjectToScript" for sharing between WinForm C# and Javascript. My project helping to display Microsoft Visio Diagram in 3D using WebView2 control and BabylonJS inside the control will be soon available on GitHub in an updating version of VisualShow3DLight. Great posts : Thank you again.


bitbonk
April 01, 2021

# re: Taking the new Chromium WebView2 Control for a Spin in .NET - Part 1

There is also a Fixed Version distribution mode that doesn't require the runtime to be installed. Have you tried that and if so, have you had any success with that?


Rick Strahl
April 01, 2021

# re: Taking the new Chromium WebView2 Control for a Spin in .NET - Part 1

@bitbonk - yes you can ship a fixed runtime, but... it's huge. It'll add 70-80mb to your installation package as you are essentially installing a local version of Edge plus the runtime.

I wish they made a package that's just the runtime without the full Edge packaging. I'm OK with requiring users to have the latest Edge installed on their machines, but having a fixed runtime installed is really what's the main problem.


Koushik
April 05, 2021

# re: Taking the new Chromium WebView2 Control for a Spin in .NET - Part 1

Hi Rick, You have written that "The runtimes are backwards compatible so you need a current or newer version of the Runtime relative to the SDK/Controls". Is there any document, preferably from Microsoft, supporting this claim? This would be of great help as we are moving from CefSharp to WebView2 but the compatibility concerns between the runtime and the SDK are preventing us from using the evergreen bootstrapper. Thanks, Koushik


Rob Kent
April 12, 2021

# re: Taking the new Chromium WebView2 Control for a Spin in .NET - Part 1

Thanks for a great writeup, which saved me a lot of time. Some of our clients installed the pre-release control (Evergreen) and didn't get the upgrade when it went to release, as expected. We saw the standard error and I added the detection code to our app so that they see a nice error panel.

We also had a different error on a dev machine which I think might be do to with the nuget package or the build on that machine:

"Unable to load DLL WebView2Loader.dll. The specified module could not be found."

Anybody else seen that one?

Thanks a lot


Rick Strahl
April 13, 2021

# re: Taking the new Chromium WebView2 Control for a Spin in .NET - Part 1

@Rob - yes I've seen this. This is caused by a version mismatch - usually happens if you installed the WebView2 runtime, then uninstall it and some other version (perhaps from a Canary build) is used instead.

This is frustrating - had that issue and then it magically went away when I re-installed the WebView2 runtime explicitly.

Microsoft is working to get the WebView2 runtime installed with Windows updates. Once we have a few rounds of this in I think it'll be pretty safe to pick an older version and just stick with that and according to what they are shooting for that should continue to work (ie. forward compatible). We won't know until we see this in the wild though and it'll probably take a few update cycles before enough machines get these WebView2 runtime updates.

But roughly that is my plan - wait it out until the runtime becomes more integrated and then pick an older version and just stay with that.


Rob Kent
April 19, 2021

# re: Taking the new Chromium WebView2 Control for a Spin in .NET - Part 1

Yes that is what you would expect. We deployed our app today with the new WebView2 and immediately got reports of SSRS reports being broken. Seems like it doesn't respect the client desktop's regional settings and the date 2021-04-16 was rendering as 21/04/16. There is a Github issue for it. Not sure what the workaround is yet since we don't have any regional code ourselves - we just accept parameters on the URL.


Wes Ong
April 20, 2021

# re: Taking the new Chromium WebView2 Control for a Spin in .NET - Part 1

Hi Rick, just curious, you mentioned in your article that:

The WebView2 .NET integration that provides the WPF and WinForm controls, is shipped as a NuGet package that you can add to your WPF or WinForms project. The NuGet Package supports both WPF and WinForms versions of the control so regardless which you use and it works both in Full .NET Framework (NETFX) as well as .NET Core.

Did you test which minimum version of .NET Framework supports WebView2?

From the WebView2 site, it seems to suggest V4.6.2 or later.


Rick Strahl
April 20, 2021

# re: Taking the new Chromium WebView2 Control for a Spin in .NET - Part 1

@Wes - if that's what the site says then that's what it is. Nobody should be using anything older than .NET 4.62 anyway for building .NET Framework apps.


Paul
May 12, 2021

# re: Taking the new Chromium WebView2 Control for a Spin in .NET - Part 1

Hey on the standard web browser control there was an option in winforms:

webBrowser.ScriptErrorsSuppressed = true;

Is there something similar when using WebView2 at all? - Currently, I don't care about the errors that pop up! "Script Error": https://tinyurl.com/2j3t4ttk


Rick Strahl
May 12, 2021

# re: Taking the new Chromium WebView2 Control for a Spin in .NET - Part 1

@Paul - No I don't think there's anything on the control. If there is check on WebBrowser.CoreWebView2 . if there's nothing there, you could override window.onerror= function() { return false; } with ExecuteScriptAsync() to basically ignore errors.


Rajesh Kumar
September 13, 2021

# re: Taking the new Chromium WebView2 Control for a Spin in .NET - Part 1

Is there a way to show the Edge Find window. I have a WPF tree on left and Edge control on right, when we search we filter the tree based on search phrase. When we display the corresponding HTML page based on Tree View Node selected, I want to show the html page on Edge with phrase tokens highlighted.

Since I did not find APIs for this, simplest thought was to do SendKeys and that also did not work. Any thougts?


Rick Strahl
September 15, 2021

# re: Taking the new Chromium WebView2 Control for a Spin in .NET - Part 1

@Rajesh - don't think there's UI activation available for that. You can use window.find() in JavaScript, but it looks like the showDialog parameter is not supported by Edge.


David Elgstuen
September 29, 2021

# re: Taking the new Chromium WebView2 Control for a Spin in .NET - Part 1

If the application was installed in \Program File(x86)\myapp but the application also preemptively creates the .WebView2 folder, would that guarantee write access and eliminate the need to relocate/manage UserDataFolder location?


Rick Strahl
September 29, 2021

# re: Taking the new Chromium WebView2 Control for a Spin in .NET - Part 1

@David - No creating the folder is not enough since the permissions for the new files and folders are also required. Unless your installer sets the permissions of the folder preemptively created that will still fail. I recommend pushing the data into the app related %appdata% folder or if you create and wipe, maybe the %temp% folder.


Howzilla
October 02, 2021

# re: Taking the new Chromium WebView2 Control for a Spin in .NET - Part 1

I have the webbrowser2 implemented in my VS2019 c# winform app, FW 4.7.2.

Everything works fine on the development machine.

When I install my app to a user machine the form with the webbrowser2 control opens but will not display the web page. I have installed the runtime on the user machine but it just won't work.

The form is very simple. I call the form passing arguments then assemble a uri and use that uri to set the Source for the webbrowser2 control.

Any ideas ??


Howzilla
October 05, 2021

# re: Taking the new Chromium WebView2 Control for a Spin in .NET - Part 1

I can see the control is trying to write to files inside my install directory ( E:\Development...\x86\Release\MyApp.exe.WebView2\EBWebView ). This won't work in production. I have tried the code you supplied to get the code to use a userDataFolder outside the application ( CoreWebView2Environment.CreateAsync() ) but it doesn't work. No matter what I do it still uses the directory inside the install directory. This is the only thing holding up this project. Very frustrating.


Rick Strahl
October 06, 2021

# re: Taking the new Chromium WebView2 Control for a Spin in .NET - Part 1

@Howzilla, you can provide the userData when you create the environment. I think you have to make sure the location you provide (I would use %appdata% or %temp% for this) has full access to read and write the contents of this folder.


Andre
November 04, 2021

# re: Taking the new Chromium WebView2 Control for a Spin in .NET - Part 1

Question about the old WebBrowser control... Since it is based on IE11, will it stop working when IE11 reaches end of life, on June 15, 2022?

In other words, are we forced upgrading our apps to the new WebView2 control before June 2022?

Thanks in advance.


Rick Strahl
November 05, 2021

# re: Taking the new Chromium WebView2 Control for a Spin in .NET - Part 1

@Andre - Internet Explorer is getting retired, but not the MSHTML hostable engine. That will continue to work indefinitely.

There are lots and lots of applications that depend on the MSHTML engine including Office apps (both old and new!). If they pulled this a very large swath of Windows applications would no longer work, so that's not going to happen. But... it'll never get updated again, so you have to make do with the capabilities of the somewhat limited IE 11 HTML5 support.


alex
November 23, 2021

# re: Taking the new Chromium WebView2 Control for a Spin in .NET - Part 1

This stuff dont work. I mean i was able to load content froms static file. But issue is changing environment throw error

System.ArgumentException: 'WebView2 was already initialized with a different CoreWebView2Environment. Check to see if the Source property was already set or EnsureCoreWebView2Async was previously called with different values.'

Only option is null. await webView.EnsureCoreWebView2Async(null);

i want to change environment options var op = new CoreWebView2EnvironmentOptions("--disable-web-security");

Basically try to do testing locally without running separate server.


Rick Strahl
November 28, 2021

# re: Taking the new Chromium WebView2 Control for a Spin in .NET - Part 1

@alex - not sure what you mean. This works:

async Task InitializeAsync()
{
    // must create a data folder if running out of a secured folder that can't write like Program Files
    var browserFolder = Path.Combine(wsApp.Configuration.CommonFolder, 
                                     wsApp.Constants.WebViewEnvironmentFolderName);
    var options = new CoreWebView2EnvironmentOptions("--disable-web-security");
    var env = await CoreWebView2Environment.CreateAsync(
        userDataFolder: browserFolder, options: options
    );
    await WebBrowser.EnsureCoreWebView2Async(env);

The environment error means you're probably not setting up a custom folder for your environment and it's going to some existing environment that's not compatible. IOW, make sure you explicitly create an environment folder for your app.


Michael Janulaitis
February 02, 2022

# re: Taking the new Chromium WebView2 Control for a Spin in .NET - Part 1

Any idea how to capture screenshots from a Console application running in the background? The following call always hangs:

webView2.CoreWebView2.CallDevToolsProtocolMethodAsync("Page.captureScreenshot", p);


Jakob
December 20, 2022

# re: Taking the new Chromium WebView2 Control for a Spin in .NET - Part 1

I think it would be really cool to have HTA-like runtime that uses WebView2. I was able to put a WinForms together that used the old WebBrowser class. Found out i could make the functions in C# script available via the windows.external object with [ComVisible(true)].

I Could even recreate the new ActiveXObject() method that i know from HTA's

public Object NewActiveXObject(String progId)
{
    return (Activator.CreateInstance(Type.GetTypeFromProgID(progId)));
}

I tried to add that function in your script, and then from javascript do:

var fso = window.chrome.webview.hostObjects.mm.NewActiveXObject("Scripting.FileSystemObject");

But that did not work. Do you know if it's somehow possible? Webview2 has the window.external object aswell.. but i'm not sure if it can be used in the same manner as with WebBrowser class.


Rick Strahl
January 06, 2023

# re: Taking the new Chromium WebView2 Control for a Spin in .NET - Part 1

@Jakob - No that's not possible - you can't pass back an object by reference to JavaScript. You can only pass back JSON that gets deserialized on the client.

And that's a good thing. Potentially big security issue. You can proxy operations to .NET and execute in .NET then return the results as long as they are serializable.


West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2024