Requirements
All code in the example application is intended to be run using .NET 2.0 only. There is no solution for the 1.0/1.1 versions.
Introduction
Most of you are familiar with the feature of all web-browsers that when you press the F5 button, the content of a page is refreshed. After the F5 button is pressed, the browser repeats the previous request to the page. Nothing wrong will actually happen when the previous request is made by the GET method. However, problems appear when the last request is made by the POST method. Let's consider an example where a user is transferring money to a shop to pay for some goods. Having completed this operation, the user refreshes the page and as a result the server code is executed once again with the same data. Thus, the user may accidentally pay twice.
Where are we going?
The purpose of this article is to give you a generic tool to improve your application. Moreover, you will not need to change the application code. If you have a good MVC framework, you can read this article just to find out how people work in worse conditions than you do. This article is intended for those who develop their application without any MVC frameworks or previously developed supporting applications. It will teach you how to make any page refresh manageable.
Here we go�
Since standard ASP.NET functionality doesn't allow us to track the process of refreshing the page, we should add this functionality ourselves. For this purpose we will use an HTTP module. Also, it's obvious that we should store some marker on a client. Then later we will be able to recognize whether the current request is due to the user having refreshed the page or due to the page being "valid."
We have just defined our core algorithm. This simple condition will regulate all of our further steps. Note that the module does nothing when a page is requested by the GET method. This is our second rule.
How to use the Refesh module
First of all, we need to modify Web.config for ASP.NET to see the RefreshModule module and call it.
<system.web>
<httpModules>
<add name="RefreshModule"
type="RefreshModule.Module, RefreshModule"/>
</httpModules>
</system.web>
Than we need to add a reference to the RefreshModule assembly, as well as add the using statement.
using RefreshModule;
It's enough to get information when a page is being refreshed. Now we can use the RefereshHelper.IsPageRefreshed property, which can be checked to determine that the page is refreshed.
[Refresh()]
public partial class Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if(IsPostBack && !RefereshHelper.IsPageRefreshed)
{
}
else
{
}
}
}
At first it looks great that we can recognize any refresh request while a page is executed. Actually, this approach suits better for new pages due to the fact that we know about this functionality. For existing pages, however, we have to reorganize the code. What if we don't care that the manual refresh handling or the code of our page is complex and we don't want to change anything? In this case, we should modify the attribute slightly to get automatic refresh handling.
[Refresh(AutoRedirect = true)]
public partial class Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
}
}
When the RefreshModule module intercepts a request, it checks whether the automatic refresh handling is needed or not. This is the simplest automatic handling case when the RefreshModule module performs redirection to the same page after any postback is completed. The biggest disadvantage of this approach is the additional round trip from a client to the server. So, this approach should be only suggested for using with "lightweight" pages. How can we improve this situation? What if we want to show some additional information to a user? It's easy to implement. All we need to do is to keep modifying the RefreshAttribute attribute.
[Refresh(MessageModulePath = "MessageControl.ascx")]
public partial class Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
}
}
We pointed to control that will be loaded -- and its output sent to a browser as a response -- while the page is being refreshed. Actually, RefreshModule builds a valid response page based on a template which you can modify as needed. This template is defined in the RefreshModule code. If we want to customize the appearance by applying styles or adding JavaScript code, we should implement the INotificationProvider interface and register it. We can do this as follows.
public interface INotificationProvider
{
string Title
{
get;
}
string HeadHTML
{
get;
}
}
public class NotificationProvider : INotificationProvider
{
public string Title
{
get { return "Custom title"; }
}
public string HeadHTML
{
get
{
return
@"<link rel=""STYLESHEET"" type=""text/css""
href=""styles.css"">
<style>
#info table{
margin: 100px 0px 0px 200px;
}
</style>
";
}
}
}
void Application_Start(object sender, EventArgs e)
{
RefereshHelper.RegisterNotificationProvider(new NotificationProvider());
}
It's going well so far. So let's go ahead and learn how to show a better result to the user. To do that, we have to take these steps:
- Create an empty page which will be used as a template for notification messages.
- Add the
RefreshUIAttribute attribute to the page.
[Refresh(MessageModulePath = "Notification.ascx")]
[RefreshUI("__RefreshTemplate.aspx", "ContentPlaceHolder1")]
public partial class SmartAttributeNotification : Page
{
protected void Page_Load(object sender, EventArgs e)
{
}
}
The RefreshUIAttribute attribute has two parameters: template page path and container for the notification control, as defined by applying the RefreshAttribute attribute. One issue that arises here is the generation of an additional page or pages which are not necessarily supposed to be viewed by the users. To make viewing the template pages impossible, I recommend using complex names for such pages and placing them in a separate folder, e.g. [RefreshUI("RefreshTemplates/_CDFECE8A56874700877B0C22D256E0CD_.aspx", "ContentPlaceHolder1")]. This URL is much better protected from being accidentally viewed. On the other hand, it is easier to make a mistake while typing a URL like this one. Fortunately, the RefreshModule supports another method of defining a template page and container.
[Refresh(MessageModulePath = "Notification.ascx")]
public partial class SmartIntrefaceNotification : Page,
IRefreshWith<UISample>
{
protected void Page_Load(object sender, EventArgs e)
{
}
}
As you can see, the page inherits a generic IRefreshWith<T> interface. That interface doesn't contain any method and is used only as a marker. Real values are defined in the interface's typed parameter. In the example, the actual date is passed through the UISample class which implements the IUIProvider interface. Note that when a template page is executed, the RefreshModule replaces the action attribute value of the <form> tag with the original request's URL. See the example application for more details.
Summary
Now you have a tool that will help you to make your application appear and work more professionally. I didn't want to make this module complex with extraneous features and settings, so it should take you just a couple of simple steps to get it working. If you need more features or customization possibilities, it's better to use any well-know application framework. Below you can find two screenshots of a test application.
Figure 1: Test page after submit is completed

Figure 2: Automatic response result
History
- 18 May, 2007 - Original version posted
| You must Sign In to use this message board. |
|
|
 |
|
|
 |
|
 |
My question might be a little bit annoying to you but please help me out here as VB is the only thing that I am familiar with. As I am new to Web application and no good knowledge of c#. I've tried some other solutions for some reasons they just didn't work out for me such as pictures not showing etc. From your sample application I didn't see page inheritance so I thought this might fit my case. I would appreciate if you can help. How do I implement in VB code-beside page.
|
| Sign In·View Thread·PermaLink | 1.33/5 |
|
|
|
 |
|
 |
This is really well designed using interceptors. I have seen implementations with same technique but requiring specific Page base class or messy coding. I will surely consider using your framework in my next project.
One minor limitation is that this solution won't work if the session get expired (only applicable for application that does not require session validations).
Keep up good work!
Hasith
|
| Sign In·View Thread·PermaLink | 2.00/5 |
|
|
|
 |
|
 |
Hi there. Thanks for writing this article. However I have 2 questions to ask and will like to hear from you.
a) Is there a reason you choose not to use an asp.net button control instead of an html input?
b) What if I have a multiview with tabs? As you know when I navigate each tab, it will cause a postback to the server. How do you differentiate between this and a normal button click?
Thanks again :P
Regards, Chua Wen Ching
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Hi.
Here is my answers on your questions: a) There is no any reason for my choice. No difference bittwen them. b) Actually the module handles any request no matter what caused it. So there is no different when you press a submit button or navigate to any tab.
BTW, I'm going to publish updated code you can check it too.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Great. I am looking forward on that :P I assume when you update the codes, you will place a reference under the history section right :P
Regards, Chua Wen Ching Visit us at http://www.necoders.com
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Hi, I must say that I've see your solution implemented almost identically by Dino Esposito in his book, Programming ASP.NET 2.0 Advanced Topics.
However, like his implementation yours won't work when you open two pages with the same url.
Just follow these steps:
- Open your default.aspx in one browser window - Open your default.aspx in another browser window - Switch back to the first opened window and click submit - You'll get a "Page was refreshed", while actually it wasn't.
I don't want to advertise my own work but I've implemented a solution which prevents this from happening here[^] for those who need that, but I suggest you do some more testing before proposing a solution which simply doesn't work.
Simone Busoli
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Hi, Simone.
I realy appriciate your comment here, even a such agressive. I want to say that I have never read the book you mentioned. I guess that there are no many ways to solve the Refresh problem so original idias could be similar but implemetation should be different. I failed to reproduce the issue you described. It works fine with many browser windows. BTW I have to admit that there is a very little bug in the code that I haev already fixied and will update the article shortly. I would like to point you that this midule gives you not only boolean flag that you can check wehther a page is being refreshed or isn't but also helps you build automatic response when a page is being refreshed that is more imprtant. Please note I implemented my own ideas instead of improving somebody's code.
If you find any other issue you are welcome.
|
| Sign In·View Thread·PermaLink | 2.00/5 |
|
|
|
 |
|
 |
I'm sorry but it won't work. If you follow exactly the steps I've described you'll get a page refresh message when in fact no refresh occurred.
You can even guess it thinking about your implementation. You are assigning a ticket to each page which is posted back, and keep some data in the session. If the same page is open twice in the same session those data gets messed up.
I've created a short screenshot[^] to show the issue.
Simone Busoli
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Simone,
Actually we can open a new browser windows in several ways: 1.Two or more windows share the same session. (CTRL+N) 2.Two or more windows have their own sessions. (just click IE icon)
In the case N1 (just case1) we will have some "non standart" behaviour due to browser will make exact copy of the current page.It takes cashed page if there is any or repeate prevuous request. When you try to submit data with the second window the module code consider this situation as if a page is being refreshed. Note it happens only the previus request was made with POST method while the previous request was made with GET method it works correct(updated version ).We can do nothig with IE cash so we can't achive perfect result here.
In the case N2 it works fine.No problem comes here.
I have read your article and found the your idea works fine untill I turn off JavaScript in my browser. I didn't go into great details with your artical and just I wand to say that if the Guids queue or cash on the server is too 'big' it could impact performance. Correct me if I'm wrong.
I had to refuse using any JS code in this area because of users can just turn off JavaScript on his/her PC.
Tanks for your comments.
-- modified at 8:17 Thursday 24th May, 2007
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Valery_Minsk wrote: Actually we can open a new browser windows in several ways: 1.Two or more windows share the same session. (CTRL+N) 2.Two or more windows have their own sessions. (just click IE icon)
In the case N1 (just case1) we will have some "non standart" behaviour due to browser will make exact copy of the current page.It takes cashed page if there is any or repeate prevuous request. When you try to submit data with the second window the module code consider this situation as if a page is being refreshed. Note it happens only the previus request was made with POST method while the previous request was made with GET method it works correct(updated version ).We can do nothig with IE cash so we can't achive perfect result here.
In the case N2 it works fine.No problem comes here.
Yes, but I guess that most users are actually using tabbed browsing by now, so the issue will be there more often than not. Furthermore, I posted my solution being aware of its limitations.
Valery_Minsk wrote: I have read your article and found the your idea works fine untill I turn off JavaScript in my browser. I didn't go into great details with your artical and just I wand to say that if the Guids queue or cash on the server is too 'big' it could impact performance. Correct me if I'm wrong.
I had to refuse using any JS code in this area because of users can just turn off JavaScript on his/her PC.
Yes, in fact I won't say that my solution is better, I just said that it prevents this problem from happening, but since relying on Javascript it shouldn't be taken as a rock-solid solution.
However, considering a context in which most users are using tabbed browsing and most websites require Javascript to be enabled to work correctly, my solution can be considered stronger than yours.
Furthermore, my solution introduces false negatives, which I think are more tolerable than the false positives introduced by yours.
Take as an example a website like Amazon. I use to browse there with multiple tabs to compare books. This would lead to false positives always saying that the page is refreshed whenever I do a postback.
Simone Busoli
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
I can't say that I clearly unerstand the example with Amazon website. But I would like to remind that my example works not very "properly" only when a session is shared among several windows. Moreover, only in case when the user clones browser window after postback is being done and it never result in an application crash. I think my example provide a bit more automatic functionality.
I developed this example with different point of view and limitation in mind.
I agree that both articals have disadvantages and the only matter here is to get answer on the folowing question. Which articale disadvantages/advantages are more acceptable at the current time and in the current enviroment.
Thank you argued logically
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
 |
The IsPageRefreshed property is a neat idea. I wonder if Microsoft will take note and add it to a future version of ASP.NET.
But personally I've found that the best solution for such situations - where a user submits a form, and you want to perform actions with it and then respond without leaving the user in a situation where they could hit F5 and accidentally redo what they did - is to process the posted data and then redirect the user to a new page that is the next step. i.e. a user submits an order, you process it, then redirect them to a Success page. I usually only leave users on the original post page if the post was rejected due to data errors, and they need to correct it. So if the user hits F5 on the redirected page, it doesn't matter. It also results in smaller, modular, simpler code without a whole lot of if/thens.
Ron @ Byzet
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Thanks for your comment.
Actually a user can just press browsers' Back button several times to return on the previous page(s) and than press the F5 button. In that case a new order will be submitted. Also there are sutiations when you are obliged to leave the user on the same page because of marketing requirements.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
A user who would actually hit the back button and refresh an order page deserves what s/he gets, in my opinion! But good points. I don't really do ecommerce sites, so I've never had to worry about going to such efforts to make sure the user doesn't accidentally resubmit an order. Although I'm always annoyed that Amazon's one click ordering assumes repeat clicks are a mistake, when really I just want to increase the quantity.
But anyway, your solution really is a simple and elegant way to check for refreshes - I'll definitely keep it in mind for future projects. Thank you!
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|