asp.net 检测页面是否刷新



来分析这样一种实际情况,即,在HTTP处理程序处理请求之前对请求进行筛选,这有助于实现一个原本不可能的特征。回发机制有一个严重的缺陷——如果用户刷新当前显示页面,则服务器上所采取的最后一个动作将盲目地重复。例如,如果作为前一次发送的结果添加了一个新记录,则应用程序会在另一次回发时试图插入一个完全相同的记录。当然,这会导致插入完全相同的记录,因而应当产生一个异常。这一缺陷自Web编程最先出现时就已经存在了,ASP.NET无疑不会引入它。要实现非重复的动作,必须采取一些对策,本质上将任何关键的服务器端操作转换为一个幂等性。在代数中,如果一个操作不管对它执行多少次结果都不变,我们就说该操作是幂等的。例如,看一看如下SQL命令:

DELETE FROM employees WHERE employeeid=9

我们可以对该命令连续执行1000次,但是最多只会删掉1个记录,即满足WHERE子句中设定的标准的记录。另请考虑如下命令:

INSERT INTO employees VALUES (...)

每次执行该命令,都有可能把一个新记录添加到employees表中。如果存在自动编码的键列或者非惟一的列,尤其会出现这种情况。如果表设计要求键是惟一的并且明确加以规定,则第2次运行该命令时会抛出一个SQL异常。

虽然刚才考虑的特殊情况通常在数据访问层(data access layer,简称DAL)解决,但是它的基本模式代表了大多数Web应用程序的一个常见方案。因此,待研究的问题是:怎样查明页面是因为一个显式的用户操作被回传,还是因为用户按下了F5键或页面刷新工具栏按钮呢?

1. 页面刷新操作的基本原理

页面刷新操作是一种内部浏览器操作,对此浏览器不会根据事件或回调提供任何外部通知。从技术上讲,页面刷新是由最新请求的“简单的”重复组成的。浏览器缓存它所服务的最新请求,并在用户按下页面刷新键或按钮时重新显示。我所知道的浏览器不会为页面刷新事件提供任何类型通知——即使有,无疑也不是一种公认标准。

据此可知,服务器端代码(例如,ASP.NET、经典ASP或ISAPI DLL)无法将刷新请求与一般的提交或回发请求相区分。为了帮助ASP.NET检测和处理页面刷新,我们需要创建外围机制,使两个在其他方面相同的请求看起来不同。所有已知的浏览器都是通过重新发送最后发送的HTTP请求来实现刷新;为了使该副本不同于原始请求,一个额外的服务必须添加其他参数,而 ASP.NET页面必须能够捕获它们。

我考虑了一些附加需求。解决方案不应依赖会话状态,而且不应使服务器内存负荷太重。它应该是相对容易部署的,而且应尽量不引人注目。

2. 解决方案的概要描述

本解决方案基于如下思想:每个请求被分配一个标签号,而HTTP模块将跟踪它处理的每个不同页面里最后服务的标签。如果该页面持有的标签号小于该页面的最后服务的标签,则只能表明服务了相同的请求——即,页面刷新。该解决方案由两个构造块组成:一个HTTP模块和一个自定义的页面类,前者对标签号作初步检查,后者自动地将一个渐进的标签号码添加到每个服务过的页面。使该特征起作用涉及两个步骤:首先,注册该HTTP模块;其次,在相关的应用程序中改变每个页面的基本的代码隐藏类以检测浏览器刷新。

HTTP模块位于HTTP运行库环境的中间,登记应用程序中的一个资源的每个请求。页面第一次被请求时(不是回发时),不分配任何标签。HTTP模块将生成一个新的标签号,并把它存储在HttpContext对象的Items集合中。此外,该模块将最后服务的标签的内部计数器初始化为0。随后该页面每次被请求时,该模块都将最后服务的标签与页面标签进行比较。如果页面标签更新一些,则该请求被认为是一次普通的回发;否则,它将被标记为一次页面刷新。表2.6总结了这两种场景及其相关的操作。

为了确保每个请求(除了第一次以外)都有一个合适的标签号,需要得到页面类的一些帮助。这就是为什么需要将每个打算支持该特征的页面的代码隐藏类设置为一个特定类——这是我们稍候将讨论的一个过程。该页面类将从HTTP模块接收两种不同的信息:要存储在随页面一起传送的一个隐藏字段中的下一个标签,以及该请求是否为页面刷新的信息。作为对开发人员的一项增值服务,代码隐藏类将提供一个额外的布尔属性:IsRefreshed,以允许开发人员了解请求是页面刷新还是常规回发。

*重要提示    HttpContext类上的Items集合是一个载体集合,是为了让HTTP模块将信息向下传递给实际负责服务请求的页面和HTTP处理程序而特意建立的。我们这里采用的HTTP模块在Items集合中设置两个数据项。一个数据项让页面知道请求是否为页面刷新;另一个数据项让页面知道下一个标签号是什么。让HTTP模块将下一个标签号传递给页面,满足使页面类的行为尽可能地简单和线性的目的,从而将大部分实现和执行负担转移给HTTP模块。

3. 解决方案的实现

我刚刚概述的解决方案有几个问题有待研究。首先,状态是必需的,我们把它保存在哪里?其次,对每个输入请求都将调用一个HTTP模块。如何区分对相同页面的请求呢?如何把信息传递给页面呢?你希望页面有多大的智能呢?

显然,这里所列的每个问题,都可以用不同于此处所介绍的方法进行设计和实现。为了得到一个可行的解决方案,这里作出的所有设计选择应当被认为是任意的,如果需要对该代码进行重新加工以更好地满足自己的目的,可以用等效的策略替换它。下一个实例中给出的代码版本,融入了我一直以来所收集的最宝贵的建议。这些建议之一如前一个重要提示所述,尽量将代码移到HTTP模块中。

public class RefreshModule : IHttpModule

{

public void Init(HttpApplication app) {

app.BeginRequest += new EventHandler(OnAcquireRequestState);

}

public void Dispose() {

}

void OnAcquireRequestState(object sender, EventArgs e) {

HttpApplication app = (HttpApplication) sender;

HttpContext ctx = app.Context;

RefreshAction.Check(ctx);

return;

}
}

该模块监听BeginRequest事件,结束调用RefreshAction辅助类上的Check方法。 

public class RefreshAction

{

static Hashtable requestHistory = null;
// Other string constants defined here
public static void Check(HttpContext ctx) {

// Initialize the ticket slot
EnsureRefreshTicket(ctx);

// Read the last ticket served in the session (from Session)
int lastTicket = GetLastRefreshTicket(ctx);

// Read the ticket of the current request (from a hidden field)
int thisTicket = GetCurrentRefreshTicket(ctx, lastTicket);

// Compare tickets
if (thisTicket > lastTicket ||(thisTicket==lastTicket && thisTicket==0)) {

UpdateLastRefreshTicket(ctx, thisTicket);

ctx.Items[PageRefreshEntry] = false;

}

else

ctx.Items[PageRefreshEntry] = true;

}

// Initialize the internal data store

static void EnsureRefreshTicket(HttpContext ctx)

{

if (requestHistory == null)

requestHistory = new Hashtable();

}

// Return the last-served ticket for the URL

static int GetLastRefreshTicket(HttpContext ctx)

{

// Extract and return the last ticket

if (!requestHistory.ContainsKey(ctx.Request.Path))

return 0;

else

return (int) requestHistory[ctx.Request.Path];

}

// Return the ticket associated with the page

static int GetCurrentRefreshTicket(HttpContext ctx, int lastTicket)

{

int ticket;

object o = ctx.Request[CurrentRefreshTicketEntry];

if (o == null)

ticket = lastTicket;

else

ticket = Convert.ToInt32(o);

ctx.Items[RefreshAction.NextPageTicketEntry] = ticket + 1;

return ticket;

}

// Store the last-served ticket for the URL

static void UpdateLastRefreshTicket(HttpContext ctx, int ticket)

{

requestHistory[ctx.Request.Path] = ticket;

}

}

Check方法操作如下:它将最后服务的标签(如果有)与页面提供的标签进行比较。该页面将标签号存储在一个通过Request对象接口读入的隐藏字段中。HTTP模块维护一个散列表,服务的每个不同的URL都有一个表项。该散列表中的值存储该URL的最后服务的标签。

注意    Item索引器属性,来设置最后服务的标签,因为Item重写已有的项。如果数据项已经存在,则Add方法只是返回。

除了创建HTTP模块,我们还需要安排一个页面类,以用作需要检测浏览器刷新的页面的基类。下面给出了这个页面类的代码:

// Assume to be in a custom namespace
public class Page : System.Web.UI.Page

{

public bool IsRefreshed {

get {

HttpContext ctx = HttpContext.Current;

object o = ctx.Items[RefreshAction.PageRefreshEntry];

if (o == null)

return false;

return (bool) o;

}

}

// Handle the PreRenderComplete event

protected override void OnPreRenderComplete(EventArgs e) {

base.OnPreRenderComplete(e);

SaveRefreshState();

}

// Create the hidden field to store the current request ticket

private void SaveRefreshState() {

HttpContext ctx = HttpContext.Current;

int ticket = (int) ctx.Items[RefreshAction.NextPageTicketEntry];

ClientScript.RegisterHiddenField(

RefreshAction.CurrentRefreshTicketEntry,

ticket.ToString());

}

}

该示例页面定义了一个新的公共布尔属性IsRefreshed。我们可以在代码中以使用IsPostBack或IsCallback那样的方法使用该属性。该实例页面重写了OnPreRenderComplete方法,用页面标签添加隐藏字段。如前所述,该页面标签是通过Items集合中的一个特别的(并且是任意命名的)项从HTTP模块中得到的。

public partial class TestRefresh : ProAspNet20.CS.Components.Page

{

protected void AddContactButton_Click(object sender, EventArgs e)

{

Msg.InnerText = "Added";

if (!this.IsRefreshed)

AddRecord(FName.Text, LName.Text);

else

Msg.InnerText = "Page refreshed";

BindData();

}

}

IsRefreshed属性允许我们决定在一个回发动作被请求时要做什么。在上述代码中,如果页面正在刷新,则不调用AddRecord方法。不用说,IsRefreshed仅适用于这里介绍的自定义页面类。自定义页面类并非只是添加该属性,它还要添加隐藏字段,这是该机制起作用所必不可少的。

时间: 2024-11-06 01:12:50

asp.net 检测页面是否刷新的相关文章

使用一个HttpModule拦截Http请求,来检测页面刷新(F5或正常的请求)

在Web Application中,有个问题就是:“我怎么来判断一个http请求到底是通过按F5刷新的请求还是正常的提交请求?” 相信了解ASP.NET的人知道我在说什么,会有同感,而且这其实不是一个很easy的问题.那是因为HTTP协议无状态的特性不允许请求之间保持状态. 我想大多数人关注这个问题是因为,页面post的时候或之后,不想浏览器重复提交. 所以问题可以简化为:“我怎么来判断一个POST请求到底是由F5按钮触发的还是正常的页面交互?” 幸运的是,这时候DOM的一个简单细节可以用来解决

Asp.net页面无刷新请求实现

Asp.net页面无刷新请求实现 <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="omAjaxSubmit.aspx.cs" Inherits="OMDemo.demo.omAjaxSubmit" %> <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml&q

asp.net 防止页面刷新或后退引起重复提交

 项目中经常遇到刷新后重复的向数据库增加一条相同的记录,造成数据重复,如何规避这些问题呢?下面我们就一起讨论一下在asp.net怎样防止页面刷新或后退引起重复提交数据的问题: 其实asp.net防止刷新是asp.net开发中经常遇到的问题.通常有多种方法来实现:(下面只是个人总结的一些方法)不全,也很希望朋友们能多多补充.谢谢. 1:请求转发(即在数据提交操作后立即转跳到其他页面,防止页面刷新引起回发操作) 2:不保存缓存 Response.Cache.SetNoStore(); (即提交后表单

如何做页面自动刷新,又不用让用户按回车键来提交数据!

假设叶面中有如下form: <form name=InputDate> ... </form> 如果页面要自动刷新,但该页面有Request.Form,那么通常会出现一个提示框,需要用户确定来提交数据,这就不是自动 刷新了.而对于监控或股票显示来说,不要用户干预又非常重要,下面就可以解决该问题: <Script Language="JavaScript"> <!-- var limit="0:60" //定义刷新时间 if

关于页面局部刷新例程

RS技术的一个具体例子 在前面的帖子中,我介绍了RS的基本工作原理,显然如果将RS技术运用在一个 网站的设计中将会有很多非凡的作用(尤其是它的那个最大的优点,可以在不刷新 页面的情况下调用服务端的代码).正是因为这个特点,你就可以象在编写一个 传统的C/S模式的程序一样,对数据库的数据进行处理了(我想大家一定很想知道具体应该如何来实现了,下面将给出一个具体的例子,调试这个破程序几乎快把我给累死,呵呵.) 从前面的描述可以看到,要使用RS技术就需要客户端和服务端满足下面两个条件: 1.客户端只需要

asp.net网站防恶意刷新的Cookies与Session解决方法

本文实例讲述了asp.net网站防恶意刷新的Cookies与Session解决方法,是WEB程序设计中非常实用的技巧.分享给大家供大家参考.具体实现方法如下: Session版实现方法: public double time; public const int freetime = 1;//防刷冰冻时间间隔,当前为1秒 #region 防恶意刷新 if (Session.SessionID == null) {   Response.End(); } else if (Session["sion

JQuery 实现页面无刷新

对于JQuery实现页面无刷新的效果,即:应用这个JQuery这个组件,可以实现在页面上加载数据库中的数据信息,但是并没有给用户页面刷新的感觉,这样既可以有效的进行数据交互,也可以不妨碍用户的其他操作.(http://itred.cnblogs.com    itRed: [email protected]) 在用JQuery实现页面无刷新的效果之前,我们需要掌握和了解一些基础知识,以方便我们在进行代码编写时更加的得心应手. 一.异步技术 浏览器预设是使用同步的方式发出请求并等待回应,为了处理浏

页面自动刷新常用方法

在javascript编程中,经常在更新数据之后用到location.reload()实现页面刷新. reload() 方法用于重新加载当前的文档.如果该方法没有规定参数,或者参数是 false,它就会用 HTTP 头 If-Modified-Since 来检测服务器上的文档是否已改变.如果文档已改变,reload() 会再次下载该文档.如果文档未改变,则该方法将从缓存中装载文档.这与用户单击浏览器的刷新按钮的效果是完全一样的. 我们都知道客户端浏览器是有缓存的,里面存放之前访问过的一些网页文件

ASP.NET MVC 页面重定向

在asp.net中页面重定向:Server.Execute("m2.aspx"); 服务器保存此页转向前的数据后,使页面转向到m2.aspx执行, 再返回本页继续执行.再将三者结果合并后返回给浏览器. 以上都是服务器端页面转向所以浏览器不出现页更改记录(显示的地址不会改变).因此,如果用户刷新此页,也许会出现一些其它意外情况. 此类页转向,可完成一些其它功能,比如访问到前一页面中的服务端控件. 3.Response.Redirect: 当浏览器请求aspx页面时,碰到Redirect(