Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations Rhinorhino on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

ASP User Navigation issues. 1

Status
Not open for further replies.

bluecjh

Programmer
Joined
Mar 12, 2003
Messages
385
As a relatively new asp developer it strikes me that a lot of the core issues vis a vis asp aplication development are about the control (or lack of control) of the user and the ‘flow’ thorough the application.

I have been trying to successfully gain some control by looking at the ‘disabling the back button’ issue, I came to the conclusion that the easiest and cleanest method was:

<script>
history.forward();
</script>

(assuming the browser accepts this code) but by navigating through the 'history' I can get round this fix. I am not happy with this ‘cheat’ anyway because I would prefer a ‘server side’ solution (which by the way also does not involve the use of session variables).

How do you pros generally approach the issue of users repeatedly hitting the back or forward buttons or retrieving pages from the ‘history’ and thus wrecking the application logic?, for example say in on-line banking if I transferred money from one account to another I assume that the application wouldn’t let me duplicate a transaction just by clicking back and resubmitting the page.

I’m sorry for the length of this but the solutions I read all seem a bit ‘cheat’ – like and fallible. In broad terms what is the way to go?

Chris
 
How about storing the user's choices in a database or in session variables as they go through the process. Then store the location in the process they have got to and write an include file that is included at the start of each relevant asp page that checks which page the user should be on and redirects them appropriately.

That way the user can hit any page in the process and be redirected to where ever they are supposed to be.
 
Neil,
hmmm that seems to be the 'broad term' though
session variables are not available to me. But the database
approach I will ponder the detail for a bit...
Chris
 
OK Neil, Assuming I have saved to the Db the journey of pages a visitor has taken.

Supposing user is at page A.
An attempt to go to page B will be redirected because I won’t allow it (using your suggestion)
Say user legitimately navigates to C and C has a legitimate link to B
Then User selects page B from ‘history’ (originally barred)
This will be allowed because the code won’t know that this is an old copy of B.

He has gotten through my attempt to control the user and has succeeded in going back to a historical version of ‘B’

What am I missing?
 
If the application can't support the resubmission of the same form due to it's nature (your banking app is a great example), you'd want to do a couple of things.

First, you'd definitely want to put no-cache ad expires headers in the <HEAD> sections of your form pages.
Code:
<META HTTP-EQUIV="Pragma" CONTENT="no-cache">
<META HTTP-EQUIV="Expires" CONTENT="-1">
When the user hits the back button there simply is no page to go to, and he'll get that message (a "warning" in IE) that the data needs to be resubmitted. This eliminates all of the "history" problems, etc., because the pages just don't exist for the user any more.

Then you might want some kind of transaction monitoring, to keep resubmits from doing anything (just thought of this, so maybe there's a more clever way to do it, but this should at least spark something).

When the user begins his overall transaction, assign a unique identifier to the transaction (somethng involving the datetime, maybe a random bit, maybe the IP address, etc.). Put this transaction "ID" on every form page as a hidden variable. Also include on each page a variable that tells you what "step" the user is at. You'll track the transaction's current progress via the transaction ID and the "step" in the database. After submission check the transaction and the step: if this transaction and step combination has already been submitted then you know that the user has already completed this step, and odds are you'll want to void the transaction and start the user over again at step 1 (or alternately just move them on to the next step page anyway).

You'd want to do more logic tracking than that on the server side to prevent fraud, of course, so that a hacker couldn't submit fake post data with an advanced step or what have you, but this should at least get you moving in the right direction. And it doesn't require true sessions.
 
Genimuse is spot on, simply make certain all of your code is expired at point of delivery to the browser and the client will always have to query the server for a new version, thus handing process flow control to you.
 
Genimuse,

I'm lost,
I have tried the no-cache approach, not to keen on the “warning…” page
that’s delivered because the user is left in an ambiguous position and they probably won’t
know what to do next, I’ve also effectively ‘lost control’ of the user.
Also if they refresh the page they still get that page albeit the ‘form’ is empty and I don't
want them on this form at all. But most importantly I can still see the offending page still in the ‘history’ (?)


I don't see how I've got hold of the process flow at that stage, unless i'm missing something.

But as regards your further suggestion when you speak of a 'unique identifier to the transaction'
what do you mean by a transaction: do you mean a) a different id for each page
or b) one id unique to the session (conceptually as I can't use sessions)

I can't see how the latter would work.
 
On no-cache and control: you only have so much control. You're right, in a web environment users can do stupid things and make mistakes. However, if you have no-cache (and expires) on, you can keep them from submitting the exact same form, or even having it displayed if you follow what I referred to on transactions.

You say that you can't use sessions: by that I assume you mean that you can't use ASP sessions, right? You don't mean that you can't do anything to track a user's progress, just that -- due to cookies, or server farm restrictions, or something -- you can't use the ASP session object. If it's something else, please explain.

What I mean by a transaction is this. Imagine that you have a situation with a three-page form -- 1, 2, and 3 -- that a user can complete, and that you don't want the user to go back and resubmit something different because it will screw stuff up. When you receive a request for page A (the first form), you create a number or string (random number plus the datetime should be enough, add more like their IP address if desired) that will be this "transaction's" unique identifier. As part of serving page A you store in the database (in, say, a "transaction" table) the unique ID and the current step, in this case "1". In the form itself you embed two hidden variables (inputs): the unique ID and the step. The page is no-cached and expired.

The user fills out the form and submits it. The first thing you do is look in your "transaction" table is lookup the transaction that matches this unique ID. Compare the stored step (in this case "1") to the step submitted with the form (in this case "1"). If the submitted step is less than the stored step then you know that the user has used the back button and resubmitted the same form, but if it's equal to the current stored step then you know things are ok.

Process the page 1 form data. Now you're ready to serve page 2 of the form. Update your transaction table, setting the step number for this unique ID to 2, and serve the page, again with hidden input variables, one with the unique ID and one with the step, in this case "2". Again the user submits, again you check to make certain that the submitted step number is equal to the stored number, and if so then you process the data.

Now serve page 3, again updating the transaction table for this unique ID (still carried through with hidden form fields), and again handling the data. When you process this page you either update the transaction table to set the step to "4", or alternately you just delete the transaction row.

What to do if the submitted step is less than the stored step: here you've got a decision to make, and your actions depend on the application. If all the application is doing for each page is storing some info then you can simply display the latest unsubmitted page (like if they're resubmitting page 2 you can just serve page 3 again). If each page is doing something you can't fix (like transferring money or something) then you probably need to completely cancel the "transaction," reverting things to their previous state, and re-displaying the page 1 form (with a new unique ID) with a warning that the user shouldn't use the back button and needs to start again.

Does this make sense?
 
Yes I mean ASP sessions as I’m working in a thin client environment and the default setting is that cookies are switched off.

I think I understand the logic,

but,

The ‘no-cache’ approach seems very unreliable most times I can actually hit ‘back’ and the original page appears, why is this?

<%
response.Buffer=true
'response.expires = 0
'response.CacheControl = "no-cache"
session.lcid=2057
%>

<html>
<body>
<!-- #INCLUDE FILE="connection.asp" -->
<!-- #INCLUDE FILE="RetrieveId.asp" -->
<!-- #INCLUDE FILE="NavigationControl.asp" -->
<head>
<META HTTP-EQUIV="Pragma" CONTENT="no-cache">
<META HTTP-EQUIV="Expires" CONTENT="-1">


This method really only stops submissions from ‘old’ pages and doesn’t restrict stop the ‘user’ from navigating to ‘old’ pages?

Is there a reliable way to control the navigation and not just resubmission?



 
Genimuse,

If I understand you right, in the process of serving page ‘A’ I am supposed to generate the unique ID and ‘Step’ (hide these in the form and save both to the Db in the process of serving A)

However, if later the user hits ‘back’ and refreshes to get ‘A’ I get another unique ID. i.e. I no longer have the original ID to compare with tblTransactions.

Because I don’t find this in tblTransactions the re - submission of A is not rejected.

Obviously I’m missing something?
 
IE doesn't always handle no-caches correctly... its system is that if there's less than 75k in stuff then it won't wipe the old thing. I've never had a problem once expires was added, though. Still, the trick that Microsoft recommends is to do this clunky thing:
Code:
<html>
    <head>
        <title>My page title</title>
        <META HTTP-EQUIV="Pragma" CONTENT="no-cache">
        <META HTTP-EQUIV="Expires" CONTENT="-1">
    </head>
</html>

<html>
    <head>
        <title>My page title</title>
        <META HTTP-EQUIV="Pragma" CONTENT="no-cache">
        <META HTTP-EQUIV="Expires" CONTENT="-1">
    </head>
    <body>
        Your actual page goes here
    </body>
</html>
If your total page size is 75k+, though, you shouldn't need to do this.

On the transaction ID, no it doesn't work like that. With no-cache and expires working, when the user navigates back to the previous page the page won't appear, instead he'll get the warning. If he resubmits via the link there then you'll get the same thing he submitted a minute ago, which will indeed include your old transaction ID and step. Refreshing A won't give him a unique ID, it will just submit the old one.

If he re-navigates to the page (not refreshing) then you'll just generate a new ID, no biggie, he's starting over.

That is, we don't care how many IDs he generates on page 1. It's when he gets to page 2 and then goes back to page 1 and re-posts that we care.
 
Ok ‘No-Cache’ is working, albeit patchily sometimes it doesn’t yield the warning page and just skips back to ‘home’.

Anyway, my problem is:

“If he resubmits via the link there then you'll get the same thing he submitted a minute ago, which will indeed include your old transaction ID and step. Refreshing A won't give him a unique ID, it will just submit the old one.”

Yes if he goes back he gets the warning message(hopefully) and has the choice to refresh.
But refresh always yields a new ID. After all the code is in this page ‘A’ (include file):

‘get the last ID from the previous page and search for it in Db
Set rNavigation = cnnMB.execute ("SELECT * FROM tblNavigationControl WHERE transId '" & request.form("hiddenfield1") & "'")
If not rNavigation.eof then
Generate a new id…

Well rNavigation will be empty because the page is refreshed and is source of the Id, it is the start of the chain as it were and originally called by a login form which has no hidden
Fields or ID’s to request

Where am I going wrong?
 
It's not an actual refresh though, right? The previous page is being resubmitted with the ID already in it. If it's the very first page, why do you care if they get a new ID?

I think we're cross-talking here somehow. It's only on page 2 plus that we want to ensure that the steps are being done out of order.

At least that's how I understand it. It could also be that I just don't understand the problem. :-)
 
Well yes it is a refresh, let me recap

Login.asp is submitted which calls ?
Form1.asp – While serving Form1.asp get ‘Id’ & ‘Step’ and hold in form and store both in Database.
Submit Form1
Serve Next.asp – while doing so request ‘Id’ and ‘Step’ from Form1, Look for this ‘Id’ in Db if found check that ‘Step’ in Db and Form1 match (these
Should both be ‘Step’ 1)
If ok ‘update’ this same record with Step = 2.

Problem now occurs:

User hits ‘back’
Receives ‘warning’ (due to ‘no-cache’)
Decides to hit ‘Refresh’
Form1.asp is refreshed and new ID created as this always happens as Form1.asp is served.
User enters new data into Form1 and hits ‘submit’

I want this submission to be rejected (I’ll probably redirect them)
But it isn’t rejected because Next.asp can’t find this ‘new’ Id in the database
And so form is accepted.

Unless I have misunderstood your concept the only solution I can think of is to create a
Hidden form in Login.asp and do the ‘Id generating’ in Login.asp so when Form1 is called or refreshed (in this case) It will be able to ‘Request’ the Id.

Hows that?
 
You're right. And yeah, either a hidden form or a "hidden" ID that's generated at login and carried through in the querystring (which will be appropriately sent on refresh).
 
I'll try to implement it but this
no-cache is now bogging me down it's totally
unpredictable.
 
Here's another idea I just thought of. Not sure how well it will work, but it's worth a thought or brief experiment.

If no URL changes on the browser side, does the browser actually provide a "history" to the previous identical-URLed page?

If not, another option would be to use the Server.Transfer method to effectively move to a new page on the server without it appearing to be a new page on the browser... that is, it would keep replacing the current "form.asp" with the same page name but different information.

I dunno, though... I submit forms to themselves all the time and I'm pretty sure back still works. Still, might be worth a quick check.
 
I think the principle of it is there
I can log the steps
I just have to make sure I can keep a tag on who
it is who is making them.
No-cache has just stopped working, I tried your microsoft
suggestion but it's still unpredictable and it has to be
predictable.
I will hunt the web to find out why no-cache fails.
 
Problem seems to be not that it's failing
to no-cache but that it's auto refreshing
somtimes, so I don't get the warning how can I stop
this auto refresh?
 
I don't know, I've not seen that problem, sorry. Bummer.
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top