上面的代码就是解决重复刷新的代码,那么我们来分析这个代码,当我们进入页面然后点击Button1是怎么来处理这些刷新的信息的。
当我们进入页面时按照下面的顺序来执行
1、当第一次进入页面,首先由系统自动调用RefreshModule的Init事件,在此事件里,我们给Application对象的请求关联状态事件
(AcquireRequestState)注册了一个事件处理器(OnAcquireRequestState),那么当我们请求关联状态时会自动调用
OnAcquireRequestState函数。
2、调用MyBasePage的构造函数,在此函数里注册了PreRender的事件处理器。
3、第一次进入页面也是一个关联请求,现在会自动调用OnAcquireRequestState事件处理器
4、在OnAcquireRequestState事件处理器中我们调用静态类RefreshAction静态类的Check方法,HttpContext作为参数传入
1. 在Check方法里,我们首先初始化服务端票证(保存在Session里),让服务端票证的值为0。
然后我们得到上一次刷新的票证值也就是服务端票证值,它为0。
2. 我们得到这次请求保存在隐藏域里的当前的票证值,因为这是第一次请求,那么这个值为空,转换为整数,为0
3. 对比两个票证,如果当前的票证值大于上一次的票证值或者当前票证值等于上一次票证值,并且两者都为0(这表明是第一次
刷新),那么我们将当前的票证值保存为上一次票证值。这时候,客户端和服务端的票证值都为0。将标志页面是否是重复刷新的值
设置为false。如果对比条件为假,那么设置重复刷新的值为 true。
4. OnAcquireRequestState事件处理完毕
5、现在触发了PreRender事件,在页面呈现之前触发,此事件调用SaveRefreshState用来保存客户端当前票证的值。
1. 首先将刷新次数的值得到并加1,此时刷新次数为0。
2. 将刷新次数的加1的值保存到客户端当前票证的隐藏域中,那么现在当前票证的值为1,上一次票证值为0,刷新次数的值为0。
6、当我们点击Button1按钮的时候首先调用MyBasePage的构造函数注册PreRender的事件处理器
7、然后系统自动调用AcquireRequestState事件处理器,调用RefreshAction的Check方法
1. 初始化服务端票证函数无用,因为服务端票证已经存在值
2. 得到上一此票证刷新的值为0
3. 得到当前票证刷新的值为1
4. 判断票证,这时当前票证值是大于上次票证的值,将当前票证的值更新到上一次票证值,此时上一次票证值为1
5. 设置是否重刷新标志为false,这时候当前票证为1,上一次票证为1。
8、这时候不是调用PreRender事件,而是调用Button1的Click事件。
1. 判断MyBasePage的IsPageRefresh属性是否为真,很显然,现在这个值为假
2. 那么调用MyBasePage的TrackRefreshState方法,在这个方法里将刷新次数加1,保存在Session里。注意此时当前票证为1,上一
次票证为1,刷新次数为1。
9、那么这时候调用PreRender事件处理器的SaveRefreshState方法
1. 将刷新次数加1,并且保存到当前票证里,那么这时候当前票证为2,上一次票证为1,刷新次数为1。
那么我们可以观察到正常的提交服务端(上一次)票证始终小于客户端(当前)票证,刷新次数也小于当前票证,那么如果是按F5刷新呢
?我们观察一下代码
1、调用MyBasePage的构造函数注册PreRender事件
2、调用AcquireRequestState事件处理器里的Check方法
1. 初始化服务端票证,此时无效
2. 得到上一次刷新的票证为1,得到当前的票证也为1
3. 判断两个票证,此时肯定为假,那么设置重复刷新标志为false
3、处理button1的Click事件
判断IsPageRefresh属性,显然此时重复刷新标志为true,表明此次刷新是按F5刷新的。
在这里很奇怪,在正常点击时,当前票证(客户端)为2,上一次票证(服务端)为1,刷新次数为1,那么为什么按F5刷新以后,当
前票证为1了?
我刚开始也很奇怪,然后我做了一个实验,使一个按钮点击时增加隐藏域的值,让他加1,在Page_Load的时候去读取这个隐藏域,我
点击button让隐藏域的值增加,但是当我按F5时,隐藏域的值始终保持不变,那么我猜测,按F5时,不是将当前页面的数据提交给服
务端,是将缓存的数据提交给服务端,所以我们捕获到的数据值就是上一次正常提交的数据,此时隐藏域的值仍然保存最新的票证值
,但是按F5,这个值不会提交给服务器。,直到正常的点击Button1提交数据。
那么回退/前进可以说更好理解,我回退之后再点击Button1,此时提交的是上一个页面的隐藏域的值,但是存在Session里上一个票
证的值已经增加了,那么对比的时候就可以知道这是重复刷新提交的操作。
上面是这个解决方案件的原理已经阐述完毕。但是这个解决方案仍然有一定的缺点。如回退之后第一次点击可以探明是重复提交,但
是第二次点击仍然会说明是正常提交。还有一个缺点,服务端票证保存在Session里,Session是会过期的,这时候应该加一个
Session超时的判断。还有一个最大的缺点,此解决方案不能配合IFrame使用,因为在IFrame中,客户端页面会加载两次(即IFrame
外的父窗口和IFrame导向的子窗口),导致客户端票证与服务端票证相同,那么在IFrame中,提交始终是重复提交。
在实际应用中,我们肯定不能像示例那样使用这个解决方案。因为我们在项目中经常会使用用户控件,一般我们是将Button和文本框
包装成一个用户控件,点击Button抛出一个事件,由页面处理。这样还比较好判断页面是否是重复提交的。但是如果在Button在不抛
出事件,就在用户控件里自行解决,那么这样比较难以实现在事件中处理和判断页面是否重复提及。我认为这个判断最好放在
Page_Load事件里,如果是重复刷新的就跳转到另外一个提示页面(中断button的处理器),然后在跳转回来,作为第一次进入这个
页面。这样就可以避免在每次提交事件来做页面是否是刷新页面的判断。
在这里我觉得需要回顾一下Page的加载顺序。
1. Page的构造函数
2. protected void Page_PreInit(object sender, EventArgs e)
3. protected void Page_Init(object sender, EventArgs e)
4. protected void Page_InitCompleted(object send, EventArgs e)
5. protected void Page_PreLoad(object sender, EventArgs e)
6. protected void Page_Load(object sender, EventArgs e)
7. 处理完Page_Load事件,如果有提交事件就开始处理提交事件,在处理完提交事件之后在处理剩下的Page事件
8. protected void Page_LoadComplete(object sender, EventArgs e)
9. protected void Page_PreRender(object sender, EventArgs e)
10. protected void Page_PreRenderComplete(object sender, EventArgs e)
11. protected void Page_SaveStateComplete(object sender, EventArgs e)