转 -- 不及格的程序员-八神 的 ASP.NET框架数据回发与事件回发

2024-04-14 11:18

本文主要是介绍转 -- 不及格的程序员-八神 的 ASP.NET框架数据回发与事件回发,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

ASP.NET框架数据回发与事件回发

作者:不及格的程序员-八神

源从何起

经常在网上的论坛看到有人问Page类的方法RegisterRequiresPostBack有什么用,它是做什么的呢?

简短的官方解释

MSDN对它的解释是将控件注册为要求在页面回发至服务器时进行回发处理的控件,说真的我知道这段话想描述些什么,但是你知道怎么用它,在哪里用吗?

 

寻找回发真相

首先ASP.NET框架规定,凡是要进行数据回发的控件都要实现IPostBackDataHandler 接口,它有两个方法LoadPostDataRaisePostDataChangedEvent,该接口方法将可以进行回发数据处理,并可以引发任何回发数据已更改的事件.还有就是要在页生命周期的 Page_PreRender 事件中或该事件之前向页面注册控件.

为什么要这里注册呢?,那我们看看篇头提到的Page. RegisterRequiresPostBack这个方法的源码就会明白了.

 

public void RegisterRequiresPostBack(Control control)

{

        // Fail if the control is not an IPostBackDataHandler (VSWhidbey 184483) 指定控件必须实现IPostBackDataHandler 接口

        if (!(control is IPostBackDataHandler)) {

            IPostBackDataHandler dataHandler = control._adapter as IPostBackDataHandler;

            if (dataHandler == null)

                throw new HttpException(SR.GetString(SR.Ctrl_not_data_handler));

        }

        if (_registeredControlsThatRequirePostBack == null)

            _registeredControlsThatRequirePostBack = new ArrayList();

             //这里将需要注册回发的控件标识保存到数组列表中.

        _registeredControlsThatRequirePostBack.Add(control.UniqueID);

}

 

另外在Page类的方法SaveAllState中有这么一段代码,判断上面的数组列表是否为空,然后将其保存到视图状态,以便页面回发时再读回来.

if (_registeredControlsThatRequirePostBack != null && _registeredControlsThatRequirePostBack.Count > 0)

{

           ...省略若干代码

        if (controlStates == null)

           {

                controlStates = new HybridDictionary();

              }

        controlStates.Add(PageRegisteredControlsThatRequirePostBackKey,  _registeredControlsThatRequirePostBack);

}

这样需要回发的控件标识就会保存到视图状态中去了.

现在你明白为什么需要在 Page_PreRender事件之前注册回发控件了吧,因为在Page_PreRender事件之后就是要写入视图状态了.

如果不在Page_PreRender事件之前注册该控件需要回发,那么视图状态中就不会有该控件标识,结果在页面回发时,页面将不能触发该控件的回发事件.当然了,不是绝对, 类似于TextBox这样的表单域控件,是不需要注册的,框架内部会自动找到它,后面会提及框架是如何做的.

那页面是怎么样找到该控件并触发该控件的回发呢?再看Page:: LoadAllState私有方法源码,它从视图状态中恢复了要求回发的控件列表:

private void LoadAllState()

{

              ...省略读取视图状态若干代码

        if (controlStates != null)

{


//这里将那些需要回发的控件标识又都读了回来,SaveAllState方法中存入的.

            _controlsRequiringPostBack = (ArrayList)controlStates[PageRegisteredControlsThatRequirePostBackKey];

                 //变量_registeredControlsRequiringControlState是调用Control的方法AddedControl时建立的.

//Controls集合中添加对象时都会调用AddedControl方法.

//AddedControl方法中会调用 Page::RegisterRequiresControlState(Control control)方法,由它真正的创建.

            if (_registeredControlsRequiringControlState != null)

                     {

                foreach (Control ctl in _registeredControlsRequiringControlState) {

                                   //读回控件的视图状态

                    ctl.LoadControlStateInternal(controlStates[ctl.UniqueID]);

                }

            }

        }     ...

}

页面在执行回发时Page要处理回发数据会用到下面方法:

private void ProcessPostData(NameValueCollection postData, bool fBeforeLoad);

比如说一个TextBox回发了,它是实现IPostBackDataHandler接口的类,Page就会执行TextBox控件的LoadPostData的方法.

如果方法返回值为真,它将会调用控件本身实现IPostBackDataHandler接口的另一个方法RaisePostDataChangedEvent触发回发数据更改事件.

 

,到这里我们分析Textbox这样的表单域控件在没有执行Page::RegisterRequiresPostBack方法进行注册,怎么还能进行数据回发呢?

因为它是表单元素,它始终会被回发,ProcessPostData会自动处理这样的表单控件(隐藏域控件也是),但像Checkbox则不行,虽然它也是表单元素,但是当它不是被选中状态时,它是不会被回发的,也就是说服务器取不到它的值.

 

但如果是你的一个自定义(非表单域)控件(如一个Label的子类),就必须在OnPreRender方法之前注册这个控件需要回发;

让我们写一个简单的例子,了解回发的整个过程:

using System;

using System.Collections.Generic;

using System.Text;

 

namespace CustomWebControls

{

    public class LabelPostData : System.Web.UI.WebControls.Label, System.Web.UI.IPostBackDataHandler

    {

        protected override void OnPreRender(EventArgs e)

        {

            base.OnPreRender(e);

            this.Page.RegisterRequiresPostBack(this);

        }

 

        #region IPostBackDataHandler 成员

 

        bool System.Web.UI.IPostBackDataHandler.LoadPostData(string postDataKey, System.Collections.Specialized.NameValueCollection postCollection)

        {

            return true;

        }

 

        void System.Web.UI.IPostBackDataHandler.RaisePostDataChangedEvent()

        {

            System.Console.Write(this.Page.Request.Form.ToString());//输出当前表单窗体的字符串形式

        }

 

        #endregion

    }

}

上面代码始终会输出当前表单窗体的字符串形式.

 

如何做到回发事件

       控件要处理回发事件,它必须实现IPostBackEventHandler接口.

在处理回发事件时,比如LinkButton的单击事件回发,它会在隐藏域__EVENTTARGET中加入自己的id.这样PageRaisePostBackEvent方法就会根据该隐藏域中的id找到LinkButton,然后执行它的IPostBackEventHandler接口方法RaisePostBackEvent.

但是类似像ImageButton,并不是依靠IPostBackEventHandler,它是在IPostBackDataHandler 接口LoadPostData方法中判断是否有它的表单域名称(UniqueID)回发,如果有则执行Page.RegisterRequiresRaiseEvent进行回发事件注册,被注册的对象的接口IPostBackEventHandler::RaisePostBackEvent方法会被执行.

 

注意Page.ProcessPostData函数要执行两遍,一次在页面生存周期的Load事件之前(为静态控件处理回发事件,动态控件的回发数据会做为参数传给该函数,在第二次调用时用到.).

第二次在页面生存期Load之后(为动态控件处理回发事件).

为什么要执行两遍呢?因为第一次加载视图状态后,就马上执行了ProcessPostData,不会有其它的干挠(保证页面的完整),比如开发人员在Page_Load写一些其它代码.第二次执行它可以处理在Page.Load之中生成对象的回发事件,给足了开发人员面子.

 

在下面的对ProcessPostData方法整体的注解中,大家会了解到ASP.NET框架在处理回发数据的原理;

//处理回发数据方法

private void ProcessPostData(NameValueCollection postData, bool fBeforeLoad) {

        if (_changedPostDataConsumers == null)

            _changedPostDataConsumers = new ArrayList();

        // identify controls that have postback data

        //处理表单域控件,例如Text,Submit,前面说了TextBox控件内部没有执行Page::RegisterRequiresPostBack方法,但仍能处理回发数据,原因就在这里.

        if (postData != null) {

            foreach (string postKey in postData) {

                if (postKey != null) {

                    // Ignore system post fields

                    if (IsSystemPostField(postKey))

                        continue;

                    Control ctrl = FindControl(postKey);

                    //根据回发对象标识获取表单域控件

                    if (ctrl == null) {

                        if (fBeforeLoad) {

                            // It was not found, so keep track of it for the post load attempt

                            //为动态控件保留这些没有被处理的回发键值,准备下一次调用会用到(Page.Load事件之后).

                            if (_leftoverPostData == null)

                                _leftoverPostData = new NameValueCollection();

                            _leftoverPostData.Add(postKey, null);

                        }

                        continue;

                    }

                    IPostBackDataHandler consumer = ctrl.PostBackDataHandler;

                    // Ignore controls that are not IPostBackDataHandler (see ASURT 13581)

                    if (consumer == null) {

                        // If it's a IPostBackEventHandler (which doesn't implement IPostBackDataHandler),

                        // register it (ASURT 39040)

                        //处理回发事件

                        //比如现在,回发的是Button控件,目前条件一定会满足下面的.

                              //Button是表单元素,同时它实现了IPostBackEventHandler接口.

                              //所以它的事件会被执行.

                        if(ctrl.PostBackEventHandler != null)

                            RegisterRequiresRaiseEvent(ctrl.PostBackEventHandler);

                        continue;

                    }

                    bool changed;

                    if(consumer != null) {

                        changed = consumer.LoadPostData(postKey, _requestValueCollection);

                       //如果控件本身的数据变了,那么后面将会执行数据变更事件.

                        if(changed)

                           _changedPostDataConsumers.Add(ctrl);

                    }

// ensure controls are only notified of postback once

//确保控件回发事件只被执行一次

                             //比如一个CheckBox控件在选中状态后自动回发,那么这里将删除它的注册记录.(CheckBox控件执行了Page.RegisterRequiresPostBack方法)

                                    //否则它的回发事件可能会被执行两次.

                    if (_controlsRequiringPostBack != null)

                        _controlsRequiringPostBack.Remove(postKey);

                }

            }

        }

 

        // Keep track of the leftover for the post-load attempt

        ArrayList leftOverControlsRequiringPostBack = null;

 

        // process controls that explicitly registered to be notified of postback

           //处理注册的要求回发的控件,比如自定义的Lable控件

        if (_controlsRequiringPostBack != null) {

            foreach (string controlID in _controlsRequiringPostBack) {

                Control c = FindControl(controlID);

 

                if (c != null) {

                    IPostBackDataHandler consumer = c._adapter as IPostBackDataHandler;

                    if(consumer == null) {

                        consumer = c as IPostBackDataHandler;

                    }

 

                    // Give a helpful error if the control is not a IPostBackDataHandler (ASURT 128532)

                    if (consumer == null) {

                        throw new HttpException(SR.GetString(SR.Postback_ctrl_not_found, controlID));

                    }

 

                    bool changed = consumer.LoadPostData(controlID, _requestValueCollection);

                    if (changed)

                        _changedPostDataConsumers.Add(c);

                }

                else

              {

                   //首次加载,这里如果没有找到相应注册过的id控件,那么它可能就是动态控件,将这些id留给第二次调用该方法.

                    if (fBeforeLoad) {

                        if (leftOverControlsRequiringPostBack == null)

                            leftOverControlsRequiringPostBack = new ArrayList();

                        leftOverControlsRequiringPostBack.Add(controlID);

                    }

                }

            }

 

            _controlsRequiringPostBack = leftOverControlsRequiringPostBack;

        }

 

}

 

结束语

 

       本文与前一篇”ASP.NET Internet安全Forms身份验证指南系为分析ASP.NET框架源码的产物,而本文写作时间实际更早,写于20089,两天之后便要重新上班,故此整文.

       … …

posted on 2009-03-03 15:59 不及格的程序员-八神

这篇关于转 -- 不及格的程序员-八神 的 ASP.NET框架数据回发与事件回发的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/902869

相关文章

spring @EventListener 事件与监听的示例详解

《spring@EventListener事件与监听的示例详解》本文介绍了自定义Spring事件和监听器的方法,包括如何发布事件、监听事件以及如何处理异步事件,通过示例代码和日志,展示了事件的顺序... 目录1、自定义Application Event2、自定义监听3、测试4、源代码5、其他5.1 顺序执行

基于.NET编写工具类解决JSON乱码问题

《基于.NET编写工具类解决JSON乱码问题》在开发过程中,我们经常会遇到JSON数据处理的问题,尤其是在数据传输和解析过程中,很容易出现编码错误导致的乱码问题,下面我们就来编写一个.NET工具类来解... 目录问题背景核心原理工具类实现使用示例总结在开发过程中,我们经常会遇到jsON数据处理的问题,尤其是

SpringBoot集成图片验证码框架easy-captcha的详细过程

《SpringBoot集成图片验证码框架easy-captcha的详细过程》本文介绍了如何将Easy-Captcha框架集成到SpringBoot项目中,实现图片验证码功能,Easy-Captcha是... 目录SpringBoot集成图片验证码框架easy-captcha一、引言二、依赖三、代码1. Ea

MySQL InnoDB引擎ibdata文件损坏/删除后使用frm和ibd文件恢复数据

《MySQLInnoDB引擎ibdata文件损坏/删除后使用frm和ibd文件恢复数据》mysql的ibdata文件被误删、被恶意修改,没有从库和备份数据的情况下的数据恢复,不能保证数据库所有表数据... 参考:mysql Innodb表空间卸载、迁移、装载的使用方法注意!此方法只适用于innodb_fi

Gin框架中的GET和POST表单处理的实现

《Gin框架中的GET和POST表单处理的实现》Gin框架提供了简单而强大的机制来处理GET和POST表单提交的数据,通过c.Query、c.PostForm、c.Bind和c.Request.For... 目录一、GET表单处理二、POST表单处理1. 使用c.PostForm获取表单字段:2. 绑定到结

mysql通过frm和ibd文件恢复表_mysql5.7根据.frm和.ibd文件恢复表结构和数据

《mysql通过frm和ibd文件恢复表_mysql5.7根据.frm和.ibd文件恢复表结构和数据》文章主要介绍了如何从.frm和.ibd文件恢复MySQLInnoDB表结构和数据,需要的朋友可以参... 目录一、恢复表结构二、恢复表数据补充方法一、恢复表结构(从 .frm 文件)方法 1:使用 mysq

mysql8.0无备份通过idb文件恢复数据的方法、idb文件修复和tablespace id不一致处理

《mysql8.0无备份通过idb文件恢复数据的方法、idb文件修复和tablespaceid不一致处理》文章描述了公司服务器断电后数据库故障的过程,作者通过查看错误日志、重新初始化数据目录、恢复备... 周末突然接到一位一年多没联系的妹妹打来电话,“刘哥,快来救救我”,我脑海瞬间冒出妙瓦底,电信火苲马扁.

golang获取prometheus数据(prometheus/client_golang包)

《golang获取prometheus数据(prometheus/client_golang包)》本文主要介绍了使用Go语言的prometheus/client_golang包来获取Prometheu... 目录1. 创建链接1.1 语法1.2 完整示例2. 简单查询2.1 语法2.2 完整示例3. 范围值

javaScript在表单提交时获取表单数据的示例代码

《javaScript在表单提交时获取表单数据的示例代码》本文介绍了五种在JavaScript中获取表单数据的方法:使用FormData对象、手动提取表单数据、使用querySelector获取单个字... 方法 1:使用 FormData 对象FormData 是一个方便的内置对象,用于获取表单中的键值

Node.js net模块的使用示例

《Node.jsnet模块的使用示例》本文主要介绍了Node.jsnet模块的使用示例,net模块支持TCP通信,处理TCP连接和数据传输,具有一定的参考价值,感兴趣的可以了解一下... 目录简介引入 net 模块核心概念TCP (传输控制协议)Socket服务器TCP 服务器创建基本服务器服务器配置选项服