Asp.net中Postback及Callback
我们知道,在默认的情况下,当我们点击Asp.net Page中的一个服务器Button时(默认其实是Submit Form),会导致Page被Recreated,这个过程我们称之为Postback,它是Page生命周期的一个阶段。我们将从以下几个方面来简单谈谈Asp.net中的Postback:
1.为什么使用Postback
当我们每次通过浏览器在向服务器请求一个Page时,由于http是无状态协议,对于服务器来说,都是一个新的请求。然而,在有的时候,我们希望在新请求中能够保存上一次请求页面状态,这正是Postback的由来。那页面状态是如何保持的呢,这里引入了ViewState机制,工作原理大致如下:一个请求到达服务器后,最终,服务器在Render Html时,会将页面所有EnableViewState属性为true的服务器Control状态序列化,然后通过默认的SHA1算法(可在Config中配置)加密输出,输出内容是在中,当点击页面按钮时,会调用生成的会将表单和__VIEWSTATE隐藏域提交给Form,服务器端Page在Load的时候通过IsPostBack属性判断出来是PostBack请求,就会将__VIEWSTATE的值反序列化来设置控件状态,最后将当前页面状态Render到Html,这是一个流程。
2.Postback工作过程
首先,我们来举个简单的例子: 创建一个如下的Aspx页面:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="WebForm4.aspx.cs" Inherits="_2PostBack.WebForm4" %> <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title></title> </head> <body> <form id="form1" runat="server"> <asp:Label ID="Label1" runat="server" Text="Label"></asp:Label> <asp:TextBox ID="TextBox1" runat="server"></asp:TextBox> <asp:DropDownList ID="DropDownList1" runat="server" OnSelectedIndexChanged="DropDownList1_SelectedIndexChanged" AutoPostBack="true"> <asp:ListItem Text="郑州" Value="1"></asp:ListItem> <asp:ListItem Text="洛阳" Value="2"></asp:ListItem> </asp:DropDownList> <asp:Button ID="Button1" runat="server" Text="Button1" OnClick="Button1_Click" OnCommand="Button1_Command" CommandArgument="button1"/> <asp:Button ID="Button2" runat="server" Text="Button2" OnClick="Button2_Click" OnCommand="Button1_Command" CommandArgument="button2" UseSubmitBehavior="false"/> </form> </body> </html>
Code behind代码:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; namespace _2PostBack { public partial class WebForm4 : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { } protected void Button1_Click(object sender, EventArgs e) { this.Label1.Text = "111"; this.TextBox1.Text = "222"; } protected void DropDownList1_SelectedIndexChanged(object sender, EventArgs e) { this.Label1.Text = this.DropDownList1.SelectedItem.Text + "被选中"; } protected void Button2_Click(object sender, EventArgs e) { //this.ClientScript.RegisterClientScriptBlock(this.GetType(), "", "alert('button2 is clicked');", true); } protected void Button1_Command(object sender, CommandEventArgs e) { this.ClientScript.RegisterClientScriptBlock(this.GetType(), "", "alert('Command event is fired by "+e.CommandArgument+"');", true); } } }
查看页面源:
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /><title> </title></head> <body> <form method="post" action="WebForm4.aspx" id="form1"> <div class="aspNetHidden"> <input type="hidden" name="__EVENTTARGET" id="__EVENTTARGET" value="" /> <input type="hidden" name="__EVENTARGUMENT" id="__EVENTARGUMENT" value="" /> <input type="hidden" name="__LASTFOCUS" id="__LASTFOCUS" value="" /> <input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="Vqv+KjPUckg+144xpf/QLmbVZMyUllu8bC/2XIXx6m9nl9zU1fvBY2vEvG3+yItJZ0KxnR2bBZArLGD6aZmvDpyNsXkmR0lOhuBb9IhlRm8VeVo1Sf3K8ZE7bCTXrM8C" /> </div> <script type="text/javascript"> //<![CDATA[ var theForm = document.forms['form1']; if (!theForm) { theForm = document.form1; } function __doPostBack(eventTarget, eventArgument) { if (!theForm.onsubmit || (theForm.onsubmit() != false)) { theForm.__EVENTTARGET.value = eventTarget; theForm.__EVENTARGUMENT.value = eventArgument; theForm.submit(); } } //]]> </script> <div class="aspNetHidden"> <input type="hidden" name="__VIEWSTATEGENERATOR" id="__VIEWSTATEGENERATOR" value="46C3663D" /> <input type="hidden" name="__EVENTVALIDATION" id="__EVENTVALIDATION" value="sIYZq5CKr84HI9Xl83lk+dbfP8dmxk8DsHHFopi45JLSqIGDkhpRjkagLQ64HpyIxKgJvaWNcKeczV3RwU7GSqQ4YCljmXzlle4LuISbIketSy8wozEbj27ERYlEe2aOmcZMsY59JL2OmfM9UyJ2MvLDJZTXP9nZZn1c/iL+tnMYR3PA9U0aGwXUUIS3vfcsDPXcyr2qvn9ncjj2wIyV3GkytI9DqsErncURMoKdVKs=" /> </div> <span id="Label1">Label</span> <input name="TextBox1" type="text" id="TextBox1" /> <select name="DropDownList1" onchange="javascript:setTimeout('__doPostBack(\'DropDownList1\',\'\')', 0)" id="DropDownList1"> <option selected="selected" value="1">郑州</option> <option value="2">洛阳</option> </select> <input type="submit" name="Button1" value="Button1" id="Button1" /> <input type="button" name="Button2" value="Button2" onclick="javascript:__doPostBack('Button2','')" id="Button2" /> </form> </body> </html>
会发现生成部分有这样几种比较特别的东西:
1.Hidden Input
__EVENTTARGET:触发Event的Control的Unique name;
__EVENTARGUMENT:Event Handler定义的参数;
__LASTFOCUS:这个一般是Changed事件定义;
__VIEWSTATE:ViewState;
2.Script
定义了一个__doPostBack function来提交表单,以eventTarget和eventArgument为参数。
3.Control
Button默认被Render成Submit Button,当UseSubmitBehavior设为false时,是通过Script来提交的,内容是一致的。 看了页面源,大概也能明白Postback是如何工作的:在Client端通过提交一些参数__EVENTTARGET、__EVENTARGUMENT等,不管是直接提交还是通过脚本,在Server端通过提交的__EVENTTARGET值反射出该Control,然后判断该Control是否实现了IPostBackEventHandler接口,如果实现了该接口,当前Page就会调用RaisePostBackEvent方法。
[EditorBrowsable(EditorBrowsableState.Advanced)]
protected virtual void RaisePostBackEvent(IPostBackEventHandler sourceControl, string eventArgument) { sourceControl.RaisePostBackEvent(eventArgument); }
第一个参数就是Postback的Source control,第二个参数其实就是__EVENTARGUMENT值。 我们这里具体指的是Button,先来看下Button定义:
[DefaultEvent("Click"), DefaultProperty("Text"), Designer("System.Web.UI.Design.WebControls.ButtonDesigner, System.Design, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b03f5f7f11d50a3a"), DataBindingHandler("System.Web.UI.Design.TextDataBindingHandler, System.Design, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"),
SupportsEventValidation, ToolboxData("<{0}:Button runat=\"server\" Text=\"Button\">")]
public class Button : WebControl, IButtonControl, IPostBackEventHandler{}
IPostBackEventHandler接口定义如下:
public interface IPostBackEventHandler { void RaisePostBackEvent(string eventArgument); }
RaisePostBackEvent方法在Button的实现如下:
protected virtual void RaisePostBackEvent(string eventArgument)
{
base.ValidateEvent(this.UniqueID, eventArgument);
if (this.CausesValidation)
{
this.Page.Validate(this.ValidationGroup);
}
this.OnClick(EventArgs.Empty);
this.OnCommand(new CommandEventArgs(this.CommandName, this.CommandArgument));
}
这样调用页面的RaisePostBackEvent方法其实还是调用了Button的RaisePostBackEvent方法,在Button的RaisePostBackEvent方法具体实现中,先进行验证,然后触发Click,接着触发Command,执行相应的事件处理,然后Render Html,工作过程大致这样。另外,需要注意的是:当将Label和TextBox的EnableViewState设为false(默认为true)后,依次点击button1、button2,发现Label值最终变为初始值,而TextBox值不变,原因是在提交表单时,不管是否启用ViewState,TextBox都会提交,而Label不会;DropDownList的AutoPostBack属性默认为false,此时SelectedItem改变时不会触发Changed事件,而设为true后,SelectedItem改变会回发,最终触发Changed事件,原因是DropDownList实现了IPostBackDataHandler接口。
3.为什么使用Callback
在Asp.net中客户端与服务端的交互默认是整页面提交(包括自动生成的Hidden Input),这无疑加重了数据传输负担,加大的服务端的工作压力,而且用户还需要等待最终处理结果。在开发过程中,一个很常见的功能是:在用户注册时,当用户输完用户名,文本框失去焦点就应该提示用户名是否可用。该功能的实现目前主要是通过两种手段,一是纯javascript,二是通过.net类库。在.net类库中常用的有微软的Asp.net Ajax技术以及第三方的AjaxPro类库。然而,在这些.net类库没有出现之前,使用的是什么方法呢?就是我们所要说的Callback,它减轻了数据传输负担,缓解了服务端的工作压力,并且具有异步性。
4.Callback工作过程
客户端回调本质上就是指通过前端的客户端脚本向服务器端传递相应的数据参数,服务器端再以接受到的参数进行查询和处理,最后将结果回传到客户端进行显示。 asp.net 2.0提供了实现无刷新回调的接口ICallbackEventHandler.为了实现客户端回调,你必须实现一个 ICallbackEventHandler接口,该接口定义了两个方法法RaiseCallbackEvent和GetCallbackResult. RaiseCallbackEvent()从浏览器接受一个字符串作为事件参数,即该方法接受客户端JavaScript传递的参数,注意它是首先触发 的。接下来触发的就是GetCallbackResult()方法,它将所得到的结果传回给客户端的JavaScript,JavaScript再将结果 更新到页面。我们来使用Callback实现上面提到的用户注册失去焦点判断用户是否已存在。
Aspx代码:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="WebForm1.aspx.cs" Inherits="_3Callback.WebForm1" %> <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <title></title> <script type="text/javascript"> function Success(args, context){ spanUsername.innerText = args; } function Error(args, context) { spanUsername.innerText = "发生异常"; } </script> </head> <body> <form id="form1" runat="server"> <div> <table> <tr> <td>用户名:</td> <td> <input type="text" id="tbUsername" onblur="CallServerMethod(tbUsername.value,null);"/> </td> <td> <span style="color:red;">*</span> <span id="spanUsername"></span> </td> </tr> <tr> <td>密码:</td> <td> <input type="text" id="tbPassword" /> </td> <td> <span style="color:red;">*</span> <span id="spanPassword"></span> </td> </tr> </table> </div> </form> </body> </html>
Code Behind代码:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; namespace _3Callback { public partial class WebForm1 : System.Web.UI.Page,ICallbackEventHandler { private ClientScriptManager csm; private string result; protected void Page_Load(object sender, EventArgs e) { csm = this.Page.ClientScript;//获取当前页的ClientScriptManager //获取回调引用。会在客户端生成DoCallback脚本方法,调用它来实现异步调用 // string reference = csm.GetCallbackEventReference(this, "args", "Success", "", "Error", false); string callbackScript = "function CallServerMethod(args,context){\n" + reference + ";\n}"; csm.RegisterClientScriptBlock(this.GetType(), "CallServerMethod", callbackScript, true); } public string GetCallbackResult() { return result; } public void RaiseCallbackEvent(string eventArgument) { if (eventArgument == "jello") result = eventArgument + "已存在"; else result = eventArgument + "可用"; } } }
这里让当前Page实现了ICallbackEventHandler接口,由它来处理客户端回调。
工作流程大致如下:在页面Load的时候,获取回调引用,会在客户端生成DoCallback脚本方法,调用它来实现异步调用,然后注册需要在客户端直接调用的脚本并在客户端调用,在页面加载完后,首先会调用WebForm_InitCallback()对页面一些标签做不同处理,当操作导致客户端事件触发时将调用WebForm_DoCallback脚本方法,这俩个方法是在生成的axd文件中,查看该文件发现,其实WebForm_DoCallback方法也是通过javascript ajax处理的,当回调成功时,将GetCallbackResult()返回值作为第一个参数,将context作为第二个参数并调用该成功回调方法。
5.Postback与Callback的区别
1.Postback本质是一次Submit Form的过程,而Callback本质是一次javascript ajax的过程
2.Postback通过ClientScriptManager.GetPostBackEventReference(Control control, string argument)获取引用从而生成__doPostback客户端方法,而Callback通过ClientScriptManager.GetCallbackEventReference(Control control, string argument, string clientCallback, string context, bool useAsync)获取引用从而生成Webform_DoCallback客户端方法
3.其它待总结