Getting the Back Button to work with Ajax
Microsoft's Asp.Net Ajax makes it easy to create a web page that can refresh itself with out posting back. Unfortunately when a web page updates itself using ajax the user is unable to press the back button to get back to what is was on the page before. In this article I will first show to create an ajax enable website which uses a webservice to update itself. Then we will make the back button work properly.
First create a new AjaxEnabledWebsite and lets start off by adding a webservice to the Project named Customers. This web service will connect to the northwind database and return the company name for a customer id. To make this webservice work with ajax we have to make the web service a ScriptService.
Imports System.Web
Imports System.Web.Services
Imports System.Web.Services.Protocols
Imports System.Data.SqlClient
Imports System.Data
Imports System.Text
<WebService(Namespace:="http://tempuri.org/")> _
<WebServiceBinding(ConformsTo:=WsiProfiles.BasicProfile1_1)> _
<Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()> _
<Microsoft.Web.Script.Services.ScriptService()> _
Public Class Customers
Inherits System.Web.Services.WebService
<WebMethod()> _
Public Function GetName(ByVal strID As String) As String
Dim strOut As New StringBuilder
Dim connStr As String
connStr = "Server = .;Database = Northwind; integrated security=sspi;"
Dim conn As New SqlConnection(connStr)
Dim cmd As New SqlCommand
Try
conn.Open()
If strID <> "" Then
cmd = New SqlCommand("Select CompanyName From Customers where Customerid = @CustomerID", conn)
cmd.Parameters.AddWithValue("@CustomerID", strID)
strOut.Append("<html><body>")
strOut.Append(cmd.ExecuteScalar.ToString())
strOut.Append("</body></html>")
End If
Catch ex As Exception
strOut.Append("<H1>Database Error</H1>")
Finally
conn.Close()
End Try
Return strOut.ToString
End Function
End Class
We also have to make a change to the httpHandlers section of the web.config file for this to work.
<httpHandlers>
<remove verb="*" path="*.asmx"/>
<add verb="*" path="*.asmx" validate="false" type="Microsoft.Web.Script.Services.ScriptHandlerFactory, Microsoft.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
<add verb="GET" path="ScriptResource.axd" type="Microsoft.Web.Handlers.ScriptResourceHandler" validate="false"/>
</httpHandlers>
Lets set up default.aspx to use the webservice for updating its content. First we have to register the webservice with the pages script manager.
<asp:ScriptManager ID="ScriptManager1" runat="server">
<Services>
<asp:ServiceReference Path="~/Customers.asmx" /></Services>
</asp:ScriptManager>
Now we need to make a callback function in java script for the web service. This function will display the results.
<script language="javascript" type="text/javascript">
function displayName(results)
{
$get("CompanyName").innerHTML = results;
}
</script>
Of course we have to add a few controls to the form to display the data. Here is the html for default.aspx.
<%@ Page Language="VB" AutoEventWireup="true" CodeFile="Default.aspx.vb" Inherits="_Default" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Untitled Page</title>
<script language="javascript" type="text/javascript">
function displayName(results)
{
$get("CompanyName").innerHTML = results;
}
</script>
</head>
<body>
<form id="form1" runat="server">
<asp:ScriptManager ID="ScriptManager1" runat="server">
<Services>
<asp:ServiceReference Path="~/Customers.asmx" /></Services>
</asp:ScriptManager>
<div>
<asp:Label ID="Label1" runat="server" Text="Label"></asp:Label>
<table>
<tr>
<td>
<asp:DataList ID="DataList1" runat="server" DataKeyField="CustomerID" DataSourceID="SqlDataSource1">
<ItemTemplate>
<a href="javascript:Customers.GetName('<%# Eval("CustomerID") %>', displayName)">
<%# Eval("CustomerID") %>
</a>
<br />
</ItemTemplate>
</asp:DataList>
<asp:SqlDataSource ID="SqlDataSource1" runat="server" ConnectionString="<%$ ConnectionStrings:NorthWindConnectionString %>"
SelectCommand="SELECT [CustomerID] FROM [Customers]"></asp:SqlDataSource>
</td>
<td style='vertical-align:top; width: 50%;'>
<div id="CompanyName" >
</div>
</td>
</tr>
</table>
</div>
</form>
</body>
</html>
On the page we have a label lblNow which I use for displaying the time the page was displayed. Here is the code behind file which updates the label
Partial Class _Default
Inherits System.Web.UI.Page
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
lblNow.Text = Now.ToLongTimeString
End Sub
End Class
So if we run the project you will see a list of the northwind product categories. When you click on one of the customer id it will call a java script function which will display the company name. You can see by the fact the time is not changing in the label the page is not posting back.
The browsers back button remembers when a webpage loads, posts back, or the page loaded in an IFrame changes. So for the back button to work with ajax we need to add a hidden IFrame to the page. The trick here is to get the page we navigate to in the iframe to update our webpage.
Add a new webpage to the project named History.aspx. In the call back function we are using parent.document to change the div on the main form. Here is the html for the page.
<%@ Page Language="VB" AutoEventWireup="false" CodeFile="history.aspx.vb" Inherits="history" %>
<!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 runat="server">
<title>Untitled Page</title>
<script language="javascript" type="text/javascript">
function GetID()
{
var url=window.location.href;
var idstring=url.split("?")[1];
if(!idstring)
return null;
idstring=idstring.substr(("ID=").length);
idstring=unescape(idstring);
return idstring;
}
function displayResult(results)
{
parent.document.all.item("CompanyName").innerHTML = results;
}
function body_onload()
{
var idstring=GetID();
if(idstring)
{
Customers.GetName(idstring, displayResult);
}
}
</script>
</head>
<body onload="body_onload();">
<form id="form1" runat="server">
<div>
<asp:ScriptManager ID="ScriptManager1" runat="server">
<Services>
<asp:ServiceReference Path="~/Customers.asmx" />
</Services>
</asp:ScriptManager>
</div>
</form>
</body>
</html>
Now we need to make some changes to default.aspx. First we have to add a hidden iframe to the form.
<iframe id="hiddeniframe" src="history.aspx" style='visibility: hidden;'></iframe>
Finally we have to get the link to change the page in the iframe instead of calling the web service. Here is the updated pages html.
<%@ Page Language="VB" AutoEventWireup="true" CodeFile="Default.aspx.vb" Inherits="_Default" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Untitled Page</title>
<script language="javascript" type="text/javascript">
function ChangeFrame(id)
{
window.frames["hiddeniframe"].location.href ="history.aspx?ID=" + id;
}
</script>
</head>
<body>
<form id="form1" runat="server">
<asp:ScriptManager ID="ScriptManager1" runat="server">
</asp:ScriptManager>
<div>
<asp:Label ID="Label1" runat="server" Text="Label"></asp:Label>
<table>
<tr>
<td>
<asp:DataList ID="DataList1" runat="server" DataKeyField="CustomerID" DataSourceID="SqlDataSource1">
<ItemTemplate>
<a href="javascript:ChangeFrame('<%# Eval("CustomerID") %>')">
<%# Eval("CustomerID") %>
</a>
<br />
</ItemTemplate>
</asp:DataList>
<asp:SqlDataSource ID="SqlDataSource1" runat="server" ConnectionString="<%$ ConnectionStrings:NorthWindConnectionString %>"
SelectCommand="SELECT [CustomerID] FROM [Customers]"></asp:SqlDataSource>
</td>
<td style='vertical-align:top; width: 50%;'>
<div id="CompanyName" >
</div>
</td>
</tr>
</table>
<iframe id="hiddeniframe" src="history.aspx" style='visibility: hidden;'></iframe>
</div>
</form>
</body>
</html>
Now when the user clicks on the link it calls a webpage in a hidden iframe which updates the main page. The user now can use the back button