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

相关文章

MyBatis-Plus通用中等、大量数据分批查询和处理方法

《MyBatis-Plus通用中等、大量数据分批查询和处理方法》文章介绍MyBatis-Plus分页查询处理,通过函数式接口与Lambda表达式实现通用逻辑,方法抽象但功能强大,建议扩展分批处理及流式... 目录函数式接口获取分页数据接口数据处理接口通用逻辑工具类使用方法简单查询自定义查询方法总结函数式接口

SQL中如何添加数据(常见方法及示例)

《SQL中如何添加数据(常见方法及示例)》SQL全称为StructuredQueryLanguage,是一种用于管理关系数据库的标准编程语言,下面给大家介绍SQL中如何添加数据,感兴趣的朋友一起看看吧... 目录在mysql中,有多种方法可以添加数据。以下是一些常见的方法及其示例。1. 使用INSERT I

Python使用vllm处理多模态数据的预处理技巧

《Python使用vllm处理多模态数据的预处理技巧》本文深入探讨了在Python环境下使用vLLM处理多模态数据的预处理技巧,我们将从基础概念出发,详细讲解文本、图像、音频等多模态数据的预处理方法,... 目录1. 背景介绍1.1 目的和范围1.2 预期读者1.3 文档结构概述1.4 术语表1.4.1 核

MySQL 删除数据详解(最新整理)

《MySQL删除数据详解(最新整理)》:本文主要介绍MySQL删除数据的相关知识,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录一、前言二、mysql 中的三种删除方式1.DELETE语句✅ 基本语法: 示例:2.TRUNCATE语句✅ 基本语

MyBatisPlus如何优化千万级数据的CRUD

《MyBatisPlus如何优化千万级数据的CRUD》最近负责的一个项目,数据库表量级破千万,每次执行CRUD都像走钢丝,稍有不慎就引起数据库报警,本文就结合这个项目的实战经验,聊聊MyBatisPl... 目录背景一、MyBATis Plus 简介二、千万级数据的挑战三、优化 CRUD 的关键策略1. 查

python实现对数据公钥加密与私钥解密

《python实现对数据公钥加密与私钥解密》这篇文章主要为大家详细介绍了如何使用python实现对数据公钥加密与私钥解密,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录公钥私钥的生成使用公钥加密使用私钥解密公钥私钥的生成这一部分,使用python生成公钥与私钥,然后保存在两个文

mysql中的数据目录用法及说明

《mysql中的数据目录用法及说明》:本文主要介绍mysql中的数据目录用法及说明,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、背景2、版本3、数据目录4、总结1、背景安装mysql之后,在安装目录下会有一个data目录,我们创建的数据库、创建的表、插入的

Spring 框架之Springfox使用详解

《Spring框架之Springfox使用详解》Springfox是Spring框架的API文档工具,集成Swagger规范,自动生成文档并支持多语言/版本,模块化设计便于扩展,但存在版本兼容性、性... 目录核心功能工作原理模块化设计使用示例注意事项优缺点优点缺点总结适用场景建议总结Springfox 是

Navicat数据表的数据添加,删除及使用sql完成数据的添加过程

《Navicat数据表的数据添加,删除及使用sql完成数据的添加过程》:本文主要介绍Navicat数据表的数据添加,删除及使用sql完成数据的添加过程,具有很好的参考价值,希望对大家有所帮助,如有... 目录Navicat数据表数据添加,删除及使用sql完成数据添加选中操作的表则出现如下界面,查看左下角从左

解决未解析的依赖项:‘net.sf.json-lib:json-lib:jar:2.4‘问题

《解决未解析的依赖项:‘net.sf.json-lib:json-lib:jar:2.4‘问题》:本文主要介绍解决未解析的依赖项:‘net.sf.json-lib:json-lib:jar:2.4... 目录未解析的依赖项:‘net.sf.json-lib:json-lib:jar:2.4‘打开pom.XM