. Net prevents repeated page refresh submissions

Source: Internet
Author: User

Question:

When processing page events, we often encounter this situation: When we submit a page form, after the submission is successful, when we try to refresh the page by pressing F5, the data is repeatedly submitted. Therefore, the Asp.net Application Server cannot distinguish between adding by clicking the button normally or adding by F5 refresh. In this way, N identical data entries will exist in the database. Why is this problem not encountered in the original ASP development program? I think it is because the ASP program submits the form to another page for processing, and after processing this page, it will jump to another prompt page. In ASP, you only need to set the page to expire when you roll back. This effectively avoids repeated submission. However, in ASP. NET, basically all operations are based on event operations. In essence, an event is submitted to itself by the page, and the page cannot identify whether the normal operations are refresh again.


This problem was found in the first web application I developed at the end of the project, that is, when it was almost completed, and the entire page framework was very stable in the later stage of the project, at this time, modifying the entire framework is not worth the candle. We can only solve this problem in a flexible way. Fortunately, the data access layer of the entire system processes data through the stored procedure. Then, based on several basic key fields, we can determine whether the data exists in the table, if an exception exists, the system will throw an exception (mainly the insert Stored Procedure) and judge whether the data is submitted repeatedly through the stored procedure. This is only an appropriate solution. It is not a good solution because it is only useful for entity tables, but it may be difficult to judge the insertion of Relational Tables, relational Tables mainly store the primary keys of other tables. If multiple primary keys of another table are logically unique, it is better to judge, however, if multiple primary keys in another table cannot be logically unique, it is very difficult to determine the uniqueness. In addition, the object table cannot be used in some cases. If an object table allows the same fields except the primary key fields, it cannot be used to determine whether the data is submitted repeatedly.

How can this problem be solved? Microsoft provides a solution on msdn, which is provided by Italy's Dino Esposito. His idea is to save a flag on the client and a flag on the server, and compare the values of the two flags during submission to determine whether the two flags are submitted repeatedly.

First, let's look at the following code. The first is a refreshaction static class, which is mainly used to initialize the server session to save the value of the last ticket and compare the value of the client and server ticket, when it is detected that the refresh is not repeated, the client's ticket value will be updated to the server.

 

Public static class refreshaction <br/>{< br/> // constant <br/> // server ticket key <br/> Public const string lastrefreshticketentry = "_ lastrefreshticket "; <br/> // client ticket key <br/> Public const string currentrefreshticketentry = "_ currentrefreshticket "; <br/> // key used to save the refresh attribute <br/> Public const string pagerefreshentry = "ispagerefresh"; <br/> Private Static hashtable requesthistory = NULL; // store request history <br/> // Check if the F5 button is pressed <br/> Public static void check (httpcontext CTX) <br/>{< br/> // initialize the server ticket <br/> ensurerefreshticket (CTX ); <br/> // read the last provided ticket from the session <br/> int lastticket = getlastrefreshticket (CTX ); <br/> // read the ticket of the current page from the hidden domain in the request <br/> int thisticket = getcurrentrefreshticket (CTX ); <br/> // compare two tickets <br/> If (thisticket> lastticket ||< br/> (thisticket = lastticket & thisticket = 0 )) <br/> {<br/> // If The current ticket value is greater than the previous ticket value or <br/> // The current ticket value is equal to the previous ticket value, and the current ticket value is 0 (this is the first refresh) <br/> // update the last ticket value in the session to the current ticket value <br/> updatelstrefreshticket (CTX, thisticket ); <br/> // set whether to refresh the current page repeatedly to false <br/> CTX. items [pagerefreshentry] = false; <br/>}< br/> else <br/>{< br/> // set whether to refresh the current page repeatedly to true; <br/> CTX. items [pagerefreshentry] = true; <br/>}< br/> // confirm that the last ticket is not null <br/> static void ensurerefreshticket (httpcontext CTX) <Br/>{</P> <p> If (requesthistory = NULL) <br/> requesthistory = new hashtable (); <br/>}< br/> // obtain the ticket value of the previous request <br/> static int getlastrefreshticket (httpcontext CTX) <br/>{< br/> If (! Requesthistory. containskey (CTX. request. path) <br/> return 0; <br/> else <br/> return (INT) requesthistory [CTX. request. path]; <br/>}< br/> // The current ticket value saved from the current request to the hidden domain <br/> static int getcurrentrefreshticket (httpcontext CTX) <br/>{< br/> return convert. toint32 (CTX. request [currentrefreshticketentry]); <br/>}< br/> // Save the current ticket value to the last refresh ticket value in the session <br/> Private Static void updatelstrefreshticket (httpcontext CTX, int ticket) <br/>{< br/> requesthistory [CTX. request. path] = ticket; <br/>}< br/>}// end class <br/>

The following is an httpmodule class that detects the ticket values of both parties at the beginning of the request.

Public class refreshmodule: ihttpmodule <br/>{< br/> Public refreshmodule () <br/>{< br/> // <br/> // todo: add constructor logic here <br/> // <br/>}< br/> # region ihttpmodule members <br/> Public void dispose () <br/>{< br/> throw new notimplementedexception (); <br/>}< br/> Public void Init (httpapplication APP) <br/> {<br/> // event processor used to register a request's associated status, that is, when a request arrives at the server, <br/> // triggers the event first, handled by onacquirerqeuststate event <br/> app. acquirerequeststate + = new eventhandler (this. onacquirerequeststate); <br/>}< br/> # endregion <br/> private void onacquirerequeststate (Object sender, eventargs E) <br/>{< br/> httpapplication APP = sender as httpapplication; <br/> httpcontext CTX = app. context; <br/> refreshaction. check (CTX); // refreshaction class to check the context Rey of the current request <br/> return; <br/>}< br/>}// end class

The following is the base class inherited from the page. It is mainly used to save the number of refreshes and the value of the client ticket. It also provides an attribute to indicate whether the page is submitted repeatedly.

Public class mybasepage: system. web. UI. page <br/>{< br/> // constant <br/> Public const string refreshticketcounter = "refreshticketcounter"; <br/> Public mybasepage () <br/>{< br/> This. prerender + = new eventhandler (refreshpage_prerender ); <br/>}< br/> // whether to refresh the flag attributes repeatedly by pressing F5 on the flag page <br/> Public bool ispagerefresh <br/>{< br/> Get <BR/>{< br/> Object o = httpcontext. current. items [refreshaction. pagerefreshentry]; <br/> If (O = NULL) <br/> return false; <br/> return (bool) O; <br/>}< br/> // Add an internal counter for refreshing tickets <br/> Public void trackrefreshstate () <br/>{< br/> // initialize the refresh counter <br/> initrefreshstate (); <br/> // Add the refresh counter to 1, then put it into the session <br/> int ticket = convert. toint32 (session [refreshticketcounter]) + 1; <br/> session [refreshticketcounter] = ticket; <br/>}< br/> // initialize the refresh counter <br/> private void initrefreshstate () <br/> {<br/> If (session [refreshticketcounter] = NULL) <br/> session [refreshticketcounter] = 0; <br/>}< br/> // prerender event processor <br/> private void refreshpage_prerender (Object sender, eventargs E) <br/>{< br/> // Save the ticket value to the hidden domain before the page is displayed <br/> saverefreshstate (); <br/>}< br/> // create a hidden domain to save the ticket value of the current request. <br/> private void saverefreshstate () <br/> {<br/> // Add the value of the ticket counter to 1 and register the value to the hidden domain of the current ticket. <br/> int ticket = convert. toint32 (session [refreshticketcounter]) + 1; <br/> This. clientscript. registerhiddenfield (refreshaction. currentrefreshticketentry, ticket. tostring (); <br/>}< br/>}// end class

The following is a page inherited from the mybasepage class. It displays the corresponding values by judging whether the refresh attribute is repeated.
Public partial class repeatsubmit_add: mybasepage <br/>{< br/> protected void page_load (Object sender, eventargs E) <br/>{< br/>}< br/> protected void button#click (Object sender, eventargs e) <br/>{< br/> If (this. ispagerefresh) <br/>{< br/> This. label1.text = "this is a refresh page"; <br/>}< br/> else <br/>{< br/> This. trackrefreshstate (); <br/> This. label1.text = "this is the normal submission page"; <br/>}< br/>}

Add the httpmodule for request processing under the system. Web node of webconfig

<Httpmodules> <br/> <Add name = "mymodule" type = "refreshmodule"/> </P> <p> </pttpmodules>

 

The above code solves the repeated refresh code, so we will analyze this code. When we enter the page and click button1, how can we process the refreshed information.

When we enter the page, execute it in the following order:

1. When you enter the page for the first time, the system automatically calls the refreshmodule init event. In this event, we associate the request State event with the application object.

(Acquirerequeststate) registers an event processor (onacquirerequeststate), which is automatically called when we request the association status.

Onacquirerequeststate function.

2. Call the mybasepage constructor to register the prerender event processor.

3. The first access to the page is also an associated request. Now the onacquirerequeststate event processor is automatically called.

4. In the onacquirerequeststate event processor, we call the Check Method of the static refreshaction class. httpcontext is passed as a parameter.

1. In the check method, we first initialize the server ticket (saved in the session) and set the server ticket value to 0.
Then we get the last refresh ticket value, that is, the server ticket value, which is 0.
2. We get the current ticket value saved in the hidden domain in this request. Because this is the first request, the value is null and is converted to an integer and 0.
3. Compare two tickets. If the current ticket value is greater than the previous ticket value or the current ticket value is equal to the previous ticket value, and both are 0 (this indicates that it is the first time

Refresh), then we save the current ticket value as the previous ticket value. At this time, the client and server ticket value is 0. Indicates whether the page is Refresh repeatedly.

Set to false. If the comparison condition is false, set the refresh value to true.
4. onacquirerequeststate event handled

5. Now the prerender event is triggered before the page is displayed. This event calls saverefreshstate to save the value of the current client ticket.

1. First, add the value of the refresh count to 1. At this time, the refresh count is 0.
2. save the value of the refresh count plus 1 to the hidden domain of the current client ticket. The current ticket value is 1, The Last ticket value is 0, and the refresh Count value is 0.

6. When we click the button1 button, we first call the mybasepage constructor to register the prerender event processor.
7. Then the system automatically calls the acquirerequeststate event processor and calls the refreshaction check method.

1. The server ticket initialization function is useless because the server ticket already has a value.
2. The last refresh value of this ticket is 0.
3. the refresh value of the current ticket is 1.
4. Determine the ticket. When the current ticket value is greater than the value of the previous ticket, the current ticket value is updated to the previous ticket value. At this time, the last ticket value is 1.
5. Set whether to refresh the logo to false. At this time, the current ticket is 1 and the last ticket is 1.

8. In this case, the click event of button1 is called instead of the prerender event.

1. Check whether the ispagerefresh attribute of mybasepage is true. Obviously, this value is false.
2. Call the trackrefreshstate method of mybasepage. In this method, add 1 to the number of refreshes and save them in the session. Note that the current ticket is 1 at this time.

The refresh count is 1 and the refresh count is 1.

9. Call the saverefreshstate method of the prerender event processor at this time.

1. Add 1 to the refresh count and save it to the current ticket. At this time, the current ticket is 2, the last ticket is 1, and the refresh count is 1.

Then we can observe that the normal server (last time) ticket submission is always less than the client (current) ticket, and the number of refresh times is less than the current ticket. What if we press F5 to refresh?

? Let's take a look at the code.

1. Call the constructor of mybasepage to register the prerender event.

2. Call the Check Method in the acquirerequeststate event processor.

1. initialize the server ticket. The ticket is invalid at this time.
2. The last refresh ticket is 1, and the current ticket is also 1.
3. Determine whether two tickets are false. Set the refresh flag to false.

3. process the click event of button1

Judge the ispagerefresh attribute. Obviously, the repeated refresh mark is true, indicating that the refresh is refreshed by pressing F5.
It is strange here that when you click normally, the current ticket (client) is 2, the last ticket (server) is 1, and the refresh count is 1. Why do you press F5 to refresh

Is the previous ticket 1?

I was surprised at the beginning. Then I made an experiment to add the value of the hidden field when a button is clicked, and Add 1 to it. I read this hidden field during page_load, me

Click the button to increase the value of the hidden field. However, when I press F5, the value of the hidden field remains unchanged. So I guess, when I press F5, does not submit data on the current page to the server

The service end submits the cached data to the server. Therefore, the captured data value is the data submitted normally last time. At this time, the hidden Domain value still saves the latest ticket value.

But press F5. This value is not submitted to the server ., Until you click button1 to submit data normally.

So it can be better understood to roll back/forward. After I roll back, click button1. At this time, the value of the hidden domain on the previous page is submitted, but there is a ticket in the session.

The certificate value has been increased, so we can know that this is a refresh and submission operation.

The principles of this solution have been elaborated above. However, this solution still has some disadvantages. For example, if you click back for the first time, you can find that it is submitted repeatedly,

The second click will still indicate normal submission. Another drawback is that when the server-side ticket is saved in the session, the session will expire. At this time, you should add

Session Timeout. Another major drawback is that this solution cannot be used with IFRAME, because in IFRAME, the client page is loaded twice (that is, IFRAME

Parent window and IFRAME-oriented sub-window). As a result, the client ticket is the same as the server ticket. in IFRAME, commit is always repeated.

In practical applications, we certainly cannot use this solution as in the example. Because user controls are often used in projects

Wrap it into a user control and click button to throw an event, which is processed by the page. In this way, it is better to judge whether the page is submitted repeatedly. However

When an event is generated, it is automatically solved in the user control. Therefore, it is difficult to process and judge whether the page is repeatedly mentioned in the event. I think it is best to put this judgment

In the page_load event, if it is Refresh repeatedly, it will jump to another prompt page (interrupt the button processor), and then jump back, as the first time you enter this

Page. In this way, you can avoid judging whether the page is refreshed by submitting events each time.

Here I think we need to review the page loading sequence.

1. Page Constructor
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. After processing the page_load event, if there is any submitted event, start to process the submitted event. After processing the submitted event, process the remaining page events.
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)

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.