Limitations of the WebView in Windows 8 Metro Apps

Limitations of the WebView in Windows 8 Metro Apps

Having worked quite intimately with the WebBrowser control in Windows Phone I was a little flawed by how immature and annoying the WebView for Windows 8 metro app is. As noted by Brent Schooley in his post on Metro WebView Source and HTML workarounds you can work around some of the limitations such as the missing SaveToString method. One of the features of the WebBrowser control that I quite often use is the series of events that are raised around navigating to pages within the control. Unlike the WebBrowser, the WebView is missing events such as Navigating, Navigated and NavigationFailed, and has only the LoadCompleted event. I figured that I should be able to detect when the current page is changing, and thus raise a Navigating event. Turns out that it is possible with a combination of InvokeScript (to invoke some javascript within the page) and window.external.notify (to raise an event when the page in the browser is changing). The following WebViewWrapper class exposes a Navigating event which you can wire an event handler to in order to detect when the browser is being navigated away from the current page (unfortunately there doesn’t seem to be a way to extract the target url, nor to be able to cancel the navigation, unlike the real Navigating event that the WebBrowser control has). I’ve also included a SaveAsString method which wraps the code Brent had in his post.

public class WebViewWrapper
{
    // Maintain a reference to the WebView control so that
    // we can invoke javascript
    public WebView WebView { get; private set; }

    public WebViewWrapper(WebView webView)
    {
        WebView = webView;
    }

    // The Navigating event is a custom event, allowing us to hook/unhook
    // from the ScriptNotify and LoadCompleted events. To invoke this
    // event, we actually invoke the internalNavigating event.
    private event EventHandler<NavigatingEventArgs> internalNavigating;
    public event EventHandler<NavigatingEventArgs> Navigating
    {
        add
        {
            WebView.ScriptNotify+=NavigatingScriptNotify;
            WebView.LoadCompleted+=WireUpNavigating;
            internalNavigating += value;
        }
        remove
        {
            WebView.ScriptNotify -= NavigatingScriptNotify;
            WebView.LoadCompleted-=WireUpNavigating;
            internalNavigating -= value;
        }
    }

    // When each page loads, run a javascript function which wires up
    // an event handler to the onbeforeunload event on the window.  This
    // event is raised when the window is about to unload the current page
    // In the event handler we call window.external.notify in order to raise
    // the ScriptNotify event on the WebView. The javascript function also
    // returns the current document location. This is used to update the
    // AllowedScriptNotifyUris property on the WebView in order to permit
    // the current document to call window.external.notify (remembering
    // that even though we injected the javascript, it’s being invoked in the
    // context of the current document.
    private void WireUpNavigating(object sender, NavigationEventArgs e)
    {
        var unloadFunc = "(function(){" +
                            " function navigating(){" +
                            "  window.external.notify(‘%%’ + location.href);" +
                            "} " +
                            "window.onbeforeunload=navigating;" +
                            "return location.href;" +
                            "})();";
        var host = WebView.InvokeScript("eval", new[] { unloadFunc });
        WebView.AllowedScriptNotifyUris = new[] { new Uri(host) };
    }

   // Check to see if the ScriptNotify was raised by the javascript we
   // injected (ie does it start with %%), and then raise the Navigating
   // event.
   private void NavigatingScriptNotify(object sender, NotifyEventArgs e)
    {
        if (internalNavigating == null) return;
        if(!string.IsNullOrWhiteSpace(e.Value))
        {
            if(e.Value.StartsWith("%%"))
            {
                internalNavigating(this, new NavigatingEventArgs() {LeavingUri = new Uri(e.Value.Trim(‘%’))});
            }
        }
    }

    public string SaveToString()
    {
        try
        {
            var retrieveHtml = "document.documentElement.outerHTML;";
            var html = WebView.InvokeScript("eval", new[] { retrieveHtml });
            return html;
        }
        catch
        {
            return null;
        }
    }
}

public class NavigatingEventArgs:EventArgs
{
    public Uri LeavingUri { get; set; }
}

Actually making use of the WebViewWrapper class is as simple as creating an instance and then wiring up an event handler.

public BlankPage()
{
    this.InitializeComponent(); 

    var wrapper = new WebViewWrapper(MyWebView);
    wrapper.Navigating += WrapperNavigating;
}

void WrapperNavigating(object sender, NavigatingEventArgs e)
{
    Debug.WriteLine(e.LeavingUri);
    Debug.WriteLine((sender as WebViewWrapper).SaveToString());
}

Timeout of HttpWebrequest and WCF service calls on Windows Phone

Timeout of HttpWebrequest and WCF service calls on Windows Phone

Firstly, I’m ashamed that the Windows Phone team didn’t do a better job of providing an ability to set a Timeout for either HttpWebRequest, WebClient and WCF service requests. You might be thinking…. but it’s Silverlight and everything’s Async all the time. Well you’d be correct in that thought process but just because something is asynchronous, doesn’t mean that the user isn’t sitting there waiting for something to complete.

For example say you have a simple form that the user has to fill in before they can proceed (eg “create user” form). Clearly if they are on a good network then you’d hope that the service request returns almost instantaneously and the user is cleared to proceed within the app. However, if for whatever reason the request takes longer than expected (slow network, busy server etc) you don’t want the user to sit there indefinitely. After about 10-20 seconds they’re going to get frustrated and probably close and then uninstall your application. Simple solution is to put a Timeout on your service calls so that you can notify that there is something wrong and that they should try again later or check their connection.

Simple you say…. not so. There is no Timeout property that you can set on either HttpWebRequest, WebClient or on the WCF proxy classes that are generated by Add Service Reference. Luckily there are a number of good posts already on the web that cover the basics of implementing a timeout for doing HttpWebRequests (for example this post on stackoverflow).

What isn’t as well covered is how to implement a Timeout for WCF service calls. The challenge with implementing a Timeout property is trying to wrap the asynchronous pattern that the WCF proxy generates. For example if you have a service method called “Authenticate” the proxy will have a AuthenticateAsync method and an AuthenticateCompleted event. Attempting to use these to implement a Timeout is going to result in some very messy code. The other thing to consider is that .NET is evolving towards having true asynchronous support in the form of asynchronous method support. This is built in for WinRT and can be incorporated into your Windows Phone applications today using the Async CTP.

In this post we’re going to use the Async CTP to do two things. Firstly, wrap the fictitious Authenticate service call into an asynchronous operation and secondly give it Timeout support, again through the use of an async operation. The code is implemented as a partial class to the proxy class that is generated when you do  “Add Service Reference”. It does required you to implement the first method for each service method that you want to wrap.

namespace PhoneApp30.MyServices
{
    public partial class SimpleServiceClient    {
        public async Task<string> Authenticate(string username, string password, object state = null)
        {
            return await Async( (cb,st)=>
                Channel.BeginAuthenticate(username,password,cb,st),
                Channel.EndAuthenticate, state);
        }
        private async Task<TResult> Async<TResult>(
            Func<AsyncCallback, object, IAsyncResult> beginMethod,
            Func<IAsyncResult, TResult> endMethod,
            object state)
        {
         
            var method = Task<TResult>.Factory.FromAsync(
                beginMethod,
                endMethod, state);
            return await InvokeWithTimeout(method);
        }
        public async Task<TResult> InvokeWithTimeout<TResult>(Task<TResult> method)
        {
            var newTask = Task.Factory.StartNew(() =>
            {
                if (!method.Wait(30*1000))
                {
                    Abort();
                    throw new TimeoutException();
                }
                return method.Result;
            });
            return await newTask;
        }
    }
}

Now we can call the Authenticate method from our code in a relatively synchronous manner. Note the difference is that this won’t block the UI as it is an asynchronous operation.

async void MainPage_Loaded(object sender, RoutedEventArgs e)
{
    try{
    var client = new SimpleServiceClient();
    var result = await client.Authenticate();
    Console.WriteLine(result);
    }
    catch(TimeoutException ex){
        // Handle timeout 
    }
}