Christof Laenzlinger
2006-03-25 10:36:58 UTC
It seems that the default behavior when a bind/validation error
occurs in a Spring MVC controller's is to *forward* to the
view rather than redirect. This winds up giving the user the form page
but with the input url (the value of the form's "action" attribute) in
the browser's address bar. This is extremely poor usability. Am I
missing something here? Is there a simple and straightforward way of
ALWAYS redirecting after post?
I have had similar thought when I first looked at Spring MVC. I amoccurs in a Spring MVC controller's is to *forward* to the
view rather than redirect. This winds up giving the user the form page
but with the input url (the value of the form's "action" attribute) in
the browser's address bar. This is extremely poor usability. Am I
missing something here? Is there a simple and straightforward way of
ALWAYS redirecting after post?
also convinced that a very good strategy to avoid all sorts of
back-button and page-reload problems is to ALWAYS redirect after POST
(On success and also in case of bind/validation errors).
The SimpleFormController does a forward to the form view, so I was
thinking of RedirectFormController that behaves similar to the
SimpleFormController in terms the workflow. But instead of forwarding
to the successView in case of validation errors it does a redirect
back to the view defined in the formRedirect property. Before it does
the redirect, the errors are stored in the session. When the form is
requested again, the errors are removed from the session and stored
again in the request. I have copied the source code of the
RedirectFormController below.
This is just a proposal and I am not sure if this is the best solution
for solving this problem, so please let me know what you think about it.
Christof
------------------------------------------------------------------------------
package example;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.validation.BindException;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.AbstractFormController;
public class RedirectingFormController extends AbstractFormController {
protected static Log log =
LogFactory.getLog(RedirectingFormController.class);
private String formRedirect;
private String formView;
private String successView;
protected ModelAndView showForm(HttpServletRequest request,
HttpServletResponse response, BindException errors)
throws Exception {
return showForm(request, response, errors, null);
}
protected ModelAndView showForm(
HttpServletRequest request, HttpServletResponse response,
BindException errors, Map controlModel)
throws Exception {
log.debug("showForm");
String errorsAttrName = getErrorsSessionAttributeName(request);
BindException sessionErrors =
(BindException) request.getSession().getAttribute(errorsAttrName);
if (sessionErrors != null) {
if (log.isDebugEnabled()) {
log.debug("redirected error request");
}
errors.addAllErrors(sessionErrors);
request.getSession().removeAttribute(errorsAttrName);
}
return showForm(request, errors, getFormView(), controlModel);
}
protected Object formBackingObject(HttpServletRequest request)
throws Exception {
Object sessionForm = request.getSession().getAttribute(
getFormSessionAttributeName(request));
if (sessionForm != null) {
return sessionForm;
}
return super.formBackingObject(request);
}
protected ModelAndView processFormSubmission(HttpServletRequest request,
HttpServletResponse response, Object command, BindException errors)
throws Exception {
if (errors.hasErrors()) {
if (log.isDebugEnabled()) {
log.debug("Data binding errors: " + errors.getErrorCount());
}
return redirectForm(request, response, command, errors);
} else {
log.debug("No errors -> processing submit");
return onSubmit(request, response, command, errors);
}
}
// redirect view --------------------------------------------------------
protected ModelAndView redirectForm(
HttpServletRequest request, HttpServletResponse response,
Object command, BindException errors)
throws Exception {
ModelAndView mv = new ModelAndView(getFormRedirect());
if (isSessionForm()) {
String formAttrName = getFormSessionAttributeName(request);
if (log.isDebugEnabled()) {
log.debug("Setting form session attribute ["
+ formAttrName + "] to: " + errors.getTarget());
}
request.getSession().setAttribute(formAttrName,
errors.getTarget());
}
String errorsAttrName = getErrorsSessionAttributeName(request);
if (log.isDebugEnabled()) {
log.debug("Setting errors session attribute [" +
errorsAttrName + "] to: " + errors);
}
request.getSession().setAttribute(errorsAttrName, errors);
if (log.isDebugEnabled()) {
log.debug("send redirect to [" + getFormRedirect() + "]");
}
return mv;
}
protected String getErrorsSessionAttributeName(
HttpServletRequest request) {
return getErrorsSessionAttributeName();
}
protected String getErrorsSessionAttributeName() {
return getClass().getName() + ".ERRORS." + getCommandName();
}
// on submit chain ------------------------------------------------------
protected ModelAndView onSubmit(
HttpServletRequest request, HttpServletResponse response,
Object command, BindException errors)
throws Exception {
return onSubmit(command, errors);
}
protected ModelAndView onSubmit(Object command, BindException errors)
throws Exception {
ModelAndView mv = onSubmit(command);
if (mv != null) {
// simplest onSubmit version implemented in custom subclass
return mv;
}
else {
// default behavior: render success view
if (getSuccessView() == null) {
throw new ServletException("successView isn't set");
}
return new ModelAndView(getSuccessView(), errors.getModel());
}
}
protected ModelAndView onSubmit(Object command) throws Exception {
doSubmitAction(command);
return null;
}
protected void doSubmitAction(Object command) throws Exception {
}
/**
* @return Returns the formView.
*/
public String getFormView() {
return formView;
}
/**
* @param formView The formView to set.
*/
public void setFormView(String formView) {
this.formView = formView;
}
/**
* @return Returns the successView.
*/
public String getSuccessView() {
return successView;
}
/**
* @param successView The successView to set.
*/
public void setSuccessView(String successView) {
this.successView = successView;
}
/**
* @return Returns the formRedirect.
*/
public String getFormRedirect() {
return formRedirect;
}
/**
* @param formRedirect The formRedirect to set.
*/
public void setFormRedirect(String formRedirect) {
this.formRedirect = formRedirect;
}
}