客户回调的工作原理(转载)
注明:本篇转自We Will be the One 的 客户端回调的工作原理
客户程序调用使用CallBackManager的客户端方法。CallBackManager创建了对服务器上的.aspx页面的请求。服务器处理该页面和它的所有事件,直到Pre_Render事件为止,然后调用服务器端方法。接着,CallBackManager解析服务器响应,执行一个回调方法,它是一个客户端方法。图4-1演示了客户回调。
图 4-1
CallBackManager必须知道客户端回调方法的名称,才能在调用服务器端方法,由CallBackManager解析其响应时,确定在客户端应该调用哪个方法。为了在处理服务器端方法后指定调用哪个客户端方法,可以使用Page类的GetCallBackEventReference方法。GetCallBackEventReference方法在ClientSideScriptManger类中,可以从Page类的ClientScript属性中访问:
Page.ClientScript.GetCallbackEventReference(…)
使用GetCallBackEventReference,还可以指定在服务器抛出异常时CallBackManager应调用哪个方法。本章后面将详细介绍GetCallBackEventReference方法。
客户回调特性有一个限制:不能指定应执行服务器上的哪个方法。客户回调特性将执行服务器上的RaiseCallbackEvent方法,它在ICallbackEventHandler接口中定义。因此,在使用客户回调特性执行服务器端方法时,必须执行RaiseCallbackEvent方法,给该方法添加自己的服务器端逻辑。由于不能指定要执行哪个服务器端方法,就只能执行服务器端代码,把结果返回给客户。如果要指定调用哪个方法,就可以给RaiseCallbackEvent传送一个参数,来指定调用哪个方法,并在RaiseCallbackEvent中处理它。如下面的例子所示:
public string RaiseCallbackEvent(string eventArgs)
{
if (eventArgs == "Method1")
//call method 1
else if (eventArgs == "Method2")
//call method 2
...
}
根据控件调用不同服务器端方法的另一个解决方案是通过定制控件来使用客户回调特性。为此,可以有不同的RaiseCallbackEvent方法,它们根据页面上使用的控件来执行。
下面看看客户回调特性如何由Web窗体实现和使用。接下来的例子将使用客户回调特性在服务器上进行一个验证,但不执行回送。服务器端方法将验证用户在文本框中输入的数字是否在1到1000之间。如果文本框的值不是一个数字,或者该数字不在1到1000之间,就显示一个包含错误消息的消息框(在服务器端发生异常时,会在客户机上执行指定的客户端错误回调方法):
<%@ page language="C#" %>
<%@ implements interface="System.Web.UI.ICallbackEventHandler" %>
<script runat="server">
private string _returnFromServer;
public void RaiseCallbackEvent(string eventArgs)
{
try
{
int value = Int16.Parse(eventArgs);
if( value >= 1 && value <= 1000 )
this. _returnFromServer = "You entered the number: " +
eventArgs;
else
this. _returnFromServer = "Please enter a number between 1 and
1000";
}
catch
{
throw new ApplicationException("You must enter a number.");
}
}
public string GetCallbackResult()
{
return this._returnFromServer;
}
public void Page_Load(object sender, EventArgs e)
{
if (!Request.Browser.SupportsCallback)
throw new ApplicationException("This browser doesn’t support Client
callbacks.");
string src = Page.ClientScript.GetCallbackEventReference(
this,
"arg",
"ClientCallback",
"ctx",
"ClientErrorCallback",
false);
string mainSrc = @"function ValidateNumber(arg, ctx)
{ " + src + "; }";
Page.ClientScript.RegisterClientScriptBlock(
this.GetType(),
"ValidateNumber",
mainSrc,
true);
}
</script>
<html>
<head runat="server">
<title>Client Callback</title>
<script language="javascript">
function Validate()
{
var n = document.forms[0].txtNumber.value;
//Call the server-side method to validate the number
ValidateNumber(n, "txtNumber");
}
//ClientCallback will be executed after the server-side method
//is executed and the server response is parsed.
function ClientCallback( result, context )
{
alert(result);
}
//If the server-side method throws an exception, the
ClientErrorCallback
//will be executed to show the exception.
function ClientErrorCallback( error, context )
{
alert("The validation failed. " + error);
}
</script>
</head>
<body>
<form runat="server">
Please enter a number between 1 and 1000:<br />
<input id="txtNumber" name="txtNumber" type="text"/>
<button id="butValidate" OnClick="Validate()">Validate</button>
</form>
</body>
</html>
下面逐个分析代码的重要部分:
<%@ page language="C#" %>
<%@ implements interface="System.Web.UI.ICallbackEventHandler" %>
必须执行System.Web.UI.ICallbackEventHandler,才能确保在使用客户回调特性时,回调事件在回送过程中处理。ICallbackEventHandler接口也可以由控件执行,例如按钮、文本框等。在本章后面的一个例子中,就是由一个控件使用ICallbackEventHandler的。为了指定Web窗体应执行的接口,可以使用implements指令,该指令的interface特性可用于指定要执行的接口名称。
ICallbackEventHandler的两个方法RaiseCallbackEvent和GetCallbackResult必须执行,才能使回调发挥作用。RaiseCallbackEvent方法带一个参数,它包含客户端方法中的数据,GetCallbackResult方法给客户机返回一个字符串,表示服务器端方法的输出。返回值作为一个参数传送给客户端回调方法。在前面的例子中,RaiseCallbackEvent会检查参数(eventArgs)是否为一个数字,且在1到1000之间。如果不是,就抛出一个异常,在客户端显示一个消息框,告诉用户输入一个在1到1000之间的值。如果参数包含允许的值,就返回该值:
public void RaiseCallbackEvent(string eventArgs)
public string GetCallbackResult()
在使用客户回调特性时,一定要检查用户的浏览器是否支持该特性。为此,可以使用Page类的Browser属性中的SupportsCallback特性。下面的代码就检查客户机是否支持客户回调特性,如果不支持,就抛出一个异常:
if (!Request.Browser.SupportsCallback )
throw new ApplicationException("This browser doesn’t support Client
callbacks.");
示例的下一个重要部分是获得回调事件引用。该引用表示一个要生成的客户端方法,它用于调用服务器端代码。在获得回调事件引用时,需要指定回调方法。这是必需的,以便让客户回调特性知道处理完服务器端方法后,应调用哪个客户端方法。要添加一个引用,应使用Page类的GetCallbackEventReference方法。GetCallbackEventReference有三个重载方法:
public string GetCallbackEventReference(
Control control,
string argument,
string clientCallback,
string context)
public string GetCallbackEventReference(
Control control,
string argument,
string clientCallback,
string context,
string clientErrorCallback)
public string GetCallbackEventReference(
string target,
string argument,
string clientCallback,
string context,
string clientErrorCallback)
表4-1描述了GetCallbackEventReference的参数:
表 4-1
参 数 |
说 明 |
control |
执行IcallbackEventHandler的控件 |
target |
如果没有指定控件,该参数就是执行ICallbackEventHandler的控件的ID |
argument |
要发送给RaiseCallbackEvent的值 |
clientCallback |
客户端回调方法的名称 |
context |
从客户端事件传送回客户端回调方法的值 |
clientErrorCallback |
客户端错误回调方法的名称。这个方法在服务器抛出异常时执行 |
下面的代码使用GetCallbackEventReference指定客户端回调方法和执行ICallbackEvent Handler的控件:
public void Page_Load(object sender, EventArgs e)
{
string src = Page.GetCallbackEventReference(
this,
"arg",
"ClientCallback",
"ctx",
"ClientErrorCallback");
string mainSrc = @"function ValidateNumber(arg, ctx)
{ " + src + "; }";
在上面的例子中,当前页面执行了ICallbackEventHandler,所以当前页面对象作为一个值传送给GetCallbackEventReference方法的控件参数。在执行完服务器端方法,CallBackManager解析完服务器端调用的服务器响应之后,就执行客户端的ClientCallback方法。服务器端方法的返回值作为一个参数传送给客户的Callback方法。如果服务器抛出一个异常,就执行客户端的ClientErrorCallback方法。传送给GetCallbackEventReference的arg和ctx值是客户端事件的参数名称,该客户端事件将调用服务器端方法。参数和回调事件引用创建为一个字符串,添加到代码段ValidateNumber的mainSrc变量中。mainSrc将包含如下字符串:
function ValidateNumber(arg, ctx)
{ WebForm_DoCallback('__Page',arg,ClientCallback,ctx,ClientErrorCallback,
false); }
WebForm.DoCallback方法及其参数由GetCallbackEventReference方法生成。Validate Number事件必须添加到客户机中,作为一个客户端脚本,这样才能由客户端方法调用,或通过HTML控件的事件调用(例如按钮的OnClick事件等)。在ASP.NET 1.x中,客户端脚本可以通过RegisterClientScriptBlock和RegisterStartupScript方法添加到页面上,这两个方法可以通过Page对象访问,在ASP.NET 2.0中,为了保证向后兼容,这两个方法仍可以使用,但本例使用ASP.NET 2.0中新增的RegisterClientScriptBlock方法,把客户端脚本添加到客户机上。这个新方法可以通过当前Page类的新ClientScript属性(ClientScriptManager)访问。ASP.NET 1.x版本中的RegisterClientScriptBlock带两个参数:表示脚本块的唯一键和带客户端脚本的脚本字符串。新方法可以多带两个参数:type和addScriptTags。新的RegisterClientScriptBlock方法有两个重载版本:
Public void RegisterClientScriptBlock(
Type type,
string key,
string script)
Public void RegisterClientScriptBlock(
Type type,
string key,
string script,
bool addScriptTags)
表4-2描述了RegisterClientScriptBlock的参数:
表 4-2
参 数 |
说 明 |
Type |
一种类型,使同一个键可以使用两次 |
Key |
标识脚本块的唯一键 |
Script |
要添加到客户机中的脚本块 |
addScriptTags |
允许方法插入<script>标记 |
调用服务器的客户端方法使用新方法RegisterClientScriptBlock注册:
Page.ClientScript.RegisterClientScriptBlock(
this.GetType(),
"ValidateNumber",
mainSrc,
true);
前面的RegisterClientScriptBlock会生成如下代码,并把它们添加到客户机上:
<script type="text/javascript">
<!--
function ValidateNumber(arg, ctx)
{WebForm_DoCallback('__Page',arg,ClientCallback,ctx,ClientErrorCallback,
false); }// -->
</script>
WebForm_DoCallback方法带5个参数:当前页面,发送到服务器端的RaiseCallbackMethod中的参数值,客户端回调方法,发送给客户端回调方法的context值,以及服务器抛出异常时执行的客户端方法。
WebForm_DoCallback方法可以通过客户端函数直接执行,或从客户端控件事件中直接执行,例如按钮的onClick事件。在本例中,WebForm_DoCallback通过ValidateNumber方法来执行。
下面看看客户端回调方法。Validate方法由按钮控件的OnClick事件执行。这个方法会获得在文本框控件txtNumber中输入的值,并把它作为参数传送给前面介绍的ValidateNumber方法中的arg参数。Validate方法还通过ValidateNumber方法的context参数传送文本框控件的id(context参数的值传送给客户端回调方法)。ClientCallback带两个参数:从RaiseEventCallback方法返回的结果和通过ValidateNumber方法传送的context。在服务器完成其请求,并在消息框中显示返回值时,执行RaiseEventCallback。如果服务器抛出一个异常,就执行客户端的ClientCallErrorCallback方法,在消息框中显示从RaiseErrorEventCallback返回的错误消息。
function Validate()
{
var n = document.forms[0].txtNumber.value;
ValidateNumber(n, "txtNumber");
}
function ClientCallback( result, context )
{
alert(result);
}
function ClientErrorCallback( error, context )
{
alert("The validation failed. " + error);
}
Validate方法关联到按钮的onClick事件上,所以单击该按钮时,就会引发Validate方法,执行服务器端代码:
<form runat="server">
Please enter a number between 1 and 1000:<br />
<input id="txtNumber" name="txtNumber" type="text"/>
<button id="butVaidate" OnClick="Validate()">Validate</button>
</form>
接着看看例子的运行情况,运行这个例子时,会显示一个包含文本框和按钮的页面,该页面提示输入一个1到1000之间的数字。
单击 Validate按钮,就会执行客户回调。服务器端的代码将确定输入的数字是否正确。在服务器端的方法上,输入一个非数字的值会抛出一个异常,显示一个错误消息,请求用户输入一个1到1000之间的数字。如果抛出了异常,就调用客户端的ClientErrorCallback方法。客户端方法会在消息框中显示从服务器返回的错误。
如果验证成功,就执行ClientCallback方法,把文本框中输入的数字显示在消息框中。
从例子中可以看出,不需要通过回送,在客户端代码中执行服务器端方法。这是使用客户回调特性的主要优点。客户回调还可以在控件中实现,如下一节所述。
在定制控件中实现客户回调
不仅Web窗体可以实现和使用客户回调特性,还可以为定制控件使用客户回调特性。例如,如果建立一个Tree控件,它有非常大的节点结构,则在扩展节点,以保存资源时,就可以从数据源中获取子节点(而不是进行回送),并动态添加节点。一个页面上只能有一个RaiseCallbackEvent,所以为定制控件实现客户回调特性,可以确保为同一个页面上的不同控件执行不同的服务器端方法。下面的例子是一个实现客户回调特性的HtmlButton:
using System;
using System.Web.UI;
using System.Web.UI.HtmlControls;
namespace CallbackTest
{
public class CallbackButton : HtmlButton, ICallbackEventHandler
{
private string _value;
public string MyCallBackArgument
{
get
{
string s = (string)ViewState["MyCallBackArgument"];
if (s == null)
return String.Empty;
return s;
}
set
{
ViewState["MyCallBackArgument"] = value;
}
}
protected override void RenderAttributes(HtmlTextWriter writer)
{
base.RenderAttributes(writer);
writer.WriteAttribute("onClick",
Page.ClientScript.GetCallbackEventReference(
this,
this.MyCallBackArgument,
"ButtonCallback",
base.UniqueID,
"ButtonErrorCallback",
false) + "; return false;");
GetCallbackEventReference带6个参数:MyCallBackArgument属性的值,位于客户端ButtonCallback上的回调方法名称,控件的唯一id,context,错误回调,以及回调是否为异步调用。上面的代码将在客户端生成如下代码:
onClick="WebForm_DoCallback('CallbackButton1',TextBox1.value,
ButtonCallback,
CallbackButton1,ButtonErrorCallback,false); return false;"
从生成的代码可以看出,在执行控件的onClick事件时,会执行_DoCallback方法。_WebForm_DoCallback把文本框的值作为一个参数传送给RaiseCallBackEvent方法(TextBox1.Value是上面例子中使用的定制控件的MyCallBackArgument属性值)。这个例子中的RaiseCallBackEvent方法把文本框的值存储到一个私有字段中。该值由GetCallbackResult方法返回:
public void RaiseCallbackEvent(string eventArgument)
{
this._value = eventArgument;
}
public string GetCallbackResult()
{
return this._value;
}
下面的例子使用了两个定制控件(该控件显示为一个按钮,因为它继承了HtmlButton类),它们都从文本框中获取值,在单击该定制控件时,就在消息框中显示该值。
注意:
这个例子仅演示了如何在定制控件中实现客户回调特性。在实际中,这个例子也可以不使用客户回调特性来实现:使用一个简单的客户端脚本,来显示在文本框中输入的值,且不需要进行回送。
<%@ Page Language="C#" %>
<%@ Register tagprefix="nsquared2" namespace="CallbackTest"%>
<html>
<head>
<title>Implementing Client callback into a custom control</title>
<script>
function ButtonCallback(result, context)
{
alert('The arguments are ' + result);
}
function ButtonErrorCallback(result, context)
{
alert('Invalid input!' + result);
}
</script>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:TextBox id="TextBox1" runat="server" Text="My
value"></asp:TextBox>
<asp:TextBox id="TextBox2" runat="server" Text="My
value2"></asp:TextBox>
<nsquared2:CallbackButton id="CallbackButton1" runat="server" MyCallBackArgument="TextBox1.value">Run</nsquared2:CallbackButton>
<nsquared2:CallbackButton id="CallbackButton2" runat="server" MyCallBackArgument="TextBox2.value">Run</nsquared2:CallbackButton>
</div>
</form>
</body>
</html>
上面的例子有两个客户端脚本:一个是ButtonCallback,它在客户回调过程中,处理完服务器端代码后执行,另一个是ButtonErrorCallback,如果客户回调过程中,服务器端代码抛出了一个异常,就执行它:
function ButtonCallback(result, context)
{
alert('The arguments are ' + result);
}
function ButtonErrorCallback(result, context)
{
alert('Invalid input!' + result);
}
ButtonCallback会在一个消息框中显示服务器端代码的结果,该结果是在文本框中输入的值。ButtonErrorCallback会在一个消息框中显示异常的信息。
上面的代码示例在页面上使用了两个定制控件,控件的MyCallBackArgument属性设置为文本框的名称及其Value属性:
<asp:TextBox id="TextBox1" runat="server" Text="My value"></asp:TextBox>
<asp:TextBox id="TextBox2" runat="server" Text="My value2"></asp:TextBox>
<nsquared2:CallbackButton id="CallbackButton1" runat="server"
MyCallBackArgument="TextBox1.value">Run</nsquared2:CallbackButton>
<nsquared2:CallbackButton id="CallbackButton2" runat="server"
MyCallBackArgument="TextBox2.value">Run</nsquared2:CallbackButton>
单击CallbackButton控件时,为MyCallBackArgument指定的属性值就作为一个参数传送给定制控件的RaiseCallBackEvent方法,该值还在客户端从GetCallbackResult方法返回给ButtonCallback方法。
在运行示例时,会看到两个文本框和两个按钮。单击左边的按钮时,左边文本框(TextBox1)的Text属性值就会传送给服务器,执行一个客户回调。处理完服务器方法后,客户端回调就在一个消息框中显示TextBox1的Text属性值。如果单击右边的按钮,右边文本框(TextBox2)的Text属性值就会传送给服务器。在定制控件上使用客户回调特性,可以在一个页面上执行服务器端代码的几个不同部分。通过把定制控件添加到其他页面上,还可以重用这些代码。
使用客户回调特性和DHTML(IE 4.0或更高版本),可以创建功能丰富的客户程序,更便于在客户机上执行服务器端代码。不需要添加Java小程序或使用IFRAME,只要执行ICallBackEventHandler,用GetCallBackEventReference和RegisterClientScript块生成并注册客户回调的客户端方法。有了客户回调特性,就可以通过服务器端代码针对数据库获取、保存、删除、插入和更新数据,且无需重新加载页面。还可以进行服务器端等。
读感:
以前做项目的时候确实知道怎么去使用异步调用,或者ajax之类的东西,但是具体的实现机制却不曾了解,也没有去深究过,由于经验不多,公司里面的任务大多都是处理(UI),渐渐的习惯去copy了.........
兴趣所在,今天开始学习ajax.........