<

Go Premium for a chance to win a PS4. Enter to Win

x

ASP.NET code behind incompatible with back() in Android browser

Published on
10,677 Points
4,077 Views
1 Endorsement
Last Modified:
User art_snob encountered strange behavior of Android Web browser on his Mobile Web site. It took a while to find the true cause. It happens so, that the Android Web browser (at least up to OS ver. 2.3.3) tends to ignore the no-cache HTTP headers, and decides at any rate to avoid page reload. This causes severe problems with ASP.NET, which relies on synchronization of code-behind (e.g. a.aspx.vb) with the Web page (a.aspx).

Here is a stripped-down example:
a.aspx
<%@ Page Language="VB" CodeFile="a.aspx.vb" Inherits="a" EnableViewStateMac="False" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<HTML xmlns="http://www.w3.org/1999/xhtml">

<HEAD id="Head1" runat="server">
	<TITLE>a.aspx</TITLE>
	<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
</HEAD>

<BODY>
<FORM id="form1" runat="server">
	<div id="container">
		<asp:ScriptManager ID="ScriptManager1" runat="server">
		</asp:ScriptManager>  
		<asp:UpdatePanel ID="UpdatePanel1" runat="server" UpdateMode="Always">
			<ContentTemplate>
				<asp:Button ID="ServerButton" runat="server" OnClick="Button_Click" text="go to b.html on server" /><p />
				<asp:Button ID="ClientButton" runat="server" onClientClick="location = 'b.html'" text="go to b.html on client" /><p />
			</ContentTemplate>
		</asp:UpdatePanel>
	</div>
</FORM>
</BODY>
</HTML>

Open in new window

a.aspx.vb
Partial Class a
    Inherits Page

    Protected Sub Button_Click(ByVal sender As Object, ByVal e As EventArgs) Handles ServerButton.Click
        Response.Redirect("b.html", False)
    End Sub

End Class

Open in new window

Now, on any known browser, the ServerButton and the ClientButton produce the same result:
b.html
<html>
	<head>
		<title>b.html</title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
	</head>
	<body>
			<input name="goback" onclick="history.back()" type="button" value="go back" /></p>
			<input name="gotoA" onclick="location='a.aspx'" type="button" value="go to a.aspx" /></p>
			<input name="replaceA" onclick="location.replace('a.aspx')" type="button" value="replace to a.aspx" /></p>
			<input name="openA" onclick="window.open('a.aspx', 'top')" type="button" value="open a.aspx" /></p>
	</body>
</html>

Open in new window

And on all known browsers, all four buttons in b.html display a.aspx again and let you repeat the process. Except goback button and the Android browser.
If you start at a.aspx and then click goback, or the system Back button, you will be still able to use the  ClientButton, but the ServerButton will not work at all. Under the hood it will get an ASP.NET exception:
E/browser (xxx): Console: Uncaught Sys.WebForms.PageRequestManagerServerErrorException: Sys.WebForms.PageRequestManagerServerErrorException: An unknown error occurred while processing the request on the server. The status code returned from the server was: 0 http://xxx/ScriptResource.axd?d=31hrpqnrjCS7Ljksbfakhjr6vQHbaDyxah__zHgcOttSylOBq8Zbq2kxgVMdA3MThu4aquTF9WtwbL8sY19YcnLh9RlqCkMMQlaKeoC5pMVR-tgb3iU8M6inww0QtoQYLpIsOrH70stFuO0SE_LVrEyvT7miOJgBVPvhCD-3ekjwH95jk8pHDxgtqXOa4aGu0&t=2610f696:5

Open in new window

I have explained above what causes this problem. Now our task is to find an easy and reliable plumbing.
I will use an XMLHttpRequest to verify that the copy of the page is not coming from local cache, but is fresh from the server. This way, we can guarantee that code behind will be live.
Here is the Page_Load procedure in a.aspx.vb:
    Protected Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs) Handles Me.Load
        Dim uni = Request.QueryString("getUnique")
        If uni = "" Then
           HttpContext.Current.Session("QQQ") = Now().toString("ms")
        Else
           If (HttpContext.Current.Session("QQQ") Is Nothing)
              uni = "9999"
           Else
              uni = HttpContext.Current.Session("QQQ")
           End If

           Response.Write(uni)
           Response.Flush()
        End If
    End Sub

Open in new window

and here is the client-side javascript (put it somewhere inside a.aspx):
<script>
        originalServerTime = <%= HttpContext.Current.Session("QQQ") %>

	var request = new XMLHttpRequest();
	request.onreadystatechange = function()
	{
		if (request.readyState == 4)
		{
			newServerTime=parseInt(request.responseText);
console.log("newServerTime: " + newServerTime + " originalServerTime: " + originalServerTime);

			if (newServerTime != originalServerTime) // if not using Session persistence, allow for up to 2 sec delay
			{
				location.replace(location.href);
			}
		}
	};
	var ser = Math.round(Math.random()*1000000); // Anti-caching random number
	request.open('GET', location.href + '?' + 'getUnique=' + ser, true);
	request.send(null);
</script>

Open in new window

So, we generate a new request, make sure that it is never cached (with random getUnique), and retrieve the value that should be identical to the one embedded with <%= %> when the page was generated. If the values differ, the page has lost its connection to code behind.
We reload the page by force, and use location.replace() to replace the unhealthy page in the browser history.

Actually, you could choose to do this without using the Session object. Simply generate the server time on the fly in response to the request. Unfortunately, this approach is less robust: you must leave a couple of seconds "freedom" because the server may be too busy between the two requests, and the client may decide to press Back button really quickly.
1
Comment
Author:alexcohn
0 Comments

Featured Post

Industry Leaders: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

Join & Write a Comment

Is your data getting by on basic protection measures? In today’s climate of debilitating malware and ransomware—like WannaCry—that may not be enough. You need to establish more than basics, like a recovery plan that protects both data and endpoints.…
Screencast - Getting to Know the Pipeline

Keep in touch with Experts Exchange

Tech news and trends delivered to your inbox every month