lundi 15 août 2016

Communicating back and forth with a WebView in a UWP application using AddWebAllowedObject or ScriptNotify [HTML,C#]

There are many solutions to the problem, in this article I will try and explain why I I think that using a Windows Runtime Component library is better then using a window.external.notify to try and communicate with your WebView in a UWP application.


Using window.external.notify() and ScriptNotify 

Trying to get your application and you HTML page to communicate with each other is never easy and the WebView in UWP doesn't seem to really help us out (IMO of course).  If you try to use window.external.notify(in your WebView not only do you need the link to be in HTTPS but also need to declare the https link(s) in the manifest of your application and need to have access to the html page to add IE/EDGE specific code.

FYI: the you can only declare HTTPS links in Content URi that is why you need your webview link to be HTTPS.

And so your Content URIs might look like this for example:


Also using window.external.notify would force us to have to listen to the ScriptNotify event, and parse the value to know what method we need to call, it would look something like this:

public MainPage()
{
       this.InitializeComponent();
       string src = " https://XXXXXXXXX.com/";
       webView.Navigate(new Uri(src));
       webView.ScriptNotify += webView_ScriptNotify;
}


void webView_ScriptNotify(object sender, NotifyEventArgs e)
{
    var elementPassed =  e.Value; //need to parse this
    
    // now we need to parse our value to know what method needs to be called
    string elementName = elementPassed[0];
    string elementValue = elementPassed[1];
}

For me this was not an option, I wanted to be able to call my C# methods from within my html app.


Looking at MSDN we can see that WebView.AddWebAllowedObject method will Adds a ''native Windows Runtime object as a global parameter to the top level document inside of a WebView''.  This sounds EXACTLY like what I want: to be able to call C# code from within my local html page using javascript!

Creating the Windows Runtime Component library to communicate with the WebView

So let get started, first we need to create a new project Windows Runtime Component library.  For some weird reason you need to add create this in new WRC library and mark this class this  [AllowForWeb] so that you can bypass the WebView security subsystem... (weird but I guess MS must have a good reason for doing this?).

Here is what my Windows Runtime Component library looks like, it only have one class called HtmlCommunicator that is a public sealed class and the attribute AllowForWeb.

using System;
using Windows.ApplicationModel;
using Windows.Foundation.Metadata;


namespace MainApp.Connector
{
    [AllowForWeb]
    public sealed class HtmlCommunicator
    {
        public void getHtmlSpecificEvent()
        {
          // do something else
        }

        public string getAppVersion()
        {
            PackageVersion version = Package.Current.Id.Version;
            return String.Format("{0}.{1}.{2}.{3}",
                                 version.Major, version.Minor, version.Build, version.Revision);
        }
    }
}


Getting the Html page to communicate with our C# code

Lets start with the our main page of our XAML app, I will add a simple WebView to the app:

<Page
    x:Class="MainApp.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:MainApp"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <WebView VerticalAlignment="Bottom" 
             NavigationStarting="HtmlWebView_NavigationStarting"
             x:Name="HtmlWebView"
             Margin="0"
             Height="146"
             />
    </Grid>
</Page>

Now for the cs part of the page, we will now get our Windows Runtime Component library hocked onto the WebView .  We will need to call AddWebAllowedObject  every time the WebView navigates to a new web page, by doing this we can access the native objects that are in our HtmlCommunicator class:


        private HtmlCommunicator communicationWinRT = new HtmlCommunicator();


        private void HtmlWebView_NavigationStarting(WebView sender, WebViewNavigationStartingEventArgs args)
        {
            this.HtmlWebView.AddWebAllowedObject("SCObject", communicationWinRT);
        }

Lastly we create our html page (because I am lazy I will host the html page inside the application) with 2 methods that will call 2 different c# methods.

<!DOCTYPE html>

<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="utf-8" />
    <title></title>
</head>
<body>

    <button style="width: 200px; height: 60px; font-size: x-large" onclick="HtmlPageEvent()">Get Event</button>

    <button style="width: 250px; height: 60px; font-size: x-large" onclick="HtmlGetAppVersion()">Get App Version</button>

    <script type="text/javascript">
        function HtmlPageEvent() {
            window.SCObject.getHtmlSpecificEvent();
        }

        function HtmlGetAppVersion() {
            var version = window.SCObject.getAppVersion();
            alert(version);
        }
    </script>

</body>
</html>

And there you have, you can now call C# methods from within your local HTML page in you UWP application.

I truly believe that being able to call a specific method from within out HTML page is a better solution then using a window.external.notify() and having to catch the event using ScriptNotify to the route the event and parameters to the correct method.

You can find my solution here
Happy coding.