转 -- 不及格的程序员-八神 的 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

相关文章

Linux下利用select实现串口数据读取过程

《Linux下利用select实现串口数据读取过程》文章介绍Linux中使用select、poll或epoll实现串口数据读取,通过I/O多路复用机制在数据到达时触发读取,避免持续轮询,示例代码展示设... 目录示例代码(使用select实现)代码解释总结在 linux 系统里,我们可以借助 select、

C#利用Free Spire.XLS for .NET复制Excel工作表

《C#利用FreeSpire.XLSfor.NET复制Excel工作表》在日常的.NET开发中,我们经常需要操作Excel文件,本文将详细介绍C#如何使用FreeSpire.XLSfor.NET... 目录1. 环境准备2. 核心功能3. android示例代码3.1 在同一工作簿内复制工作表3.2 在不同

C#使用iText获取PDF的trailer数据的代码示例

《C#使用iText获取PDF的trailer数据的代码示例》开发程序debug的时候,看到了PDF有个trailer数据,挺有意思,于是考虑用代码把它读出来,那么就用到我们常用的iText框架了,所... 目录引言iText 核心概念C# 代码示例步骤 1: 确保已安装 iText步骤 2: C# 代码程

Pandas处理缺失数据的方式汇总

《Pandas处理缺失数据的方式汇总》许多教程中的数据与现实世界中的数据有很大不同,现实世界中的数据很少是干净且同质的,本文我们将讨论处理缺失数据的一些常规注意事项,了解Pandas如何表示缺失数据,... 目录缺失数据约定的权衡Pandas 中的缺失数据None 作为哨兵值NaN:缺失的数值数据Panda

C++中处理文本数据char与string的终极对比指南

《C++中处理文本数据char与string的终极对比指南》在C++编程中char和string是两种用于处理字符数据的类型,但它们在使用方式和功能上有显著的不同,:本文主要介绍C++中处理文本数... 目录1. 基本定义与本质2. 内存管理3. 操作与功能4. 性能特点5. 使用场景6. 相互转换核心区别

Java 缓存框架 Caffeine 应用场景解析

《Java缓存框架Caffeine应用场景解析》文章介绍Caffeine作为高性能Java本地缓存框架,基于W-TinyLFU算法,支持异步加载、灵活过期策略、内存安全机制及统计监控,重点解析其... 目录一、Caffeine 简介1. 框架概述1.1 Caffeine的核心优势二、Caffeine 基础2

python库pydantic数据验证和设置管理库的用途

《python库pydantic数据验证和设置管理库的用途》pydantic是一个用于数据验证和设置管理的Python库,它主要利用Python类型注解来定义数据模型的结构和验证规则,本文给大家介绍p... 目录主要特点和用途:Field数值验证参数总结pydantic 是一个让你能够 confidentl

JAVA实现亿级千万级数据顺序导出的示例代码

《JAVA实现亿级千万级数据顺序导出的示例代码》本文主要介绍了JAVA实现亿级千万级数据顺序导出的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面... 前提:主要考虑控制内存占用空间,避免出现同时导出,导致主程序OOM问题。实现思路:A.启用线程池

在.NET项目中嵌入Python代码的实践指南

《在.NET项目中嵌入Python代码的实践指南》在现代开发中,.NET与Python的协作需求日益增长,从机器学习模型集成到科学计算,从脚本自动化到数据分析,然而,传统的解决方案(如HTTPAPI或... 目录一、CSnakes vs python.NET:为何选择 CSnakes?二、环境准备:从 Py

SpringBoot分段处理List集合多线程批量插入数据方式

《SpringBoot分段处理List集合多线程批量插入数据方式》文章介绍如何处理大数据量List批量插入数据库的优化方案:通过拆分List并分配独立线程处理,结合Spring线程池与异步方法提升效率... 目录项目场景解决方案1.实体类2.Mapper3.spring容器注入线程池bejsan对象4.创建