65.9K
CodeProject 正在变化。 阅读更多。
Home

回调 WebControls

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.80/5 (41投票s)

2010 年 1 月 5 日

Ms-PL

9分钟阅读

viewsIcon

130203

downloadIcon

1365

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

回发

PageLifeCycle_PostBack.jpg

  1. 用户单击 Button1:整个页面被发送到服务器(postback)。
  2. 页面生命周期开始。在 Button1_Click 事件中,button1Text 值更改为 Button1Update
  3. protected void Button1_Click(object sender, EventArgs e)
    {
        this.Button1.Text = "Button1Update";
    }
  4. 在页面生命周期结束时,整个页面的 HTML 会被生成并发送到客户端浏览器进行解释。
  5. 浏览器重新加载并显示整个页面,其中 button1 的文本值为“Button1Update”。

回调

PageLifeCycle_CallBack.jpg

  1. 用户单击 Button1:页面未发送到服务器(callback)。
  2. 页面生命周期开始。Button1Click 事件由 RaiseCallBackEvent 方法捕获。
  3. public void RaiseCallbackEvent(string eventArgument)
    {
        this.Button1.Text = "ButtonUpdate1";
    }
  4. 在页面生命周期结束时,“GetCallbackResult”方法会生成 HTML 按钮,然后将其返回给浏览器进行解释。
  5. 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()); 
    }
  6. 浏览器显示带有 Button1 新文本值“Button1Update”的页面,**无需**重新加载整个页面。

如您所见,postback 和 callback 在页面生命周期中存在差异:在 callback 中,页面生命周期中的三个事件不会触发。

  • OnPreRenderRender 的开始。
  • 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 标准控件(如 ButtonTextBoxCheckBox 等)的基类。所有 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

IsRenderControl.jpg

事件

每个控件都有其事件;在我们的示例中,我们将实现“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);
        }
    }

    Aebutton_Click.jpg

Render 方法

如页面生命周期中所见,Render 事件会被调用以处理页面上的所有控件,因此我们必须为 AEButton 控件定义并实现此事件。在此事件中,我们将调用一个方法来生成对应的 HTML 代码“input 按钮”,该方法会考虑大多数属性,如 TextBackColorWidthHeight……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

ControlsToolBar.jpg

实现 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 方法来触发 GridViewRender

  • 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 控件。

sample.jpg

  • 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 等其他控件!

© . All rights reserved.