Form Submission and Refresh

J

Jim Little

Hello,

I'm driving myself crazy either because I'm missing something about ASP.NET,
or what I'm trying to do simply can't be done.

First, I am not using session variables to track state. My app will
ultimately be web-farmed, so I resisted using either session variables.

Second, what I'm trying to do is easy. What I'm trying to prevent seems
impossible.

I have web form "a" and web form "b". a.aspx contains a form; b.aspx shows
the data submitted by a. I am using the Context object to make information
from a.aspx available to b.aspx after a Server.Transfer("b.aspx") operation.
This works, no problem.

The problem is that if the user hits <F5> (refresh) in IE while they're
viewing b.aspx, then a.aspx is re-displayed and a "duplicate row" error
message is displayed, since a.aspx inserts a unique row into a database
table.

I am trying to "detect" when a user refreshes document b.aspx.

I understand that if I used Response.Redirect from a.aspx to go to b.aspx,
then this problem wouldn't occur. But then I have a new problem: how do I
make data submitted to a.aspx available in b.aspx without using session
variable or querystring variables? The session variables are not possible
for the reason I mentioned earlier; querystring variables will be messy,
because there are a dozen or so fields entered in a.aspx...

The only thing I could think of is to use Response.Redirect("b.aspx"), and
pass only the record-key information. I could then read the submitted data
from the database. But that seems silly, since the user just submitted the
data a moment before. Taking a database hit seems wasteful under these
circumstances.

Unless I'm missing something big, it seems that there is no good way to use
Context and Server.Transfer()--and also detect a refresh so that the form is
NOT redisplayed but, rather, the summary (b.aspx) page could be displayed
instead.

Any ideas would be greatly appreciated.

Thank you so much,
Jim
 
S

shaun duke

Jim,
Just a thought, don't know if you have capability to set viewstate=true
....
Can you use a pair of panels a and b. on a single page a.aspx.
Panel a holds the data entry fields and is visible by default, panel b
holds the summary fields and is hidden by default.
At the end of your database insert, switch the panel visiblities etc
etc.

The viewstate should mean that panel b should remain visible after an
F5 without any addition row insert.

Shaun
 
J

Jim Little

Hi Shaun:

No, I tried that. In fact, I had first used panels as my solution until I
discovered the refresh problem. If you hit <F5> when panel b is showing and
panel a is hidden, the "state" of the entire form is back to its original
state. That means that panel a's visibility is true and panel b's is false.

I created a simple test for with a button and two panels. When the page
first runs, I display panel1 (a). When you click the button, I show panel2
(b). If you then hit <F5> (refresh) and look at the value of Panel1.Visible,
it will be true, not false.

Any other ideas?

Thank you,
-- Jim

private void Page_Load(object sender, System.EventArgs e)

{

// Put user code to initialize the page here

if (!this.IsPostBack)

{

this.Panel1.Visible = true;

this.Panel2.Visible = false;

}

}

#region Web Form Designer generated code

override protected void OnInit(EventArgs e)

{

//

// CODEGEN: This call is required by the ASP.NET Web Form Designer.

//

base.OnInit(e);

InitializeComponent();

}


/// <summary>

/// Required method for Designer support - do not modify

/// the contents of this method with the code editor.

/// </summary>

private void InitializeComponent()

{

this.Button1.Click += new System.EventHandler(this.Button1_Click);

this.Load += new System.EventHandler(this.Page_Load);

}

#endregion

private void Button1_Click(object sender, System.EventArgs e)

{

this.Panel1.Visible = false;

this.Panel2.Visible = true;

}

}
 
S

shaun duke

Jim,

It took my brain a while to get into C mode but ..

I notice that you are switching the panel visibilites in the page load.


I use this all the time and have the panel visibilty defaults set at
tag level
eg <asp:panel id="a" runat="server" visible="true"> and <asp:panel
id="b" runat="server" visible="false">

Then I make sure that the visibilites are not switched during page
load, only in response to a button click.


Shaun
 
J

Jim Little

Hi Shaun:

I can assure you that it makes no difference whether you set the visibility
in the aspx document, or in the code-behind's page load event. That
function's conditional "if (!this.IsPostBack)" assures that the panels'
visibility is set only when the form is first rendered. That's the same as
setting those properties in the HTML code and not placing them in the load
function.

The issue is not that I can't get the visibilities to work. I want to
clarify that the example code I submitted works as expected, even if refresh
is hit. The problem is that hitting refresh causes the button's click even
code to fire again. That's fine when all you want to do is hide or show
panels. But when you are trying to insert data into a database when the user
clicks that button, then there is a problem. You don't know when the button
click event fires whether it is firing because the user hit refresh, or
because they clicked the button! So, if you can imagine a database unique
insert operation in the click event, you will realize that the insert will
fail the second time around. If it fails you don't know whether it failed
because the user submitted the same data as an existing record (in which
case you would want to show the insert error and let them enter some
modified data), or it failed because the user hit refresh. Either way, it
would be wrong to show Panel2 if Panel2's visibility depends on the success
of the database insert (Panel2 is essentially a "Thank you" page that
summarizes submitted data).

I hope I explained this situation well enough. Let me know if I can clarity.
Meanwhile, I'm still stuck.

Thanks,
-- Jim
 
G

Guest

On the initial page set a variable in a hidden form input, I use guids for
this. When you send the guid to the client save it in a db. When you save
the form data, delete the guid out of the table. If you try and save with an
invalid guid consider the form already saved.
 
S

shaun duke

Jim,

Yes you are quite right I missed the !, I am not a C person I am
affraid.

A quick trip over to the javascript groups reveals that it is not
possible to 'intercept' the F5 or indeed the browser refresh and there
is the back button that can also play havoc.

How is the uniqueness in your table row defined ? If you are creating a
unique key in the page and passing the key in the insert command, can
you test the uniqueness of your key prior to the insert in the stored
proc ...

if exists(select * from table where key=@passedkey)
begin
--bailout gracefully returning fail
else
--do insert returning success
end

then handle the return in the page. I dont know your data so I can't
say if you can distinguish between a legitmate but duplicated row and
an F5 duplicate. If you can make the distinction then It won't stop the
F5 but at least you are minimising the datbase hit to only those users
that do F5 not every time you go to the summary.
You can keep all the data from the panel a to display summary in panel
b
Even if you are letting the database create the primary key you could
test against each field prior to the insert.
 
J

Jim Little

Hi Scott:

That is an interesting idea, but it doesn't solve the postback issue. I can
already determine if the user inserted the data into the database--the
unique constraint tells me that. What I can't do is tell whether the user
his the refresh button. In your scenario, the refresh button would cause a
new Guid to be generated.

Thanks,
-- Jim
 
J

Jim Little

Hi Shaun:

I know there is not a way to trap a refresh request. That is the basic
problem. Sure, there are ways if you could install an ActiveX, monitor the
active window, and under certain conditions you could prevent <F5> from
being processed, but that is obviously a total non-viable approach.

The nature of the data isn't important here because it has a unique
constraint on it so I do get an error event when a duplicate row is
attempted. To understand why I don't have any options to trap F5 (refresh):

Consider a form that accepts a social security number (SSN) and name and
address. In this case, assume the SSN is the unique key.

If a user enters 123-45-6789 as the SSN on the first visit to the form, hits
submit, gets to Panel B, then hits refresh, the form "thinks" it is being
hit for the first time. It then tries to insert 123-45-6789 again and raise
a unique-constraint error. Sadly, this is exactly what happens when ANOTHER
user visits, gets to the form, and enters 123-45-6789 during the first
postback (when the submit the form). I have no way to determine the first
user (the one who hit refresh and who should be directed to Panel B) and the
second user (who entered duplicate data). The second user should absolutely
see a "SSN already exists" error message, the the second user really
shouldn't--especially if they hit refresh because there was some sort of
client-side rendering issue or connection problem. They should instead see
Panel B.

Thanks,
-- Jim
 
S

shaun duke

Jim,

Can you create a timestamp in a hidden field during load if
(!this.IsPostBack).

Then you can test the timestamp and SSN prior to the insert. That way
you will be able to distinguish between a refresh duplicate and a new
user duplicate and handle accordingly.

Shaun
 
J

Jim Little

A timestamp doesn't do anything to solve the problem. A person in Paris
could enter the same SSN within the same timeframe as the user in the U.S.
 
S

shaun duke

Jim,

I think you are taking things to the extreme with that argument. Don't
get me wrong, I agree absolutely with your statements but I think you
now have to look at this problem with a more practical rather than
idealistic view point.
Given the fact that by your own admission there is no way to test for
the refresh, you need a workaround. That can only come from generating
an additional level of uniqueness in your processing of the data. Look
at probabilities of the scenario you suggest. What realistic chance is
there of these two people entering exactly the same SSN at exactly the
same time. If you can't generate a timestamp with sufficiently high
enough resoultion to distinguish between these events, then you are
going to need to look at some way of compounding additional uniquess
into the key.

Either that or you are into a situation which you have already
dismissed of pulling the summary data back for display in page 2 and
that isn't going to help solve your Paris/ US problem.


I hope you find a way of solving this for yourself.


Regards
Shaun
 
J

Jim Little

Shaun,

My example that used SSN was intended to give you a sense of the problem,
not to describe the actual problem, which is far more complex. The actual
form allows users to enter their own user name. The user name is the primary
key. Most people will use their first name. It is entirely possible that two
people can have the same user name within a given timeframe.

I don't understand your timestamp solution anyway. Sessions are good for 20
minutes, and some people take longer to complete a form than other people.
Also, some people may wait minutes before hitting the refresh button. Your
idea of a timestamp is not only not a solution, it doesn't even make sense.

Good programmers, Shaun, do take things to the "extreme". Didn't you ever
hear "If something can go wrong it will?" Good programmers fix known
problems. The issue I'm trying to solve came up three times during our beta
testing. So I am really not so sure how "extreme" it is.

The issue is also academic.

Trying to come up with Rube Goldberg-type solutions is not what I was
looking for. Don't be upset at me just because you couldn't offer an
effective solution.

Regards,
-- Jim
 
B

Bruce Barker

you are missing the point. web sites are stateless.

the refesh request looks just like the original. if it is important to
detect the refesh, then your code needs to remember that that particular
request (or postback) has already happened once. the most common appoach is
a to use a new transaction guid with every page request. when the user
refreshes (reposts), you server code needs to notice that it has already
processed this transaction request and can reply with the same results of as
the original request.

note: this is a standard design pattern for many network based systems,
queuing in particular.

-- bruce (sqlwork.com)
 
J

Jim Little

OK--your explanation made it clearer to me...

Let me recap for the benefit of others and to ensure I've got it straight:

When the web form is first accessed (this.IsPostBack==false), set a GUID in
some storage mechanism, such as a hidden field or the ViewState. This would
not require a session variable. Let's say the GUID is 1 (what are the
chances of that?). When all data is verified, and has been successfully
written to the database, I should write a "1" to a data table that tracks
guids for this particular web form (I supposed it's best to do that in the
stored procedure that does the insert).

After that point, I should never ever ever see another postback hit the
server from that particular web form that has a GUID value of 1. I can
inspect the GUID value in the web form's load event and check whether it's
in the data table. If I determine that the GUID was used, the only
explanation is that the user hit "refresh" (<F5> in IE) after the data was
already successfully submitted.

I actually LIKE this approach!

The only question is when the best time would be to delete the GUIDs. To
determine that, they would have to be date/time-stamped, and I suppose
anything older than 24 hours could be safely deleted so that the table
doesn't grow uncessarily large.

This works--and it does not require session variables.

Thank you for clarifying!

Regards,
-- Jim
 
S

shaun duke

Jim,

I'm not upset with you for not taking the time to figure my solution
through. You have quite obviously missed the point entirely with
regards to timeframe, there is none and it doesn't relate to session
timeouts or vairables it releates to an absolute point in time. I find
it interesting that you have made the connection with the relevance
date time stamping the guid.

Perhaps a good programmer , Jim, would have taken the time to explain
the problem more fully. I'm not surprised that your problem came up
three times in beta if the sum total of uniqueness in your row was a
username.


I'm happy you have founf a solution that works for you.

Shaun
 
J

Jim Little

Shaun,

I was being dumb and I'm sorry. I did not understand the point about your
time stamp. You didn't mention anything about it other than it goes in a
hidden field and that I should later "test it". I didn't know what that
meant. Yes, a time-stamp is a sort of GUID, and I certainly am not worried
about a user in France and a user in the U.S. entering data in the exact
millisecond of time. I suppose GUID made sense to me because it is truly
globally unique, whereas time-stamps are not necessarily so. Still, until
Bruce set me straight with queuing and a known design pattern, then the
light went on.

I did miss your point. I don't know why. Maybe I was just too caught-up in
your suggestion of using the ViewState, which had proved not useful to me in
the past when trying to solve this problem. The key (which Bruce also did
not explicitly state) is not just getting some sort of reasonably unique
transaction id (GUID is obviously the quintessential choice) , but
*persisting that in non-volatile storage so that it can be easily recalled
independent of session timeouts or web-client activity*, and later
determining whether a postback contains the persisted transaction id or not.

Did you ever see that optical illusion where there is a beautiful woman and
an old hag in the same drawing?
http://www.coolopticalillusions.com/optical_illusions_images_2/young_woman2.htm
Most people see the young woman from the back at first. It's as if you told
me there was an old woman in the image (which there *is*), but I just could
not see it--and even went as far to suggest that you are wrong. Then Bruce
said "Hey! The woman's ear is the old woman's eye! And the old woman is
looking down and to the left, with a nose that's the young woman's
cheek/chin, the necklace is the old woman's mouth!" Once he said those
things, the old woman appeared. That "aha!" experience is what I had when I
read Bruce's message. Please don't take it personally.

Was I being stupid? Maybe... Tired and fatigued is a better explanation.

It is likely too late now, but please accept my apology and thanks for
helping as much as you did. I now see your point clearly about the
timestamp, and it's something I'll always remember as a useful strategy for
this sort of problem. I'll be more patient and make more of an effort to
understand someone's post next time--before I respond.

I do truly appreciate the time and help you offered.

Regards,
-- Jim
 
G

Guest

Dear Jim

I am just developing a new site (and am new to ASP.NET!) and am about to
make the decision about using session variables (which would certainly
simplify the coding). Why are you avoiding them for a web farm. From what i
have read ASP.NET provides a mechanism for using session variables via a
database table (and are supposed to work on a web farm) - which is what you
are implementing manually.

Yours in curiosity
Steve Booth
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top