To analyze the fact that the request is filtered before the HTTP handler processes the request, which helps to achieve an otherwise impossible feature. The postback mechanism has a serious flaw--if the user refreshes the currently displayed page, the last action taken on the server will be repeated blindly. For example, if you add a new record as the result of a previous send, the application tries to insert an identical record at another postback. This, of course, results in an identical record being inserted, and therefore an exception should be generated. This flaw has existed since the Web programming first appeared, ASP. NET will undoubtedly not introduce it. To achieve a non-repetitive action, some countermeasures must be taken to essentially convert any critical server-side operations to a idempotent. In algebra, if an operation does not change the number of times it executes on it, we say that the operation is idempotent. For example, take a look at the following SQL command:
DELETE from Employees WHERE employeeid=9
We can execute the command 1000 times in a row, but only a maximum of 1 records are deleted, that is, a record that satisfies the criteria set in the WHERE clause. Also, consider the following command:
INSERT into Employees VALUES (...)
Each time the command is executed, it is possible to add a new record to the Employees table. This is especially true if there are auto-coded key columns or non-unique columns. If the table design requires the key to be unique and explicitly defined, a SQL exception is thrown when the command is run for the 2nd time.
Although the special case that you just considered is usually addressed in the data Access layer, or DAL, the basic schema represents a common scenario for most Web applications. So the question to be researched is: How do you find out if a page is being returned because of an explicit user action, or because the user pressed the F5 key or the Page Refresh toolbar button?
1. Fundamentals of page Refresh operations
A page refresh operation is an internal browser operation that does not provide any external notifications based on events or callbacks. Technically, page refreshes are made up of "simple" repetitions of the most recent request. The browser caches the latest requests it serves and displays them again when the user presses the page Refresh key or button. The browser I know does not provide any type of notification for page refresh events--even if it does, it is certainly not a recognized standard.
As a result, server-side code (for example, ASP. NET, Classic ASP, or ISAPI DLLs) cannot distinguish a refresh request from a normal commit or postback request. To help ASP. NET to detect and Process page refreshes, we need to create a perimeter mechanism that makes two of the same requests look different in other ways. All known browsers implement refreshes by resending the last HTTP request sent, and in order for the replica to be different from the original request, an extra service must add additional parameters, while the ASP. NET pages must be able to capture them.
I have considered some additional requirements. The solution should not rely on session state, and should not cause the server memory load to be too heavy. It should be relatively easy to deploy and should be as unobtrusive as possible.
2. Summary description of the solution
The solution is based on the idea that each request is assigned a tag number, and the HTTP module tracks the label of the last service on each of the different pages it handles. If the page holds a label number that is smaller than the last service label for that page, it can only indicate that the service has the same request-that is, the page refreshes. The solution consists of two building blocks: an HTTP module and a custom page class that initially checks the tag number, which automatically adds a progressive label number to each service page. Making the feature work involves two steps: first, register the HTTP module, and second, change the basic code-behind class for each page in the relevant application to detect browser refreshes.
The HTTP module resides in the middle of the HTTP runtime environment, registering each request for a resource in the application. When the page is first requested (not postback), no labels are assigned. The HTTP module will generate a new tag number and store it in the items collection of the HttpContext object. In addition, the module initializes the internal counter of the last service's label to 0. The module then compares the label of the last service to the page label each time the page is requested. If the page label is updated, the request is considered a normal postback, otherwise it will be marked as a page refresh. Table 2.6 summarizes the two scenarios and their associated operations.
To ensure that each request (except for the first time) has a suitable tag number, you need some help with the page class. That is why it is necessary to set the code-behind class for each page that is intended to support that feature to a specific class-a process that we will discuss later. The page class receives two different kinds of information from the HTTP module: The next label in a hidden field to be stored along with the page, and whether the request is information for page refresh. As a value-added service to developers, the code-behind class provides an additional Boolean attribute: Isrefreshed, which allows developers to understand whether a request is a page refresh or a regular postback.
* Important Note the Items collection on the HttpContext class is a collection of vectors that are deliberately created to allow the HTTP module to pass information down to the page and HTTP handlers that are actually responsible for the service request. The HTTP module we use here sets two data items in the Items collection. One data item lets the page know whether the request is a page refresh, and another data item that lets the page know what the next tag number is. Having the HTTP module pass the next tag number to the page satisfies the purpose of making the behavior of the page class as simple and linear as possible, thus transferring most of the implementation and execution burden to the HTTP module.
3. Implementation of the solution
The solution I just outlined has several issues to study. First, the state is required, where do we keep it? Second, an HTTP module is called for each input request. How do I differentiate a request for the same page? How to pass information to the page? How much intelligence do you want the page to be?
Obviously, each of the issues listed here can be designed and implemented in a different way than the one described here. To get a workable solution, all design choices made here should be considered arbitrary, and if the code needs to be re-machined to better serve its purpose, it can be replaced with an equivalent policy. The code version given in the next example incorporates the most valuable advice I've ever collected. One of these recommendations, as described in the previous important note, is to move the code to the HTTP module as much as possible.
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) se NDEr; HttpContext CTX = App. Context; Refreshaction.check (CTX); return;}
}
public class refreshaction{static Hashtable requesthistory = null;//other string constants defined herepublic static void Check (HttpContext ctx) {//Initialize the Ticket slotensurerefreshticket (CTX);//Read The last ticket served in the Sessi On (from Session) int lastticket = Getlastrefreshticket (CTX);//Read The ticket of the current request (from a hidden field int thisticket = Getcurrentrefreshticket (CTX, lastticket);//Compare Ticketsif (Thisticket > Lastticket | | (Thisticket==lastticket && thisticket==0)) {Updatelastrefreshticket (CTX, thisticket); ctx. Items[pagerefreshentry] = false;} Elsectx. Items[pagerefreshentry] = true;} Initialize the internal data storestatic void Ensurerefreshticket (HttpContext ctx) {if (requesthistory = = null) requesth istory = new Hashtable ();} Return the last-served ticket for the urlstatic int getlastrefreshticket (HttpContext ctx) {//Extract and return the Las T Ticketif (!requesthistory.containskey (CTX). Request.path)) return 0;elsereturn (int) rEquesthistory[ctx. Request.path];} Return the ticket associated with the pagestatic int getcurrentrefreshticket (HttpContext ctx, int lastticket) {int Ticke T;object o = ctx. Request[currentrefreshticketentry];if (o = = null) ticket = Lastticket;elseticket = Convert.ToInt32 (o); ctx. Items[refreshaction.nextpageticketentry] = ticket + 1;return ticket;} Store the last-served ticket for the urlstatic void Updatelastrefreshticket (HttpContext ctx, int ticket) {requesthistory [CTX. Request.path] = ticket;}}
The check method operates as follows: It compares the label of the last service, if any, to the label provided by the page. This page stores the tag number in a hidden field that is read through the Request object interface. The HTTP module maintains a hash list, and each of the different URLs for the service has a table entry. The value in the hash table stores the label of the last service for that URL.
Note the item indexer property to set the last service label because item overrides an existing item. If the data item already exists, the Add method simply returns.
In addition to creating an HTTP module, we also need to schedule a page class to be used as the base class for pages that need to detect browser refreshes. The following shows the code for this page class:
Assume to BES in a custom namespacepublic 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 eventprotected override void Onprerendercomplete (EventArgs e) {base. Onprerendercomplete (e); Saverefreshstate ();} Create The hidden field to store the current request ticketprivate void Saverefreshstate () {HttpContext ctx = Httpconte Xt. Current;int ticket = (int) ctx. Items[refreshaction.nextpageticketentry]; Clientscript.registerhiddenfield (Refreshaction.currentrefreshticketentry,ticket. ToString ());}}
The example page defines a new public boolean property, Isrefreshed. We can use this property in our code in a way that uses IsPostBack or Iscallback. The instance page overrides the Onprerendercomplete method and adds a hidden field with a page label. As mentioned earlier, the page label is obtained from the HTTP module through a special (and arbitrarily named) entry in the Items collection.
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); elsemsg.innertext = "page refreshed"; Binddata ();}}
The Isrefreshed property allows us to decide what to do when a postback action is requested. In the preceding code, if the page is being refreshed, the AddRecord method is not called. Needless to say, isrefreshed only applies to the custom page classes described here. Customizing the page class is not just adding the property, it also adds a hidden field, which is essential for the mechanism to work.
ASP. NET detects if the page is refreshed