PDA

View Full Version : success messages - redirect after post



Keith Donald
Nov 4th, 2004, 12:10 PM
In Matt Raible's "comparing web frameworks presentation", he notes:


Struts is the only framework that allows success messages to live through a redirect

In Spring, can't you just add "messages" as bound parameters in a model map and return a populated RedirectView descriptor? What am I missing? What should be enhanced?

katentim
Nov 4th, 2004, 03:28 PM
Well, if it's what I think it is, I do something like:

Map model = new HashMap();
model.put("employeeId", employee.getEmployeeId());
model.put("saveSuccessful", new Boolean(true));
My browser then has the URL:

?employeeId=0&saveSuccessful=true
And I use the saveSuccessful=true to then display the (i18n) success message.

rcarver
Nov 6th, 2004, 02:06 AM
I just whipped up something to help with this...

for example, in onSubmit(..):


model.put(SessionMessage.RESULT, SessionMessage.create(request, "user.userEmail.edit.success"));


which puts a key in the model with a unique id referencing this message, so you end up with a redirect url like "?result=234".

That key is stored in the HttpSession until a HandlerInterceptor automatically pulls out that message and adds it to the model:



public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
if (okToPopulate(modelAndView, request)) {
Map model = modelAndView.getModel();
model.put(RESULT_MESSAGE_KEY, SessionMessage.retrieveMessage(request, SessionMessage.RESULT));
model.put(ERROR_MESSAGE_KEY, SessionMessage.retrieveMessage(request, SessionMessage.ERROR));
}
}


So I end up with the variable ${resultMessage} on every jsp page. retrieveMessage() uses the stored code to look up the actual text with a MessageResolver. It also removes the value from the HttpSession since this type of message should only be shown once.

I just have a jsp include file that prints ${resultMessage} if it's set. Nice and easy, and keeps the url clean.

dwsmith75
Nov 6th, 2004, 02:10 PM
its a simple as:

return new ModelAndView(new RedirectView(getSuccessView())).addObject(Constant s.STATUS_MESSAGE,"updated");

kantorn
Nov 7th, 2004, 12:51 AM
So I end up with the variable ${resultMessage} on every jsp page. retrieveMessage() uses the stored code to look up the actual text with a MessageResolver. It also removes the value from the HttpSession since this type of message should only be shown once.

Your approach is very interesting. Could you post the code for the SessionMessage class? And if you could post the way you configure the use of the postHandler()-method in appropriate .xml?

Very grateful!

infinity2heaven
Oct 3rd, 2007, 10:18 AM
This doesnt seem to work as I expect: -

onSubmit

map=errors.getModel()
// ...some logic
map.put("id", domain.getId());
map.put("statusMessage", "save");
return new ModelAndView(new RedirectView("some.action"), map)


formBackingObject



String id = request.getParameter("id");
if (StringUtils.isNotEmpty(id))
{
return loadExistingCommand(id);
}
else {
return createDefaultCommand();
}

It does redirect with the command object (based on the request param id). However the statusMessage is always empty.

When, I do this, instead
onSubmit


map.put("id", agreement.getId());
map.put("statusMessage", "save");
return showForm(request, errors, getSuccessView(), map);


it works for the first return, but later on some of my values on the screen don't show up (like listoptions etc from the command object)

jglynn
Oct 3rd, 2007, 05:24 PM
I personally like to use properties for my messages so I typically to something like this (really basic example):



Controller:
model = new HashMap();
model.put(UIConstants.MODEL_MESSAGES,"message.saved");
return new ModelAndView("fooRedirect", model);

Messages.properties:
message.saved=Your record was saved

Redirected View's Controller:
String msgCode = request.getParameter(UIConstants.MODEL_MESSAGES);

if (msgCode != null) {
try {

String message = getMessageSourceAccessor().getMessage(msgCode);
model.put(UIConstants.MODEL_MESSAGES,message);

} catch (NoSuchMessageException e){
//log that no message found for this code
}
}

infinity2heaven
Oct 4th, 2007, 09:37 AM
I think you've read my question wrong. I do use message properties ("save" above is a key to the message properties file and I use <fmt> tags). In my case redirectView redirects to the same controller. So the 'get' semantics are called. ie. formBackingObject, initbinder ... return ModelAndView(). The only way I can think of writing this logic is formBackingObject but it has only parameter - httpRequest

jglynn
Oct 4th, 2007, 04:34 PM
I think you've read my question wrong.

What question did you pose?
I don't seem to see one.

infinity2heaven
Oct 8th, 2007, 04:54 PM
I got this working, would be interested to know if this is an elegant solution.
From the code I'd given in the previous replies, instead of writing the code map.put("statusMessage", "save"); in onSubmit, add the same in referenceData() method.

It would be interesting to understand the flow with a RedirectView:-
1. User clicks on something, form submits
2. onSubmit, process/save and return a RedirectView with a map of id and success message.
3. Now, the redirect could be to the same controller or a new controller; whatever that is, it is a http get call and not a post; hence, the order of sequence is formBackingObject, initBinder, referenceData before returning ModelAndView. In formBackingObject, id would be checked, reloading the object again. But where do we get the statusMessage?
4. the only other 'hook' which has an errors object is referenceData. So you add the code here

Now, whatever you've added in the 'map' before returning RedirectView would be available with the new request


String status = map.put("statusMessage", request.getParameter("statusMessage"));

F.Degenaar
Oct 9th, 2007, 10:42 AM
The following interceptor does the trick for us. It determines, if to redirect or not by looking at the view name. If so, it saves the model and the view name without the prefix into the HttpSession. Then it constructs a redirect url with the id of the saved model.
The same interceptor is used to reconstruct the model by looking at the request parameters.
The only caveat is, that lazy-loading doesn't work for the saved model, because a new request has started and the hibernate session the objects in the model point to is already closed.
The only even more transparent way I can imagine is to use a ServletFilter that saves the generated response to the session and outputs a redirect instead. After the redirect has happend, the previously saved response is sent to the browser. That should solve all Spring MVC problems with redirect-after post.

The code

import java.rmi.server.UID;
import java.util.Date;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.ModelAndViewDefini ngException;
import org.springframework.web.servlet.handler.HandlerInt erceptorAdapter;
import org.springframework.web.servlet.view.RedirectView;

public class RedirectInterceptor extends HandlerInterceptorAdapter
{
private String viewPrefix;
private String sessionAttributePrefix;
private String requestParameterName;

private static class RedirectInfo
{
long timestamp;
ModelAndView modelAndView;
}

public boolean preHandle(
HttpServletRequest request,
HttpServletResponse response,
Object handler)
throws Exception
{
// Are we coming from a redirect?
if ("GET".equals(request.getMethod()) && request.getParameter(requestParameterName)!= null)
{
// reconstruct name of the RedirectInfo-Object
String attributeName=sessionAttributePrefix+request.getPa rameter(requestParameterName);
HttpSession session=request.getSession(false);
// go to the originally wanted view
RedirectInfo info=null;
if (session != null && session.getAttribute(attributeName) != null)
{
info = (RedirectInfo) session.getAttribute(attributeName);
session.removeAttribute(attributeName);
throw new ModelAndViewDefiningException(info.modelAndView);
}
}
return true;
}

public void postHandle(
HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView modelAndView)
throws Exception
{
// Do we want to redirect?
String viewName=getActualViewName(request, modelAndView);
if (viewName != null)
{
// remember the model and the view and put it into the session
ModelAndView sessionMAV=new ModelAndView(viewName, modelAndView.getModel());
String id=getNewId();
String attributeName=sessionAttributePrefix+id;
RedirectInfo info=new RedirectInfo();
info.timestamp=new Date().getTime();
info.modelAndView=sessionMAV;
HttpSession session=request.getSession();
session.setAttribute(attributeName, info);
// modify model and view in order to let a redirect happen
modelAndView.clear();
RedirectView rv= new RedirectView(getOwnUrl(request));
modelAndView.setView(rv);
modelAndView.getModel().put(requestParameterName,i d);
}
}

// Returns null, if no redirect should happen, otherwise the view name without prefix
private String getActualViewName(HttpServletRequest request, ModelAndView modelAndView)
{
String viewName=null;
// POST and prefix trigger a redirect
if ("POST".equals(request.getMethod()))
{
if (modelAndView != null &&
modelAndView.getViewName() != null &&
modelAndView.getViewName().startsWith(viewPrefix))
{
viewName=modelAndView.getViewName();
viewName=viewName.substring(viewPrefix.length());
}
}
return viewName;
}

private String getNewId()
{
return new UID().toString();
}

private String getOwnUrl(HttpServletRequest request)
{
String url=request.getServletPath();
if(request.getPathInfo() != null)
{
url+=request.getPathInfo();
}
url=url.substring(url.lastIndexOf('/')+1);
return url;
}

public void setRequestParameterName(String string)
{
requestParameterName = string;
}

public void setSessionAttributePrefix(String string)
{
sessionAttributePrefix = string;
}

public void setViewPrefix(String string)
{
viewPrefix = string;
}

}

How to configure it:

<bean id="redirectInterceptor" class="RedirectInterceptor">
<property name="viewPrefix" value="RAP:"/>
<property name="sessionAttributePrefix" value="RAP"/>
<property name="requestParameterName" value="RAP"/>
</bean>

How to use it in a controller:

<property name="successView" value="RAP:yourview"/>


HTH
Fokko

iHeartUnicorns
Dec 3rd, 2009, 05:26 PM
Anyone figured out how to do this in RC2?

I've tried using an instance of RedirectView and setting setExposeModelAttributes to false and redirectView.addStaticAttribute("success", "success");

I don't understand the API because this does not work and it the model just get's cleared when it hits the next handler method.

is the only way to do this really to use an interceptor? really?

mschipperheyn
Jan 31st, 2010, 06:02 AM
Hi,

Does anyone have a generic Spring 3.0 solution that doesn't involve the session? I like to keep the session out of this for performance reasons.

Kind regards,

Marc