Spring - Redirect After Post (Even with Validation Errors)

How to preserve validation errors in spring with redirect after a POST?

Assume you have 2 controllers.If you redirect from one controller to another controller the values in model object won't be available in the other controller. So if you want to share the model object values then you have to say in first controller

the error that you make is you should replace attr.addAttribute to attr.addFlashAttribute

attr.addFlashAttribute("org.springframework.validation.BindingResult.enqueteur", binding);
attr.addFlashAttribute("enqueteur", enqueteur);

SpringMVC controller: how to stay on page if form validation error occurs

I tried the solution metioned in this post at this weekend, but it doesn't work for BindingResult.

The code below works but not perfect.

@ModelAttribute("command")
public PlaceOrderCommand command() {
return new PlaceOrderCommand();
}

@RequestMapping(value = "/placeOrder", method = RequestMethod.GET)
public String placeOrder(
@ModelAttribute("command") PlaceOrderCommand command,
ModelMap modelMap) {
modelMap.put(BindingResult.MODEL_KEY_PREFIX + "command",
modelMap.get("errors"));
return "placeOrder";
}

@RequestMapping(value = "/placeOrder", method = RequestMethod.POST)
public String placeOrder(
@Valid @ModelAttribute("command") PlaceOrderCommand command,
final BindingResult bindingResult, Model model,
final RedirectAttributes redirectAttributes) {
if (bindingResult.hasErrors()) {
redirectAttributes.addFlashAttribute("errors", bindingResult);

//it doesn't work when passing this
//redirectAttributes.addFlashAttribute(BindingResult.MODEL_KEY_PREFIX + "command", bindingResult);

redirectAttributes.addFlashAttribute("command", command);
return "redirect:/booking/placeOrder";
}
......
}

Spring MVC: Validation, Post-Redirect-Get, Partial Updates, Optimistic Concurrency, Field Security

  1. To partially update an entity, you should use @SessionAttributes to store the model in session between requests. You could use hidden form fields, but session is more secure.

  2. To use P/R/G with validation, use flashAttributes

  3. To secure fields use webDataBinder.setAllowedFields("field1","field2",...) or create a class specific to the form then copy values to your entity. Entities don't require setters for id and version (if using Hibernate).

  4. To use Optimistic Concurrency Control use the @Version annotation in your Entity and use @SessionAttributes on your controller.

Example code:

@Controller
@RequestMapping("/foo/edit/{id}")
@SessionAttributes({FooEditController.ATTRIBUTE_NAME})
public class FooEditController {

static final String ATTRIBUTE_NAME = "foo";
static final String BINDING_RESULT_NAME = "org.springframework.validation.BindingResult." + ATTRIBUTE_NAME;

@Autowired
private FooRepository fooRepository;

/*
Without this, user can set any Foo fields they want with a custom HTTP POST
setAllowedFields disallows all other fields.
You don't even need setters for id and version, as Hibernate sets them using reflection
*/
@InitBinder
void allowFields(WebDataBinder webDataBinder){
webDataBinder.setAllowedFields("name");
}

/*
Get the edit form, or get the edit form with validation errors
*/
@RequestMapping(method = RequestMethod.GET)
String getForm(@PathVariable("id") long id, Model model) {

/* if "fresh" GET (ie, not redirect w validation errors): */
if(!model.containsAttribute(BINDING_RESULT_NAME)) {
Foo foo = fooRepository.findOne(id);
if(foo == null) throw new ResourceNotFoundException();
model.addAttribute(ATTRIBUTE_NAME, foo);
}

return "foo/edit-form";
}

/*
@Validated is better than @Valid as it can handle http://docs.jboss.org/hibernate/validator/5.1/reference/en-US/html/chapter-groups.html
@ModelAttribute will load Foo from session but also set values from the form post
BindingResult contains validation errors
RedirectAttribute.addFlashAttribute() lets you put stuff in session for ONE request
SessionStatus lets you clear your SessionAttributes
*/
@RequestMapping(method = RequestMethod.POST)
String saveForm(
@Validated @ModelAttribute(ATTRIBUTE_NAME) Foo foo,
BindingResult bindingResult,
RedirectAttributes redirectAttributes,
HttpServletRequest request,
SessionStatus sessionStatus
) {

if(!bindingResult.hasErrors()) {
try {
fooRepository.save(foo);
} catch (JpaOptimisticLockingFailureException exp){
bindingResult.reject("", "This record was modified by another user. Try refreshing the page.");
}
}

if(bindingResult.hasErrors()) {

//put the validation errors in Flash session and redirect to self
redirectAttributes.addFlashAttribute(BINDING_RESULT_NAME, bindingResult);
return "redirect:" + request.getRequestURI();
}

sessionStatus.setComplete(); //remove Foo from session

redirectAttributes.addFlashAttribute("message", "Success. The record was saved");
return "redirect:" + request.getRequestURI();
}
}

Foo.java:

@Entity
public class Foo {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Version //for optimistic concurrency control
private int version;

@NotBlank
private String name;

public Long getId() {
return id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

}

edit-form.jsp (Twitter Bootstrap compatible):

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %>

<form:form modelAttribute="foo">

<spring:hasBindErrors name="foo">
<c:if test="${errors.globalErrorCount > 0}">
<div class="alert alert-danger" role="alert"><form:errors/></div>
</c:if>
</spring:hasBindErrors>

<c:if test="${not empty message}">
<div class="alert alert-success"><c:out value="${message}"/></div>
</c:if>

<div class="panel panel-default">
<div class="panel-heading">
<button class="btn btn-primary" name="btnSave">Save</button>
</div>

<div class="panel-body">

<spring:bind path="name">
<div class="form-group${status.error?' has-error':''}">
<form:label path="name" class="control-label">Name <form:errors path="name"/></form:label>
<form:input path="name" class="form-control" />
</div>
</spring:bind>

</div>
</div>

</form:form>

ResourceNotFoundException.java:

@ResponseStatus(HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {
}

Spring validator annotation and error redirection

How should I do to manage my redirection correctly ?

When you redirect a request, then the current request destroyed, and a new request object is created to process the request, so as per Sotirios Delimanolis has mentioned in comment, that model attributes are request attributes, which are available for per request only, If you want to store model attributes in the HTTP session between requests use @SessionAttributes, or a FlashAttributes.

As per your html form you have:

<p th:if="${#fields.hasErrors('name')}" th:errors="*{name}">Name Error</p>

you mean you want to show validation errors in the same page, then don't redirect return the same view.

if (bindingResult.hasErrors()) {
//return "redirect:/formPrinter";
return "your html form view";
}

then, on your view you can render all validation error messages like:

<p th:each="err : ${#fields.errors('*')}" th:text="${err}"></p> 

SpringMVC : POST Redirect GET and error message?

I made it work, but i have NO idea why :

Instead of this :

 @RequestMapping(method = RequestMethod.GET)
public void loadSettings(Model model) {
model.addAttribute("settings", new Settings());
model.addAttribute("settingsList", settingsService.getAllSettings();
}

I have this :

 @RequestMapping(method = RequestMethod.GET)
public void loadSettings(Model model) {
if (!model.containsAttribute("settings")) {
model.addAttribute("settings", new Settings());
}
model.addAttribute("settingsList", settingsService.getAllSettings();
}

And it works, but the form keeps the data posted. It's one or the other :/

Passing BindingResult through RedirectionAttributes

One more approach that may resolve the issue. Session attributes help to persist objects between requests, so the following based on it

@Controller
@SessionAttributes(
{ "exampleForm" })
public class HomeController
{

@ModelAttribute("exampleForm")
public ExampleForm getExampleForm()
{
return new ExampleForm();
}

@RequestMapping(value = "/myform", method = RequestMethod.POST)
public String proccessForm(@Valid @ModelAttribute("exampleForm")
final ExampleForm form, final BindingResult result,
final SessionStatus sessionStatus)
{
if (result.hasErrors())
{
return "redirect:/myform";
}
sessionStatus.setComplete();
return "redirect:/complete";
}

@RequestMapping(value = "/myform", method = RequestMethod.GET)
public String showForm(final Model model)
{
return "form";
}

@RequestMapping(value = "/complete", method = RequestMethod.GET)
public String showSomething(final Model model)
{
return "complete";
}
}


Related Topics



Leave a reply



Submit