Et irritationsmoment for brugere af webapplikationer er ventetiden ved postbacks. Der findes en lang række situationer, hvor det kræver et postback midtvejs i udfyldelse af en formular, f.eks. når man vælger et land i en dropdown boks hvor regioner/stater så skal fremgå i en efterfølgende dropdown boks. Løsningen på det problem hedder XMLHTTP, hvor javascript på html-siden sender et request via XMLHTTP, får et svar retur og f.eks. udfylder regions dropdown boksen.
I ASP.NET 2.0 er der direkte support for XMLHTTP callbacks. For at udnytte dette kræver det lidt javascript på klienten samt at aspx-siden implementerer System.Web.UI.ICallbackEventHandler - lad os se på et eksempel skrevet til VS 2005:
Et eksempel
Ofte bliver man på en webside bedt om at taste en adresse ind og herunder postnummer og by. Lad os hjælpe brugeren lidt og spare ham/hende for at skulle taste bynavnet ind:
Denne aspx side indeholder 3 tekstfelter: txtAddress, txtPostalCode og txtCity. På txtCity er propertien ReadOnly sat til True. Når der indtastes en værdi i txtPostalCode og feltet herefter forlades, udfyldes txtCity på baggrund af returværdien fra et XMLHTTP Callback.
Server side
På serversiden skal klassen Default.aspx.vb implementere ICallbackEventHandler og dennes enlige metode: RaiseCallbackEvent. Der er kun én RaiseCallbackEvent metode pr. aspx-side, så hvis siden skal håndtere forskellige XMLHTTP Callbacks skal vi sørge for at input strengen (eventArgument) indeholder informationer om, hvilken metode vi ønsker udført på serveren. I eksemplet herunder indikeres hvilken metode der er tale om ved at eventArgument-strengen starter med "PostalCode:".
Ikke alle browsere understøtter denne type af callback. Der er kommet to nye properties SupportsCallback og SupportsXmlHttp på klassen HttpCapabilitiesBase, som man kan bruge til at undersøge, hvorvidt den aktuelle browser understøtter callback.
I Page_Load kigger vi på om XMLHTTP er understøttet af browseren og sætter i så fald et clientside event op, så der ved ændring i txtPostalCode tekstfeltet kaldes ResolveCity javascript metoden. På en web-page kan man få fat i en ClientScriptManager og via den generere en streng ved hjælp af GetCallbackEventReference. Denne streng kommer til at se ud i retning af WebForm_DoCallback('__Page',Command,CallBackHandler,context,onError,true) og den skal vi bruge om et øjeblik, når vi ser på klient koden.
Partial Class _Default
Inherits System.Web.UI.Page
Implements System.Web.UI.ICallbackEventHandler
Protected _callbackstr As String
Protected _eventArgument As String
Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If (Request.Browser.SupportsXmlHttp) Then
txtPostalCode.Attributes.Add("OnChange", "ResolveCity();")
_callbackstr = Page.ClientScript.GetCallbackEventReference(Me, "Command", "CallBackHandler", "context", "OnError", True)
Else
txtCity.ReadOnly = False
End If
End Sub
Private Sub RaiseCallbackevent(ByVal eventArgument As String) Implements ICallbackEventHandler.RaiseCallbackEvent
_eventArgument = eventArgument
End Sub
Private Function GetCallbackResult() As String Implements ICallbackEventHandler.GetCallbackResult
Dim unknown As String = ""
If _eventArgument.StartsWith("PostalCode:") Then
Return PostalCodeUtil.GetCity(_eventArgument.Substring("PostalCode:".Length), unknown)
Else
Return unknown
End If
End Function
End Class
public partial class _Default : System.Web.UI.Page, System.Web.UI.ICallbackEventHandler
{
protected string _callbackstr;
protected string _eventArgument = "";
protected void Page_Load(object sender, System.EventArgs e)
{
if (Request.Browser.SupportsXmlHttp)
{
txtPostalCode.Attributes.Add("OnChange", "ResolveCity();");
_callbackstr = Page.ClientScript.GetCallbackEventReference(this, "Command", "CallBackHandler", "context", "OnError", true);
}
else
{
txtCity.ReadOnly = false;
}
}
void System.Web.UI.ICallbackEventHandler.RaiseCallbackEvent(string eventArgument)
{
_eventArgument = eventArgument;
}
string System.Web.UI.ICallbackEventHandler.GetCallbackResult()
{
string unknown = "";
if (_eventArgument.StartsWith("PostalCode:"))
{
return PostalCodeUtil.GetCity(_eventArgument.Substring("PostalCode:".Length), unknown);
}
else
{
return unknown;
}
}
}
Understøttelse for Firefox
ASP.NET 2.0 går automatisk ud fra at Firefox ikke understøtter XMLHTTP. Dette resulterer i at Request.Browser.SupportsXmlHttp altid returnerer false i Firefox, og dermed sættes XMLHTTP Callback ud af spillet. Det forholder sig rent faktisk sådan at der ER understøttelse for XMLHTTP i Firefox. Men for at få det til at virke, er det nødvendigt at tilføje følgende til sin web.config:
<browserCaps>
<!-- GECKO Based Browsers (Netscape 6+, Mozilla/Firebird, ...) //-->
<case match="^Mozilla/5\.0 \([^)]*\) (Gecko/[-\d]+)? (?'type'[^/\d]*)([\d]*)/(?'version'(?'major'\d+)(?'minor'\.\d+)(?'letters'\w*)).*">
browser=Gecko
type=${type}
frames=true
tables=true
cookies=true
javascript=true
javaapplets=true
ecmascriptversion=1.5
w3cdomversion=1.0
css1=true
css2=true
xml=true
tagwriter=System.Web.UI.HtmlTextWriter
supportsXmlHttp=true
supportsCallback=true
<case match="rv:(?'version'(?'major'\d+)(?'minor'\.\d+)(?'letters'\w*))">
version=${version}
majorversion=${major}
minorversion=${minor}
<case match="^b" with="${letters}">
beta=true
</case>
</case>
</case>
</browserCaps>
browserCaps giver mulighed for at angive hvilke egenskaber en specifik type browser har. Når ASP.NET identificerer en browser type, ser den om der er angivet en definition af denne browsers egenskaber i web.config. Findes denne type browsers definition ikke her, benyttes en default configuration.
Client side
På klient siden undgår vi ikke helt at gribe til javascript ;-), men det er dog begrænset, hvad der skal til i dette eksempel. Funktionen ResolveCity opretter en kommando ved at aflæse indholdet af txtPostalCode og udfører så herefter XMLHTTP Callbacket gennem den kode der blev genereret ved hjælp af GetCallbackEventReference-metoden og gemt i variablen _callbackstr i Form_Load. I denne funktion er det også sat op, at det er CallBackHandler der kaldes, hvis XMLHTTP kaldet går godt og OnError hvis der opstår en Exception på serveren.
Der kan kun være én CallBackHandler metode i klienten, så har man flere forskellige XMLHTTP callbacks på samme side så benyttes context.CommandName til at afgøre hvilken af metoderne der er tale om.
<%@ Page Language="VB" AutoEventWireup="false" ... %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" ... >
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Callback Demo</title>
</head>
<body>
<script>
function ResolveCity()
{
var Command = "PostalCode:" + document.forms[0].elements['txtPostalCode'].value;
var context = new Object();
context.CommandName = "ResolveCity";
<%= _callbackstr %>
}
function CallBackHandler(result, context)
{
if (context.CommandName == "ResolveCity" )
{
document.forms[0].elements['txtCity'].value = result;
}
}
function OnError(message, context)
{
alert("Exception :\n" + message);
}
</script>
<form id="form1" runat="server">
...
</form>
</body>
</html>
Download eksemplet i en zip fil: CallbackDemo.zip. Zipfilen indeholder både en C# og en VB udgave.