Showing POST Response in a WebView Control

June 10th 2013 HTTP Windows Store

In the world of connected applications programmatic HTTP requests are more and more common. In Windows Store applications HttpClient class serves most purposes as long as communication is targeted at services or at least the results are processed programmatically and don't need to be shown directly to the user:

var http = new HttpClient();
// GET request
var getRequest = "http://hroch486.icpf.cas.cz/cgi-bin/echo.pl?key=value";
var getResponse = await http.GetAsync(getRequest);
var getResult = await getResponse.Content.ReadAsStringAsync();
// POST request
var postUri = new Uri("http://hroch486.icpf.cas.cz/cgi-bin/echo.pl");
var postFields = new Dictionary<string, string>
  {
    { "key", "value" }
  };
var postContent = new FormUrlEncodedContent(postFields);
var postResponse = await http.PostAsync(postUri, postContent);
var postResult = await postResponse.Content.ReadAsStringAsync();

There are cases, though, when its necessary to display the response to the user unchanged in a web browser, e.g. when displaying search results he will use as a starting point for further navigation. The simplest approach would be to just display the string response as HTML in a WebView control:

MyWebView.NavigateToString(postResult);

Unfortunately this doesn't work as expected in all but the simplest cases. As soon as there are any relative links included in the page or external files referenced, the page won't work correctly. The browser needs to be aware of the actual page URI. For GET requests there is built in support in WebView control:

MyWebView.Navigate(new Uri(getRequest));

POST requests are a tougher nut to crack. There's no method available to directly invoke it on a WebView control, therefore we need to find another approach. Since the POST request needs to be sent from the WebView context, InvokeScript is a good candidate as long as we can ensure a Javascript method invoking the required request. This means making the POST request will be a two step process:

  1. Display a web page containing custom HTML and Javascript required for the POST request in the WebView control.
  2. Automatically invoke the POST request by calling the Javascript method.

We can wrap all that in a WebView extension method to make it easier to use:

public static void PostAndNavigate(this WebView webView, 
  string requestUri, IEnumerable<KeyValuePair<string, string>> formFields)
{
  var formElements = String.Join("", 
    formFields.Select(
      p => String.Format(@"<input type=""hidden"" value=""{0}"" name=""{1}"">", 
      p.Value, p.Key)));
  var html = String.Format(
    @"<html>
      <head>
        <script type=""text/javascript"">
          function Submit() 
          {{ 
            document.getElementById('pay').submit(); 
          }} 
        </script>
      </head>
      <body>
        <form id=""pay"" method=""POST"" action=""{0}"">
          {1}
        </form>
      </body>
    </html>", requestUri, formElements);

  LoadCompletedEventHandler loadCompletedDelegate = null;
  loadCompletedDelegate = (s, e) =>
    {
      webView.LoadCompleted -= loadCompletedDelegate;
      webView.InvokeScript("Submit", null);
    };

  webView.NavigateToString(html);
  webView.LoadCompleted += loadCompletedDelegate;
}

There's an additional complication in the above code because InvokeScript doesn't find the Javascript method if it is called immediately after NavigateToString. The web page must first complete loading for the call to succeed, therefore an event handler is required.

Copyright
Creative Commons License