I am using Spring validation(JSR 303) in one of the web apps.I have no issues when a user submits data and spring validation works pretty neat.But I have a scenario where I have to fetch data from a service and validate it and then bind them to my view.(something non-form validation).How can I use #Valid in this case or does it have to be done differently?
Here is a sample code,i started with
#RequestMapping(value = "/{id}", method = RequestMethod.GET)
public ModelAndView getView(
#PathVariable("id") final String id, #User user,
HttpSession session) {
User user= getUser();
BindingResult result = new BeanPropertyBindingResult(user, "user");
validator.validate(user, result);
if(result.hasErrors()){
logger.log(Level.ERROR, "Errors");
}
ModelAndView view = new ModelAndView ("home");
view.addObject("user",user );
view.addAllObject(result.getModel());
return view;
As far as I understand you need to inject default org.springframework.validation.Validator into your controller (if #Valid works you should be able to do it)
#Autowired
Validator validator;
run validation manually as follows
User user = ...;
BindingResult result = BeanPropertyBindingResult(user, "user");
validator.validate(user, result);
and merge results into ModelMap (declare it as argument of your method) as follows
model.addAllAttributes(result.getModel());
Related
I am trying to create user form in my application using Spring MVC and then validating the ModelAttribute. If my validations fail, I use the ModelAndView to return the view showing validation errors on form. My Controller code to render blank form is:
#GetMapping("/home")
public ModelAndView getUserHome(){
ModelAndView mv = new ModelAndView();
//In actual application, I have more code to put attributes in ModelAndView that will be needed
//on view form, like select box options
mv.addObject("user", new User());
mv.setViewName("user/home/userHome");
return mv;
}
Once the user form on view is filled in, and submit button clicked, I POST the request to the following Controller method:
#PostMapping("/persistUser")
public ModelAndView saveUser(#ModelAttribute("user") #Validated User user, BindingResult bindingResult, ModelMap modelMap){
ModelAndView mv = new ModelAndView();
List<ObjectError> errors = bindingResult.getAllErrors();
if(bindingResult.hasErrors() && !(errors.size() == 1 && errors.get(0).getCode().equals("user.system.mismatch"))){
mv.addObject("user", user);
//more code to put more attributes like select box options on view
mv.setViewName("user/home/userHome");
return mv;
}
//if no errors, I have code to persist user object here. At this point
//I sure can have userId and pass this as request attribute to redirect URL.
//But, I need to also pass some messages(based on business logic) about what selections were
//made on user form. I do not have the option to persist these selections on backend,
//as our database does not have supporting columns for that. Therefore, I need to send them as
//redirect attributes. And these messages would be displayed only once, right after the user
//object was created. Upon subsequent visits to this userId view, these messages would no longer
//be required. So we don't really need to persist them at backend.
RedirectView rv = new RedirectView("/user/home?userId="+user.getId(), true, true, false);
//Here user.getId() is the userId newly persisted.
modelMap.addAttribute("message", messageSource.getMessage("user.system.mismatch", null, Locale.US));
return new ModelAndView(rv, modelMap);
}
I get the "user" model object here, perform validations, and if errors, I send user back to the form via ModelAndView. If no errors, I have code to persist object and redirect to another view that shows the user object that was just created. On this view, I need to display some messages which I am trying to pass as redirect attributes to the redirect receiving controller method. Please read the commented lines that explain why I need to pass the messages. I wrote my Controller method as follows:
#GetMapping("/user/home")
public ModelAndView getUser(
#RequestParam(name="userId", required = true) String userId,
#RequestParam(name="message", required = false) String message,
HttpSession session){
ModelAndView mv = new ModelAndView();
User user = (User)session.getAttribute("user");
mv.addObject("message", message);
mv.setViewName("user/home/view");
return mv;
}
I have not been able to retrieve the "message" attribute here. I have tried using RedirectAttributes too, but either way I am not able to retrieve the values stored in "message". Of course I can send "message" as another request parameter, but my messages tend to be long and I don't want to see super long URLs.
Can somebody please advice how do I tackle this problem? Thanks.
I finally figured a way to add "message" as RedirectAttributes and later retrieve it as ModelAttribute. Below are the modified version of controller methods:
#PostMapping("/persistUser")
public ModelAndView saveUser(#ModelAttribute("user") #Validated User user, BindingResult bindingResult, RedirectAttributes ra){
ModelAndView mv = new ModelAndView();
List<ObjectError> errors = bindingResult.getAllErrors();
if(bindingResult.hasErrors() && !(errors.size() == 1 && errors.get(0).getCode().equals("user.system.mismatch"))){
mv.addObject("user", user);
//more code to put more attributes like select box options on view
mv.setViewName("user/home/userHome");
return mv;
}
RedirectView rv = new RedirectView("/user/home?userId="+user.getId(), true, true, false);
//Here user.getId() is the userId newly persisted.
ra.addFlashAttribute("message", messageSource.getMessage("user.system.mismatch", null, Locale.US));
return new ModelAndView(rv);
}
And then the redirect receiving method is as follows:
#GetMapping("/user/home")
public ModelAndView getUser(
#RequestParam(name="userId", required = true) String userId,
#ModelAttribute(name="message") String message,
HttpSession session){
ModelAndView mv = new ModelAndView();
User user = (User)session.getAttribute("user");
mv.addObject("message", message);
mv.setViewName("user/home/view");
return mv;
}
This works as expected for me.
I have a simple scenario:
User submits form, if there are binding errors I redisplay it, otherwise I set a flash attribute and redirect to the home page. I can't get the command object and RedirectAttributes to play together though, I can either validate the command object or use redirect attributes but not both. This gives me a 400 Bad Request
#RequestMapping(value = "/set", method = RequestMethod.POST)
public String setPassword(#AuthenticationPrincipal User currentUser,
#Validated #ModelAttribute("command") SetPasswordCommand command,
RedirectAttributes redirectAttributes,
BindingResult bindingResult) {
if (bindingResult.hasErrors())
return SET_PASSWORD_VIEW_PATH;
...
redirectAttributes.addFlashAttribute("flashMessage", "Password changed");
return "redirect:/";
}
This works but without the flash attrbute:
#RequestMapping(value = "/set", method = RequestMethod.POST)
public String setPasswordPost(#AuthenticationPrincipal User currentUser,
#Validated #ModelAttribute("command") SetPasswordCommand command,
BindingResult bindingResult) {
if (bindingResult.hasErrors())
return SET_PASSWORD_VIEW_PATH;
...
return "redirect:/";
}
What is the recommended pattern for handling this sort of thing?
Your method signature is wrong, as explained here the BindingResult must directly follow the model attribute.
org.springframework.validation.Errors / org.springframework.validation.BindingResult validation results for a preceding command or form object (the immediately preceding method argument).
So moving the RedirectAttributes after the BindingResult will make it work.
#RequestMapping(value = "/set", method = RequestMethod.POST)
public String setPassword(#AuthenticationPrincipal User currentUser,
#Validated #ModelAttribute("command") SetPasswordCommand command,
BindingResult bindingResult,
RedirectAttributes redirectAttributes) { ... }
how to use #Requestparam #RequestBody together in spring restful
#RequestMapping(method = RequestMethod.POST, value = "/upate")
#ResponseBody
public ModelAndView availableCheck(
#RequestParam("key") String key, #RequestBody User user)
throws Exception {
//handle
//
}
I want to update user by unique key,so I need request key paramer and the new user json object.
Advance thanks!
There is some possible mistake: if you return a ModelAndView then it is highly unlikely that you want to be it the ResponseBody, therefore remove #ResponseBody.
The other problem is that RespondeBody is for strings. It mean put the Body string in this variable.
So it your user is the command object populated by some form, then just remove the #RequestBody annotation
#RequestMapping(method = RequestMethod.POST, value = "/upate")
public ModelAndView availableCheck(
#RequestParam("key") String key, User user)
throws Exception {
//handle
//
}
In one of my controllers I have:
#RequestMapping(value = "search", method = RequestMethod.GET)
public ModelAndView searchUsers(HttpSession session, HttpServletRequest request) {
UiUserSearchCriteria userSearchCriteria = (UiUserSearchCriteria) session
.getAttribute("UsersController_userSearchCriteria");
if (null == userSearchCriteria) {
userSearchCriteria= defaultUserSearchCriteria;
}
// Here be dragons
return searchUsers(userSearchCriteria, new BeanPropertyBindingResult(userSearchCriteria,
"userSearchCriteria"), session, request);
}
#RequestMapping(value = "search", method = RequestMethod.POST)
public ModelAndView searchUsers(
#ModelAttribute("userSearchCriteria") UiUserSearchCriteria userSearchCriteria,
BindingResult bindingResult, HttpSession session, HttpServletRequest request) {
userSearchCriteriaValidator.validate(userSearchCriteria, bindingResult);
if (bindingResult.hasErrors()) {
// Here be dragons
return new ModelAndView("searchUsers");
}
ModelAndView result = new ModelAndView("redirect:listUsers");
PagedListHolder<UiUser> userList = new PagedListHolder<UiUser>(
usersService.searchUsers(userSearchCriteria));
userList.setPageSize(10);
userList.setSort(defaultSort);
userList.resort();
session.setAttribute("UsersController_userList", userList);
session.setAttribute("UsersController_userSearchCriteria", userSearchCriteria);
return result;
}
The logic is simple: when the user navigates to search page I actually perform a search with default criteria and return him a list (this is the result of requirements changing, huh).
I found a problem in this code, accidentally. When default search criteria is not valid the behavior is: navigate to search -> populate search criteria with invalid criteria -> call another method (the second one, with POST) -> perform validation -> errors are not empty, so return searchUsers view. But the BindingResult bindingResult is actually syntethic, from previous method (new BeanPropertyBindingResult(userSearchCriteria, "userSearchCriteria")). So I got an error No binding result is bound to session (I agree with this).
I cannot have #ModelAttribute and BindingResult parameters (that, which are bound by Spring) pair in GET method to call POST with them.
So what is the best solutions for this?
I think you can simply associate your new BeanPropertyBindingResult(userSearchCriteria,
"userSearchCriteria") with an appropriate Spring model attribute name, this way:
BindingResult bindingResult = new BeanPropertyBindingResult(userSearchCriteria, "userSearchCriteria")
model.addAttribute(BindingResult.MODEL_KEY_PREFIX + "userSearchCriteria", bindingResult);
This is the default Spring MVC behavior of binding the validation results of a specific model attribute and should help you avoid the No binding result.. error
I have form object that I set to request in GET request handler in my Spring controller. First time user enters to page, a new form object should be made and set to request. If user sends form, then form object is populated from request and now form object has all user givern attributes. Then form is validated and if validation is ok, then form is saved to database. If form is not validated, I want to save form object to session and then redirect to GET request handling page. When request is redirected to GET handler, then it should check if session contains form object.
I have figured out that there is #SessionAttributes("form") annotation in Spring, but for some reason following doesnt work, because at first time, session attribute form is null and it gives error:
org.springframework.web.HttpSessionRequiredException: Session attribute 'form' required - not found in session
Here is my controller:
#RequestMapping(value="form", method=RequestMethod.GET)
public ModelAndView viewForm(#ModelAttribute("form") Form form) {
ModelAndView mav = new ModelAndView("form");
if(form == null) form = new Form();
mav.addObject("form", form);
return mav;
}
#RequestMapping(value="form", method=RequestMethod.POST)
#Transactional(readOnly = true)
public ModelAndView saveForm(#ModelAttribute("form") Form form) {
FormUtils.populate(form, request);
if(form.validate())
{
formDao.save();
}
else
{
return viewForm(form);
}
return null;
}
It throws Exception if controller called first time even though added #SessionAttributes({"form"}) to class. So add following populateForm method will fix this.
#SessionAttributes({"form"})
#Controller
public class MyController {
#ModelAttribute("form")
public Form populateForm() {
return new Form(); // populates form for the first time if its null
}
#RequestMapping(value="form", method=RequestMethod.GET)
public ModelAndView viewForm(#ModelAttribute("form") Form form) {
ModelAndView mav = new ModelAndView("form");
if(form == null) form = new Form();
mav.addObject("form", form);
return mav;
}
#RequestMapping(value="form", method=RequestMethod.POST)
#Transactional(readOnly = true)
public ModelAndView saveForm(#ModelAttribute("form") Form form) {
// ..etc etc
}
}
The job of #SessionAttribute is to bind an existing model object to the session. If it doesn't yet exist, you need to define it. It's unnecessarily confusing, in my opinion, but try something like this:
#SessionAttributes({"form"})
#Controller
public class MyController {
#RequestMapping(value="form", method=RequestMethod.GET)
public ModelAndView viewForm(#ModelAttribute("form") Form form) {
ModelAndView mav = new ModelAndView("form");
if(form == null) form = new Form();
mav.addObject("form", form);
return mav;
}
#RequestMapping(value="form", method=RequestMethod.POST)
#Transactional(readOnly = true)
public ModelAndView saveForm(#ModelAttribute("form") Form form) {
// ..etc etc
}
}
Note that the #SessionAttributes is declared on the class, rather than the method. You can put wherever you like, really, but I think it makes more sense on the class.
The documentation on this could be much clearer, in my opinion.
if there is no defined session object so I think it's gonna be like this:
#SessionAttributes({"form"})
#Controller
public class MyController {
#RequestMapping(value="form", method=RequestMethod.GET)
public ModelAndView viewForm() {
ModelAndView mav = new ModelAndView("form");
if(form == null) form = new Form();
mav.addObject("form", form);
return mav;
}
#RequestMapping(value="form", method=RequestMethod.POST)
#Transactional(readOnly = true)
public ModelAndView saveForm(#ModelAttribute("form") Form form) {
// ..etc etc
}
}
#Controller
#SessionAttributes("goal")
public class GoalController {
#RequestMapping(value = "/addGoal", method = RequestMethod.GET)
public String addGoal(Model model) {
model.addAttribute("goal", new Goal(11));
return "addGoal";
}
#RequestMapping(value = "/addGoal", method = RequestMethod.POST)
public String addGoalMinutes(#ModelAttribute("goal") Goal goal) {
System.out.println("goal minutes " + goal.getMinutes());
return "addMinutes";
}
}
On page addGoal.jsp user enters any amount and submits page. Posted amount is stored in HTTP Session because of
#ModelAttribute("goal") Goal goal
and
#SessionAttributes("goal")
Without #ModelAttribute("goal") amount entered by user on addGoal page would be lost
I'm struggling with this as well. I read this post and it made some things clearer:
Set session variable spring mvc 3
As far as I understood it this basically says:
that Spring puts the objects specified by #SessionAttributes into the session only for the duration between the first GET request and the POST request that comes after it. After that the object is removed from the session. I tried it in a small application and it approved the statement.
So if you want to have objects that last longer throughout multiple GET and POST requests you will have to add them manually to the HttpSession, as usual.