Post-Redirect-Get for the Force.com Developer

September 16, 2010 Appirio

by Matthew Page

There probably isn’t a web developer that hasn’t heard this, “When I hit the back button, I get a WebPage Expired message”.

Let’s see if we can recreate this with a simple VisualForce page and controller example:

Problem:










public with sharing class DemoController {
public String message{ get; set; }
public PageReference post(){
/* do something here */
return null;
}
}

This creates a page that looks something like this:

When you type a message and click the “Save” button it executes the post action in the DemoController. This controller doesn’t do much at all, but typically you might update a record, do an upsert, etc. If we do this a couple of times and then hit the back button we will be greeted with a message that looks like one of these:

Repost form data message in IE8, Safari, Chrome, and Firefox:



Now really IE8 is the only one that incorrectly states that the WebPage has expired. Safari, Chrome and Firefox get it right and warn us about resubmitting (POSTing) our form data again. If we fire up Fiddler we can watch the HTTP(S) traffic that is generated.

When we click the “Save” button we can see the following Request and Response messages:
(Note: I edited the traffic messages below to make them more readable and remove unnecessary cruft.)

Request

POST
https://c.na7.visual.force.com/apex/DemoPage HTTP/1.1
Host: c.na7.visual.force.com
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 115
Connection: keep-alive
Referer: https://c.na7.visual.force.com/apex/DemoPage
Cookie: sid=00DA0000000akOY!ARkAQPm5hbsddMNqU2kEA4MXGelClG3Z6I9x3VEkWP4rr.5Erw92QVopCcwCVRepx_JDnaeOxfGDpR9XquO.__RaszYIO2Jk; sid_Client=0000000Kg7b0000000akOY; clientSrc=67.189.32.220; inst=APPA
Content-Type: application/x-www-form-urlencoded
Content-Length: 4829
...

Response

HTTP/1.1 200 OK
Server:
Cache-Control: no-cache, must-revalidate, max-age=0, no-store, private
X-Powered-By: Salesforce.com ApexPages
P3P: CP="CUR OTR STA"
Pragma: no-cache
Expires: Fri, 03 Sep 2010 23:19:07 GMT
Content-Type: text/html; charset=UTF-8
Content-Length: 29302
Date: Fri, 03 Sep 2010 23:19:07 GMT



...

Post-Redirect-Get Demo


Test






...

Notice that we made a single request (POST) and got back a web page in the response. If at this point we hit the back button, we will be asking the browser to re-POST the form data. Our web browser will respond to this request with one of the previous warnings.

Solution:

So how do we deal with this problem? One approach is to use the Post-Redirect-Get pattern. Post-Redirect-Get (PRG) is a web development pattern that attempts to solve the problem with a bit of indirection.

I think this article boils it down nicely:

“PRG pattern splits one request into two. Instead of returning a result page immediately in response to a POST request, server responds with redirect to result page. Browser loads the result page as if it were an separate resource. After all, there are two different tasks to be done. First is to POST input data to the server. Second is to GET output to the client.”

So let’s modify our controller from the previous example to implement the PRG pattern:


public with sharing class DemoController {
public String message{ get; set; }
public PageReference post(){
/* do something */
PageReference p = new PageReference('/apex/DemoPage');
p.setRedirect(true);
return p;
}
}

Notice that our new post method is calling setRedirect(true) on the PageReference.

The SalesForce Visualforce Developers guide give us this handy little tip:

You can use the setRedirect attribute on a pageReference to control whether a postback or get request is executed. If setRedirect is set to true, a get request is executed. Setting it to false does not ignore the restriction that a postback request will be executed if and only if the target uses the same controller and a proper subset of extensions. If setRedirect is set to false, and the target does not meet those requirements, a get request will be made.

So setting setRedirect to true returns a PageReference that will perform a GET request. This seems to fit the PRG pattern. We make a POST request that redirects to a GET.

Let’s save our code, retest it, and see what Fiddler has to say:

Request:

POST
https://c.na7.visual.force.com/apex/DemoPage HTTP/1.1
Host: c.na7.visual.force.com
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 115
Connection: keep-alive
Referer: https://c.na7.visual.force.com/apex/DemoPage?save_new=1&sfdc.override=1
Cookie: sid=00DA0000000akOY!ARkAQPm5hbsddMNqU2kEA4MXGelClG3Z6I9x3VEkWP4rr.5Erw92QVopCcwCVRepx_JDnaeOxfGDpR9XquO.__RaszYIO2Jk; sid_Client=0000000Kg7b0000000akOY; clientSrc=67.189.32.220; inst=APPA
Content-Type: application/x-www-form-urlencoded
Content-Length: 4969
...

Response:

HTTP/1.1 200 OK
Server:
Cache-Control: private
X-Powered-By: Salesforce.com ApexPages
P3P: CP="CUR OTR STA"
Content-Type: text/html; charset=UTF-8
Content-Length: 401
Date: Fri, 03 Sep 2010 23:30:42 GMT








Side Note:

What is interesting here is that SF redirects us with javascript. If you read the setRedirect documentation for the PageReference object, it says that “If set to true, a redirect is performed through a client side redirect.” This Wikipedia article discusses URL redirection in all of its forms.

Request:

GET
https://c.na7.visual.force.com/apex/DemoPage HTTP/1.1
Host: c.na7.visual.force.com
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 115
Connection: keep-alive
Referer: https://c.na7.visual.force.com/apex/DemoPage
Cookie: sid=00DA0000000akOY!ARkAQPm5hbsddMNqU2kEA4MXGelClG3Z6I9x3VEkWP4rr.5Erw92QVopCcwCVRepx_JDnaeOxfGDpR9XquO.__RaszYIO2Jk; sid_Client=0000000Kg7b0000000akOY; clientSrc=67.189.32.220; inst=APPA

Response:

HTTP/1.1 200 OK
Server:
Cache-Control: no-cache, must-revalidate, max-age=0, no-store, private
X-Powered-By: Salesforce.com ApexPages
P3P: CP="CUR OTR STA"
Pragma: no-cache
Expires: Fri, 03 Sep 2010 23:30:42 GMT
Content-Type: text/html; charset=UTF-8
Content-Length: 29202
Date: Fri, 03 Sep 2010 23:30:42 GMT



...

Post-Redirect-Get Demo








...

So that was a lot to digest. But essentially setRedirect(true) does solve our problem.

Conclusion:

PRG is not a magic bullet, but I hope that this article gives you some insight and tools for dealing with this type of problem. Happy coding!

For more information:

http://en.wikipedia.org/wiki/Post/Redirect/Get
http://www.theserverside.com/news/1365146/Redirect-After-Post

Previous Article
Upcoming Salesforce.com Winter ’11 Features
Upcoming Salesforce.com Winter ’11 Features

Jeff Douglas The Winter 11 release is just around the corner and I’ve scoured through the release notes and...

Next Article
Force.com Utility Belt – Google Chrome Extension for Salesforce.com
Force.com Utility Belt – Google Chrome Extension for Salesforce.com

Jeff Douglas The Force.com Utility Belt is a Google Chrome Extension that I wrote to make my life easier. W...