From WebView to WebView2 in UWP

fanmixco

Federico Navarrete

Posted on October 16, 2022

From WebView to WebView2 in UWP

In the old days, accessing a local website in UWP was relatively easy. Even if you wanted to load your third-party libs. The basic code you required:

The WebView:



<WebView x:Name="webView1" Source="ms-appx-web:///Assets/index.html" NavigationStarting="WebView1_NavigationStarting" NavigationCompleted="WebView1_NavigationCompleted" />


Enter fullscreen mode Exit fullscreen mode

Here you defined the Source and some extra events like NavigationStarting or NavigationCompleted for loading your JS or enabling JS interaction with your third-party lib (that you had to add as a reference in your main project).

Loading your third-party lib:



private void WebView1_NavigationStarting(WebView sender, WebViewNavigationStartingEventArgs args)
{
    sender.AddWebAllowedObject("CallJSCSharp", new CallJSCSharp());
}
Adding extra code to load additional JS:

private async void WebView1_NavigationCompleted(WebView sender, WebViewNavigationCompletedEventArgs args)
{
    string filename = $"ms-appx:///Assets/js/lang/en.json";
    StorageFile file = await StorageFile.GetFileFromApplicationUriAsync(new Uri(filename));
    string contents = await FileIO.ReadTextAsync(file);

    string[] arg = { contents };

    _ = await webView1.InvokeScriptAsync("UpdateData", arg);
}


Enter fullscreen mode Exit fullscreen mode

And this is the function to execute extra functions from my app:



namespace CallJSInterface
{
    private Windows.System.Display.DisplayRequest _displayRequest;

    public sealed partial class CallJSCSharp
    {
        public void KeepScreenOn()
        {
            //create the request instance if needed
            if (_displayRequest == null)
            {
                _displayRequest = new Windows.System.Display.DisplayRequest();
            }

            //make request to put in active state
            _displayRequest.RequestActive();
        }
    }
}


Enter fullscreen mode Exit fullscreen mode

Load Website

Now, what are all the changes required for the new WebView2? There are several changes to be considered. The first one is to download the lib Microsoft.UI.Xaml from NuGet in your main project:

preview3

This lib allows you to add WebView2. It requires two changes:

Add this lib in your XAML's libs:



xmlns:controls="using:Microsoft.UI.Xaml.Controls"


Enter fullscreen mode Exit fullscreen mode

And then, you can add your WebView2:



<controls:WebView2 x:Name="webView1" NavigationCompleted="WebView1_NavigationCompleted" />


Enter fullscreen mode Exit fullscreen mode

Now comes the tricky parts. The first change is about the Source. With WebView2, we can only load external websites like bing.com or facebook.com. Any local website is going to fail.

The new approach follows creating an asynchronous function that creates a virtual host (it has to be called in the Constructor), where you map your current local website like this:



private CoreWebView2 core_wv2;

private async void LoadLocalPage()
{
    string fakeDomain = "myapp.something";
    string assetsLocation = "Assets";
    string firstPage = "index.html";

    await webView1.EnsureCoreWebView2Async();
    core_wv2 = webView1.CoreWebView2;
    if (core_wv2 != null)
    {
        core_wv2.SetVirtualHostNameToFolderMapping(
            fakeDomain, assetsLocation,
            CoreWebView2HostResourceAccessKind.Allow);

        webView1.Source = new Uri($"https://{fakeDomain}/${firstPage}");
    }
}


Enter fullscreen mode Exit fullscreen mode

Every path that you have in your old app looks like this:

ms-appx-web:///Assets/

must be changed to:

https://myapp.something/

Therefore, if you loaded Bootstrap like this:

ms-appx-web:///Assets/css/boostrap.min.css

Now, it is going to be like this:

https://myapp.something/css/bootstrap.min.css

Invoke 3rd-party Libs from JS

The next part is how to interact with your third-party lib, and this part is complicated.

The first part involves adding a new and very specific C++ project, Windows Runtime Component (C++/WinRT), to your solution that must be called WinRTAdapter.

preview2

You must install a lib from NuGet Microsoft.Web.WebView2:

preview1

Add as a reference your third-party lib.

Go to your C++ project properties go to Common Properties and choose WebView2:

preview4

Here you have to do four changes:

  • Set Use WebView2 WinRT APIs to No.
  • Set Use the wv2winrt tool to Yes.
  • Set Use Javascript case to Yes.
  • Edit Include filters and add the following ones:


Windows.System.UserProfile
Windows.Globalization.Language
CallJSInterface


Enter fullscreen mode Exit fullscreen mode

CallJSInterface is the name of my third-party's namespace.

preview6

You click on OK and build your C++ lib.

After you have built your C++ lib (WinRTAdapter), you must add it to your main project as a reference.

Now, we need to do some changes to be able to invoke the functions from our third-party lib. The first one is to register it. We do it in the same LoadLocalPage() function from before or on NavigationCompleted:



var namespacesName = "CallJSInterface";
var dispatchAdapter = new WinRTAdapter.DispatchAdapter();
core_wv2.AddHostObjectToScript(namespacesName, dispatchAdapter.WrapNamedObject(namespacesName, dispatchAdapter));


Enter fullscreen mode Exit fullscreen mode

Where CallJSInterface is your namespace. After this, you need to register your function in your JS like this:



var callJS;
if (chrome && chrome.webview) {
    chrome.webview.hostObjects.options.defaultSyncProxy = true;
    chrome.webview.hostObjects.options.forceAsyncMethodMatches = [/Async$/];
    chrome.webview.hostObjects.options.ignoreMemberNotFoundError = true;
    window.CallJSInterface = chrome.webview.hostObjects.sync.CallJSInterface;
    callJS = new CallJSInterface.CallJSCSharp();
}


Enter fullscreen mode Exit fullscreen mode

Where CallJSInterface is one more time your namespace. Now, you can invoke JS like this (the async() is mandatory):



callJS.async().KeepScreenOn()


Enter fullscreen mode Exit fullscreen mode

Inject JS code

The last part is how to load your scripts as before. The old function InvokeScriptAsync("UpdateData", arg) doesn't exit. The best way is to create a class Extension like this:



public static class Extensions
{
    public static async Task<string> ExecuteScriptFunctionAsync(this WebView2 webView2, string functionName, params object[] parameters)
    {
        string script = functionName + "(";
        for (int i = 0; i < parameters.Length; i++)
        {
            script += JsonConvert.SerializeObject(parameters[i]);
            if (i < parameters.Length - 1)
            {
                script += ", ";
            }
        }
        script += ");";
        return await webView2.ExecuteScriptAsync(script);
    }
}


Enter fullscreen mode Exit fullscreen mode

Now, from the NavigationCompleted, you can load it in the old way:



 _ = await webView1.ExecuteScriptFunctionAsync("UpdateData", arg);


Enter fullscreen mode Exit fullscreen mode

With all these steps, you can do it again your entire process.

Follow me on:

Personal LinkedIn YouTube Instagram Cyber Prophets Sharing Your Stories
Personal LinkedIn YouTube Instagram RedCircle Podcast RedCircle Podcast

sponsor me

Banner credits:

The Windows Club

💖 💪 🙅 🚩
fanmixco
Federico Navarrete

Posted on October 16, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

From WebView to WebView2 in UWP
webview2 From WebView to WebView2 in UWP

October 16, 2022