回调 WebControls






4.80/5 (41投票s)
Callback 和 JavaScript 的强大功能,无需重新加载整个页面即可渲染控件。
引言
在本文中,我将介绍 ASP.NET 2.0 中的一种名为“Callback”的通信方式,以及它如何在 Web 应用程序中使用。为此,我创建了一组基于“Callback”而不是“Postback”的“Web Controls”,它们的工作方式与 ASP.NET 2.0 的标准控件完全相同。我将首先解释理解本文的重要概念,最后,我将向您展示一个实际的例子。
Callback?
Callback 是 ASP.NET 2.0 的新特性之一,通过它我们可以与服务器通信,而无需重新提交整个页面。Callback 和 postback 的区别在于,在 postback 中,每当您单击按钮时,页面的整个 HTML 及其控件都会重新生成(Render
)。相比之下,callback 则不是这样,这一点非常重要!为什么要重新生成整个 HTML 页面?例如,当我只想在我的 GridView
中添加一行时,是否需要这样做。
因此,对于 Web 应用程序,“Render”单个控件应该是目标。我的意思是,没有必要重新生成整个页面的 HTML 代码及其控件。
您可以使用“AJAX 控件”,但您将对生成的代码一无所知,因此您将无法控制应用程序中发生的事情。
为了更好地理解这一点,我们将分别查看 postback 和 callback 模式下的页面生命周期。
页面生命周期
让我们看看页面生命周期中 postback 和 callback 的区别。更多详细信息,请参阅:页面生命周期。
在示例中:当用户单击 Button1
时,按钮的文本值将更改为Button1Update。
回发
- 用户单击
Button1
:整个页面被发送到服务器(postback)。 - 页面生命周期开始。在
Button1_Click
事件中,button1
的Text
值更改为Button1Update
。 - 在页面生命周期结束时,整个页面的 HTML 会被生成并发送到客户端浏览器进行解释。
- 浏览器重新加载并显示整个页面,其中
button1
的文本值为“Button1Update
”。
protected void Button1_Click(object sender, EventArgs e)
{
this.Button1.Text = "Button1Update";
}
回调
- 用户单击
Button1
:页面未发送到服务器(callback)。 - 页面生命周期开始。
Button1
的Click
事件由RaiseCallBackEvent
方法捕获。 - 在页面生命周期结束时,“
GetCallbackResult
”方法会生成 HTML 按钮,然后将其返回给浏览器进行解释。 - 浏览器显示带有
Button1
新文本值“Button1Update
”的页面,**无需**重新加载整个页面。
public void RaiseCallbackEvent(string eventArgument)
{
this.Button1.Text = "ButtonUpdate1";
}
public string GetCallbackResult()
{
StringWriter sw = new StringWriter();
HtmlTextWriter tw = new HtmlTextWriter(sw);
System.Reflection.MethodInfo mi =
typeof(Control).GetMethod("Render",
System.Reflection.BindingFlags.Instance |
System.Reflection.BindingFlags.NonPublic);
mi.Invoke(control, new object[] { tw });
return string.Format("{0}|{1}", control.ClientID, sw.ToString());
}
如您所见,postback 和 callback 在页面生命周期中存在差异:在 callback 中,页面生命周期中的三个事件不会触发。
OnPreRender
:Render
的开始。OnSaveStateComplete
:一个递归事件,它会为页面上的所有控件调用SaveViewState
事件。Render
:一个递归事件,它会为页面上的所有控件调用Render
事件。
现在,如何让使用 callback 模式的控件保存其 viewstate 并渲染其 HTML 代码?现在我们将看到这一点。
ICallBackEventHandler
任何想要使用 callback 方法的控件都必须实现 ICallBackEventhandler
接口。此接口有两个方法。
// Summary:
// Processes a callback event that targets a control.
//
// Parameters:
// eventArgument:
// A string that represents an event argument
// to pass to the event handler.
void RaiseCallbackEvent(string eventArgument)
// Summary:
// Returns the results of a callback event that targets a control.
//
// Returns:
// The result of the callback.
string GetCallbackResult()
更多详细信息,请参阅:ICallBackEventHandler。
视图状态(ViewState)
这是摘录。
Microsoft ASP.NET 视图状态,简而言之,是 ASP.NET 网页用来在 postbacks 之间持久化 Web Form 状态更改的技术。在我作为培训师和顾问的经验中,视图状态是 ASP.NET 开发人员中最令人困惑的问题。在创建自定义服务器控件或进行更高级的页面技术时,如果不牢固掌握视图状态是什么以及它是如何工作的,可能会让你吃亏。专注于创建低带宽、精简页面的网页设计师们常常发现自己对视图状态感到沮丧。默认情况下,页面的视图状态被放在一个名为
__VIEWSTATE
的隐藏表单字段中。这个隐藏表单字段很容易变得很大,可能高达几十 KB。__VIEWSTATE
表单字段不仅会导致下载速度变慢,而且每当用户 postback 网页时,这个隐藏表单字段的内容都必须在 HTTP 请求中 postback,从而延长了请求时间。
更多详情请参考
WebControl
System.Web.UI.WebControls.WebControl
是大多数 ASP.NET 2.0 标准控件(如 Button
、TextBox
、CheckBox
等)的基类。所有 Web 控件都必须实现此类。
ControlDesigner
所有 ASP.NET 控件都有一个与之关联的“ControlDesigner
”。这个类由“Visual Studio”的设计器用于在您将控件拖到页面上时赋予控件形状,而不是在运行时。例如,“Button
”控件的 ControlDesigner
被设置为 System.Web.UI.Design.WebControls.ButtonDesigner
。我们可以在 Button
类中看到这个属性。
[Designer("System.Web.UI.Design.WebControls.ButtonDesigner,
System.Design, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b03f5f7f11d50a3a")]
public class Button : WebControl, IButtonControl, IPostBackEventHandler
{...}
实践:AEButton 控件
为了实践上述内容,我们将创建一个在 callback 模式下工作的按钮控件:AEButton
(异步事件按钮)。
创建控件
如前所述,任何 Web 控件都必须继承自 WebControl
基类。
public class AEButton : WebControl
{
public AEButton(): base(HtmlTextWriterTag.Input)
{
}
}
WebControl
构造函数支持设置特定的 HTML 标签;在我们的示例中,这是“Input
”标签,对应于一个按钮。
<input type="button" id="Button1" value="button" />
像任何其他 Web 控件一样,此控件将具有属性、事件和方法。
属性
在本节中,我们将看到为我们的 AEButton
控件设置的属性。我将只列出最重要的属性。
Text
:此属性是input
标签中的 HTML 属性value
。_ViewState
:此属性获取页面的最终“ViewState”值,该值将保存在隐藏字段__VIEWSTATE
中。Result
:此属性将包含生成的 HTML(Render),用于在页面中更新控件。之后,我们将看到如何获取给定控件的 HTML 代码以发送到客户端页面。IsRenderControl
:当您想更新其他控件时,此属性必须设置为True
。
事件
每个控件都有其事件;在我们的示例中,我们将实现“Click”事件。
- 单击
/// <summary>
/// Occurs when the AEButton is clicked
/// </summary>
public event EventHandler Click
{
add
{
Events.AddHandler(EventClick, value);
}
remove
{
Events.RemoveHandler(EventClick, value);
}
}
事件的实现名为“OnClick
”。符号是“On + 事件名称”。
/// <summary>
/// Raises the Click event of the AEButton controls
/// </summary>
/// <param name="e">
protected virtual void OnClick(EventArgs e)
{
EventHandler handler = (EventHandler)Events[EventClick];
if (handler != null)
{
handler(this, e);
}
}
Render 方法
如页面生命周期中所见,Render
事件会被调用以处理页面上的所有控件,因此我们必须为 AEButton
控件定义并实现此事件。在此事件中,我们将调用一个方法来生成对应的 HTML 代码“input 按钮”,该方法会考虑大多数属性,如 Text
、BackColor
、Width
、Height
……Render
事件的参数是一个 HtmlTextWriter
类型的对象,它支持生成按钮的 HTML 代码。
Render
/// <summary>
/// Displays the Button on the client
/// </summary>
/// <param name="writer">
protected override void Render(HtmlTextWriter writer)
{
this.RenderButton(writer);
}
RenderButton
public void RenderButton(HtmlTextWriter writer)
{
AddAttributesToRender(writer);
...
RenderInputTag(writer, clientID, onClick, text);
}
RenderInputTag
/// Summary
/// Render Input Tag
/// param name="writer"
/// param name="clientID"
/// param name="onClick"
/// param name="text"
internal virtual void RenderInputTag(HtmlTextWriter writer,
string clientID, string onClick, string text)
{
writer.AddAttribute(HtmlTextWriterAttribute.Type, "button");
if (UniqueID != null)
{
writer.AddAttribute(HtmlTextWriterAttribute.Name, UniqueID);
}
if (Text != null)
{
writer.AddAttribute(HtmlTextWriterAttribute.Value, Text);
}
if (!IsEnabled)
{
writer.AddAttribute(HtmlTextWriterAttribute.Disabled, "disabled");
}
if (Page != null && !Page.IsCallback && !Page.IsPostBack)
{
Page.ClientScript.RegisterForEventValidation(this.UniqueID);
}
if (onClick != null)
{
writer.AddAttribute(HtmlTextWriterAttribute.Onclick, onClick);
}
string s = AccessKey;
if (s.Length > 0)
writer.AddAttribute(HtmlTextWriterAttribute.Accesskey, s);
int i = TabIndex;
if (i != 0)
writer.AddAttribute(HtmlTextWriterAttribute.Tabindex,
i.ToString(NumberFormatInfo.InvariantInfo));
writer.RenderBeginTag(HtmlTextWriterTag.Input);
writer.RenderEndTag();
}
在本节中,我们看到了生成 AEButton
控件 HTML 代码的事件和方法。工作尚未完成,现在让我们看看如何创建 AEButton
控件的设计器。
创建控件设计器
我们想要创建的控件是一个按钮,所以从逻辑上讲,创建的设计器必须与 ASP.NET 中的标准按钮相同。这就是为什么我们的 AEButtonDesigner
将继承 System.Web.UI.Design.WebControls.ButtonDesigner
类。它在 ASPX 页面中与 ASP.NET 2.0 的标准按钮具有完全相同的形式。
namespace WebGui.AEButton
{
public class AEButtonDesigner : ButtonDesigner
{
public override bool AllowResize
{
get
{
return true;
}
}
public override string GetDesignTimeHtml()
{
// Component is the control instance, defined in the base
// designer
AEButton aeb = (AEButton)Component;
if (aeb.ID != "" && aeb.ID != null)
{
StringWriter sw = new StringWriter();
HtmlTextWriter tw = new HtmlTextWriter(sw);
aeb.RenderButton(tw);
return sw.ToString();
}
else
return GetEmptyDesignTimeHtml();
}
}
}
GetDesignTimeHtml
方法由 Visual Studio 的设计器用于塑造我们的控件。您会注意到我调用了 RenderButton
方法,该方法返回对应于“Input Button”的 HTML 代码。
这是我们指定类属性 [Designer ...]
的方式。
/// <summary>
/// Represents a AEButton control
/// </summary>
[
DataBindingHandler("System.Web.UI.Design.TextDataBindingHandler"),
DefaultEvent("Click"),
Designer("WebGui.AEButton.AEButtonDesigner"),
DefaultProperty("Text"),
]
[AspNetHostingPermission(SecurityAction.LinkDemand,
Level = AspNetHostingPermissionLevel.Minimal)]
[AspNetHostingPermission(SecurityAction.InheritanceDemand,
Level = AspNetHostingPermissionLevel.Minimal)]
public class AEButton : WebControl
实现 ICallBackEventHandler
为了使用 callback,AEButton
控件必须实现 ICallBackEventHandler
接口。
- 控件加载
在 postback 模式下,ASP.NET 会生成 __doPostBack
方法,该方法由任何支持 postback 方法的控件调用。
function __doPostBack(eventTarget, eventArgument) {
if (!theForm.onsubmit || (theForm.onsubmit() != false)) {
theForm.__EVENTTARGET.value = eventTarget;
theForm.__EVENTARGUMENT.value = eventArgument;
theForm.submit();
}
}
除了这一点,在“callback”中,这并不存在,因此我们必须做同样的事情:这就是为什么它将重写控件的 OnLoad
事件。
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
string postCommandFunctionName =
string.Format("PostCommand_{0}", base.ClientID);
//Call Server
string eventReference = this.Page.ClientScript.GetCallbackEventReference(
this, "arg", "GetReponse", "context", true);
//Post Command
string postCommandScript = "\r\nfunction " +
postCommandFunctionName +
"(arg,context) {\r\n __theFormPostCollection.length" +
" = 0;__theFormPostData =\"\"; WebForm_InitCallback();" +
eventReference + ";} \r\n";
this.Page.ClientScript.RegisterClientScriptBlock(this.GetType(),
postCommandFunctionName, postCommandScript, true);
}
将在页面上生成的 JavaScript 代码如下。
function PostCommand_AEButton1(arg,context) {
__theFormPostCollection.length = 0;
__theFormPostData =""; WebForm_InitCallback();
WebForm_DoCallback('AEButton1',arg,GetReponse,context,null,true);}
AEButton
控件的 Click
事件调用 JavaScript 方法“PostCommand_AEButton1()
”。这已在 RenderButton
方法中定义。
string onClick = string.Format("PostCommand_{0}()", ClientID);
<input id=" onclick="PostCommand_AEButton1()"
value="AE Button" name="AEButton1" type="button">
RaiseCallBackEvent
RaiseCallbackEvent
方法执行给定控件的 callback 事件。因此,当您单击 AEButton
控件时,该方法会“引发 callback 事件”,从而运行此方法。
public void RaiseCallbackEvent(string eventArgument)
{
this.OnClick(new EventArgs());
}
UpdateControl
UpdateControl
方法调用 Render 方法来获取已更新控件的 HTML。结果存储在 _result
中。_result
的值将通过 GetCallbackResult
方法发送到客户端浏览器进行解释。
/// summary
/// This method must be invoked after a any updating
// of controls properties (backcolor,width...)
/// summary
/// param name="control"
public void UpdateControl(Control control)
{
StringWriter sw = new StringWriter();
HtmlTextWriter tw = new HtmlTextWriter(sw);
System.Reflection.MethodInfo mi =
typeof(Control).GetMethod("Render",
System.Reflection.BindingFlags.Instance |
System.Reflection.BindingFlags.NonPublic);
mi.Invoke(control, new object[] { tw });
this._result = string.Format("{0}|{1}",
control.ClientID , sw.ToString());
}
结果如下:“ControlID | Control Html Tag”。
protected void AEButton1_Click(object sender, EventArgs e)
{
this.GridView1.Rows[1].Visible = false ;
this.AEButton1.UpdateControl(this.GridView1);
}
在此示例中,“GridView
”的第二行被隐藏。之后,AEbutton1
通过调用 UpdateControl
方法来触发 GridView
的 Render
。
GetCallBackResult
GetCallbackResult
方法返回 callback 事件的结果。这意味着在这个方法中,我将渲染已修改的控件。它不应忘记保存控件的状态。也就是说,为页面上的所有控件调用 SaveViewState
方法非常重要。然后,在编辑后检索页面的 ViewState,并将其保存在隐藏字段 __VIEWSTATE
中。
public string GetCallbackResult()
{
if (this._IsRenderControl)
{
// Save All controls State of the page
System.Reflection.MethodInfo mi = typeof(Page).GetMethod(
"SaveAllState", System.Reflection.BindingFlags.Instance |
System.Reflection.BindingFlags.NonPublic);
mi.Invoke(this.Page, null);
//Get serialized viewstate from Page's ClientState
System.Reflection.PropertyInfo stateProp = typeof(Page).GetProperty(
"ClientState", System.Reflection.BindingFlags.Instance |
System.Reflection.BindingFlags.NonPublic);
this._ViewState = stateProp.GetValue(this.Page, null).ToString();
this._result = string.Format("{0}§{1}",
this._ViewState, this._result);// GetRender());
}
return this._result;
}
保存到 ViewState 后,我从属性页面“ClientState
”获取其值。然后我返回最终结果。要检索和处理 callback 返回的结果,**必须将此 JavaScript 函数添加到页面中**。
function GetReponse(argument)
{
var elementId;
var innerHtmlStr;
if (argument != null & argument != "")
{
var tableauM=argument.split("§");
//document.getElementById("__VIEWSTATE").value=tableauM[0];
document.getElementById("__VIEWSTATE").setAttribute("value",tableauM[0]);
var tableau=tableauM[1].split("|");
elementId=tableau[0] ;
innerHtmlStr=tableau[1] ;
document.getElementById(elementId).outerHTML=innerHtmlStr;
}
}
不要忘记将这些指令添加到页面中。
EnableEventValidation ="false"
viewstateencryptionmode="Never" validaterequest="false">
AEButton Click 事件场景
示例
在此演示中,页面上有四个控件,每个控件都更新另一个标准的 ASP.NET 控件。
AEDropDownList
更改 ASP.NET 按钮的背景颜色。
protected void AEDropDownList1_SelectedIndexChanged(object sender, EventArgs e)
{
this.Button1.BackColor =
Color.FromName(this.AEDropDownList1.SelectedItem.Value);
this.AEDropDownList1.UpdateControl(this.Button1);
}
AEbutton
选中/取消选中 ASP.NET CheckBox
。protected void AEButton1_Click(object sender, EventArgs e)
{
this.CheckBox1.Checked = !this.CheckBox1.Checked;
this.AEButton1.UpdateControl(this.CheckBox1);
}
AECheckbox
使 ASP.NET TextBox
变为只读。protected void AECheckBox1_CheckedChanged(object sender, EventArgs e)
{
this.TextBox1.ReadOnly = this.AECheckBox1.Checked;
this.AECheckBox1.UpdateControl(this.TextBox1);
}
AETextbox
更改 ASP.NET Button
的文本。protected void AETextBox1_TextChanged(object sender, EventArgs e)
{
this.Button2.Text = this.AETextBox1.Text;
this.AETextBox1.UpdateControl(this.Button2);
}
结论
我制作的这四个控件展示了 callback 结合 JavaScript 方法的强大功能,可以使 ASP.NET 应用程序更轻量级。目标很简单:只更新页面中的特定控件,而不是整个页面。尝试将其用于 GridView
等其他控件!