WebView2
Table of Contents
Setup & Installation
For every WebView2 project, use the NuGet package manager within Visual Studio to add the WebView2 SDK to the project. You install the Microsoft.Web.WebView2 SDK NuGet package for use by the current project.
After installation, you will see WebView2 in your Toolbox. If you do not see WebView2 control in your Toolbox, try to Reset Toolbox (right-click), or close and re-open Visual Studio.
Hint: If you still do not see WebView2 in your Toolbox, check path name to your project. It must not contain forbidden chars - including hash #. If you place your project to folder C:\MyProjects\c#\
, you obtain unpredictable results. Without warning.
Add WebView2 to Form
Adding WebView2
to Form
is simple – and very same as any other Control
. You can easily create your own browser. But usually you do want to create browser with some special properties.
Navigation – display given URL
The very first thing is to navigate WebView2 control to some URL address. The easy way is to set WebView2.Source
property in design time. Once you set the Source
property (e.g. https://www.stavebky.cz
), you can run the Form and the web page is displayed.
If you want to navigate to some URL from code, you have tho options. You can set WebView2.Source
property, but the property does not accept string, but Uri
object:
webView2.Source = new Uri("https://baldsoft.com");
The second option is to set CoreWebView2.Navigate
property. Here we set URL as string
– not Uri object.
webView2.CoreWebView2.Navigate("https://baldsoft.com");
Both options have the same result. WebView2 will display new URL. But that is not all! Read following:
Initialization of WebView2
If you set WebView2.Source
property in design time, then you can navigate in your code to different URL without problems. BUT if you leave WebView2.Source
property in design time empty, then do not forget to initialize the WebView2 before you use it. Following example is button click handler navigating to given URL:
private async void button1_Click(object sender, EventArgs e)
{
// Initialize webView2
await webView2.EnsureCoreWebView2Async(null);
// Navigate to URL
webView2.CoreWebView2.Navigate("https://baldsoft.com");
}
Initialization is made by webView2.EnsureCoreWebView2Async(null)
. It is asynchronous function, hence we have to wait for result with await
. And that means that the button click handler itself must be async
function.
Once again: this initialization is necessary only if the WebView2 control is not initialized by setting Source
property in design time.
Waiting for navigation to complete
Navigation of WebView2 is asynchronous. That means, that after setting webView2.Source
the program goes to next line although the web page is not loaded yet! You can wait for navigation to complete in WebView2 by using the NavigationCompleted
event. This event is raised when the WebView2 has completely loaded or loading stopped with an error.
private async void button1_Click(object sender, EventArgs e)
{
// Wait for webView2 initialization
await webView2.EnsureCoreWebView2Async(null);
// Add handler for NavigationCompleted event
webView2.NavigationCompleted += OnNavigationCompleted;
// Navigate to URL
webView2.CoreWebView2.Navigate("https://baldsoft.com");
}
private void OnNavigationCompleted(object sender, CoreWebView2NavigationCompletedEventArgs e)
{
if (e.IsSuccess)
{
// Navigation completed successfully
MessageBox.Show("Navigation completed succesfully!");
}
else
{
// Handle error
MessageBox.Show($"Something went wrong,\nWebErrorStatus {e.WebErrorStatus}\nHttpStatusCode: {e.HttpStatusCode}");
}
}
In the code above the handler was named OnNavigationCompleted
and begins on line 14. Note, that before calling Navigate method, this event handler must be attached (line 7). The handler has two arguments: sender
and e
. Argument e
is of type CoreWebView2NavigationCompletedEventArgs
with information about the result of navigation. The most important is e.IsSucces
, (see line 16 and MessageBox
on line 24).
Hint: Attaching handler in the example above gives warning on line 7. That is because the compiler is not sure, that webView2 is not null. You can get rid of this warning by modifying OnNavigationCompleted declaration (add "?" after object):
private void OnNavigationCompleted(object? sender, CoreWebView2NavigationCompletedEventArgs e)
Handler of NavigationCompleted can be attached as a lambda function as well. See following short example:
webView2.NavigationCompleted += OnNavigationCompleted;
private void OnNavigationCompleted(object sender, CoreWebView2NavigationCompletedEventArgs e)
{
if (e.IsSuccess)
{
// Navigation completed successfully
}
else
{
// Handle error
}
}
Remember that the attached handler remains attached until it is detached. The following handler will therefore cause an infinite loop:
private async void button1_Click(object sender, EventArgs e)
{
await webView2.EnsureCoreWebView2Async(null);
// Attach handler for NavigationCompleted event
webView2.NavigationCompleted += OnNavigationCompleted;
// Navigate to URL
webView2.CoreWebView2.Navigate("https://baldsoft.com");
}
private void OnNavigationCompleted(object sender, CoreWebView2NavigationCompletedEventArgs e)
{
if (e.IsSuccess)
{
MessageBox.Show("Navigation completed succesfully!");
// Beware! Following line causes infinite loop!
webView2.CoreWebView2.Navigate("https://baldsoft.com");
}
}
Solution in this case is e.g. detach the handler
private void OnNavigationCompleted(object sender, CoreWebView2NavigationCompletedEventArgs e)
{
// Detaching the handler
webView2.NavigationCompleted -= OnNavigationCompleted;
if (e.IsSuccess)
{
MessageBox.Show("Navigation completed succesfully!");
webView2.CoreWebView2.Navigate("https://baldsoft.com");
}
}
…or you can use a variable according to which different methods will be called in the handler.
Passing parameter to NavigationCompleted handler
Another way how to modify behaviour of WebView2.NavigationCompleted
handler is to pass some parameter to the handler. You can use some property which is accessible from the handler and set the property before navigation. But sometimes it would be nice to pass some parameter directly to the handler. The handler itself has only two parameters (sender, e). But we can use following trick: the handler (with two parameters) immediately calls our custom handler with custom parameters.
private async void button1_Click(object sender, EventArgs e)
{
await webView2.EnsureCoreWebView2Async(null);
// Some parameter we want to pass to the handler
string parameter = "some text";
// Lambda function (with 2 params) subscribesNavigationCompleted event
// Lambda function is calling our custom handler with 3 params
webView2.NavigationCompleted += ((sender, e) => OnNavigationCompleted(sender, e, parameter));
// Navigate to URL
webView2.CoreWebView2.Navigate("https://baldsoft.com");
}
// Custom handler receives three parameters
private void OnNavigationCompleted(object sender, CoreWebView2NavigationCompletedEventArgs e, string parameter)
{
if (e.IsSuccess)
{
MessageBox.Show($"Received parameter {parameter}");
}
}
Navigate and wait until given tag is displayed
Very often we can not rely on WebView2.NavigationCompleted
only. This handler is called after body of page is loaded (body.onload
has been raised). But sometimes we have to wait until some script loads next part of the page. Sometimes we can simply wait for some time, but that is not the best way how to solve this problem.
Better way is to check for presence of given tag. In the following example we modify WebView2.NavigationCompleted
handler to check repeatedly for presence of some tag. The tag is defined by XPATH. WebView2 itself can not work with XPATH, but we can run JavaScript on the page and intercept result od JS code.
The code also includes a solution for the situation when the tag is not found even after a certain amount of time. This time is passed in the variable waitTime
as the number of milliseconds that must elapse before we declare the attempt unsuccessful.
private async void button1_Click(object sender, EventArgs e)
{
await webView2.EnsureCoreWebView2Async(null);
// We want to wait till element <title> is found
// Define XPATH to the element
string xPath = @"//title";
// How long time to wait for the element (in miliseconds)
int waitTime = 30000;
// Attach NavigationCompleted handler - with aditional parameters
webView2.NavigationCompleted += ((sender, e) => OnNavigationCompleted(sender, e, xPath, waitTime));
// Navigate to URL
webView2.CoreWebView2.Navigate("https://baldsoft.com");
}
private async void OnNavigationCompleted(object sender, CoreWebView2NavigationCompletedEventArgs e,
string xPath, int waitTime)
{
if (e.IsSuccess)
{
// Presence of element (given by xPath) will be checked in loop
// We will wait for some time in between
int delayTime = 200;
// Calculate maximal number of loops allowed
int loopCounter = waitTime / delayTime;
bool elementFound = false;
while (!elementFound && loopCounter > 0)
{
try
{
loopCounter--;
// Run JavaScript to check if the element is present
string script = $@"
(function() {{
var element = document.evaluate('{xPath}', document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
return element != null;
}})();";
var result = await webView2.CoreWebView2.ExecuteScriptAsync(script);
elementFound = bool.Parse(result);
}
catch (Exception ex)
{
MessageBox.Show($"An error occurred: {ex.Message}");
}
if (!elementFound)
{
await Task.Delay(delayTime); // Wait for a short period before checking again
}
}
MessageBox.Show("Element found!");
}
// If e.IsSuccess is false, display message
else MessageBox.Show($"Error loading page: {e.WebErrorStatus}");
}
Navigate and wait synchronously until given tag is displayed
The above given approach has one disadvantage: the waiting routine (method OnNavigationCompleted
) is event handler and the calling method is not informed about when the handler is finished or what the result was. Very often we want to do something like:
- Navigate to URL
- wait untill page is completely loaded and
- perform some action (e.g. count anchor tags <a> or something similar).
The examples we have presented so far do points 1 and 2. But the problem is to continue with step #3. Because we call OnNavigationCompleted
and we have no signal, when this method is finished.
Sometimes we can get around the problem and add step 3 to step 2. It is, we can perform desired action (counting anchors) directly in step 2 (i.e. in the handler). But if the action in step 3 is complicated or if we want do to several steps, we need different approach. We need to call synchronously some method that will navigate to given URL and waits for specified tag. After this method is finished, it will (synchronously) return to calling method.
And here it is: Button.Click
handler calls new method WaitForElementAsync
. And this method does exactly what we need: Loads given URL, waits till XPATH is present and returns back. Note that we call await WaitForElementAsync
, that is, next line is executed afterwards WaitForElementAsync
is finished. And really: next line is MessageBox
informing about result.
private async void button1_Click(object sender, EventArgs e)
{
string xPath = @"//title";
int waitTime = 10000;
bool loaded = await WaitForElementAsync(xPath, waitTime);
MessageBox.Show($"Waiting for {xPath}\nResult:{loaded}");
}
async Task<bool> WaitForElementAsync(string xPath, int waitTime)
{
var tcs = new TaskCompletionSource<bool>();
// Presence of element (given by xPath) will be checked in loop
// We will wait for some time in between
int delayTime = 100;
// Calculate maximal number of loops allowed
int loopCounter = waitTime / delayTime;
// Attach handler for NavigationCompleted event
webView2.NavigationCompleted += async (s, e) =>
{
while (true)
{
loopCounter--;
string script = $@"
(function() {{
var element = document.evaluate('{xPath}', document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
return element != null;
}})();";
// Run JavaScript and wait for result
var resultScript = await webView2.ExecuteScriptAsync(script);
bool result = bool.Parse(resultScript);
// If tag given by XPATH was found, return true
if (result == true)
{
tcs.SetResult(true);
return;
}
// If tag was not found, return false
else if (loopCounter == 0)
{
tcs.SetResult(false);
return;
}
await Task.Delay(delayTime); // Check every 100ms (delayTime)
}
};
// Initialize webView2 and navigate
await webView2.EnsureCoreWebView2Async();
webView2.CoreWebView2.Navigate("https://baldsoft.com");
return await tcs.Task; // Wait for the element to appear and return result
}
How does it work? The key are last three lines in WaitForElementAsync
. We
- initialize webView2 by calling EnsureCoreWebView2Async
- navigate to desired URL and
- wait synchronously (
await
) forTaskCompletionSource
tcs
.
Handler webView2.NavigationCompleted
is attached via lambda function and it works in similar way as previous versions if this handler. The only (but crucial) difference is, that this handler signals result via TaskCompletionSource
tcs
.