tags:

views:

508

answers:

1

Say we have an "add new object" form in AddObject.jspx, and a Confirm.jsp page (saying something like object with id NN was added to the database...)

in faces-config.xml:

<navigation-rule>
    <from-view-id>/AddObject.jsp</from-view-id>
    <navigation-case>
        <from-outcome>add</from-outcome>
        <to-view-id>/Confirm.jsp</to-view-id>
    </navigation-case>
</navigation-rule>

So the user opens AddObject.jsp, fills out the form, and clicks the 'add' button that triggers an event handler in the AddObject's backing bean. The backing bean inserts the new object into the database and returns the "add" outcome. So we end up in the Confirm screen.

Now if I hit F5 (refresh the browser) a new object will be created and the Confirm screen redisplayed with the new ID of the new object. I can keep hitting F5 and get as many new objects as I'd like.

I don't want this. I want to detect such situations. Avoid adding the same object many times over (generating many rows in the database that differ only by ID).

Can I do this with JSF? Why does it ask me for from-view-id/outcome/to-view-id if it doesn't seem to make any good use of this information?

+1  A: 

There are two parts to this problem.

First, you need to avoid having the broswer's refresh of the confirm page resubmit the request. So one approach if for the brower's history not to say

 POST this request

and instead say

 GET this confirmation page

We cam do that by using the

 <redirect/>

statement in the JSF navigation. Now this means that a redirect response is sent to the Browser. The browser then ask JSF for the conformation page. NOTE: this is now a new request from JSF's perspective, so any data you want to display needs to be at session scope not request scope.

This "Use a redirect for actions that make changes" approach is quite a common idiom. However it doesn't address the "Quick Fire User" problem. What would happen if the user hit the "Submit" twice very rapidly? You can get to a situation where the browser has sent two requests. There are other scenarios where this can happen - network glitch on the first request, user gets impatient, sends the request again.

So we need somehow to handle such double submissions. This is not a JSF-specific problem. A typical solution is for the backend to send a token when the original request page is populated. This is held in a hidden field. When the request is submitted that token (some unique number) is sent back to the application which records that it has been used, hence a double submission is detected.

You can also use JavaScript in the browser to prevent double submission. This should give a friendlier user experience but I would not recommend relying only on Browser-side control. The server should have ultimate respsonbility.

djna
Thanks for your response.But the "store everything needed by the confirmation screen, then do a redirect to the confirmation screen" workaround is a bit of a hack. Even so, I think I'm going to use it, as I can't think of a better alternative.(btw I would like to keep the session as lean as possible...)It's disappointing how JSF has all the info needed to gracefully solve the problem, but is just too dumb to use that info :-(
prokoba
I don't agree it's a hack. Where would you have it store the stuff? The important thig is that you need to tell the browser to adjust it's view of history - so we **must** tell the browser to redirect. hence the commit screen data must be stashed somewhere keyed by session id, that's what a session is for. This is not a solution specific to JSF.
djna