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

Published:
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
5,238 Views

Comments (0)

Have a question about something in this article? You can receive help directly from the article author. Sign up for a free trial to get started.