Spring - Model attributes after BindingResult with errors - spring

In the following piece of code I just want to create a new user and link it to the selected groups.
Everything works fine when the user and group are valid. The problem comes when the bindingresult has errors. The controller detects such error (all fine so far) and returns the same view (I want to keep the data entered by the user) but the list of groups is empty (I have discovered that, after showing again the view, userform.groups is null).
Has anyone a clue about what the problem could be?
UserForm
#Component
public class UserForm {
#Valid
private User user;
#Valid
private Collection<Group> allGroups;
// Setters and getters
}
UserController
#Controller
public class UserController {
#Autowired
UserGroupService userGroupService;
#Autowired
BCryptPasswordEncoder passwordEncoder;
#InitBinder
public void initBinder (WebDataBinder binder) {
binder.registerCustomEditor(Set.class, "userform.user.groups", new GroupListEditor(userGroupService));
}
#RequestMapping(value = "/admin/users/CreateUser", method = RequestMethod.GET)
public ModelAndView createUsetGet () {
ModelAndView mav = new ModelAndView("/admin/users/CreateUser");
UserForm userForm = new UserForm();
userForm.setUser(new User());
userForm.setGroups(userGroupService.getAllEnabledGroups());
mav.addObject("userform", userForm);
return mav;
}
#RequestMapping(value = "/admin/users/CreateUser", method = RequestMethod.POST)
public String createUserPost (#Valid #ModelAttribute("userform") UserForm userForm, BindingResult result) {
if (result.hasErrors() == true) {
return "/admin/users/CreateUser";
}
userForm.getUser().setPassword(passwordEncoder.encode(userForm.getUser().getPassword()));
userGroupService.saveUser(userForm.getUser());
return "redirect:/admin/users/ViewUsers";
}
}
CreateUser.jsp (Only piece regarding the groups)
<form:form modelAttribute="userform" method="post">
Username:
<form:input path="user.loginName"/>
<!-- More fields -->
<form:select path="user.groups" multiple="true">
<form:options items="${userform.groups}" itemValue="id" itemLabel="name" />
</form:select>
<button type="submit">Create</button>
</form:form>
Any help is appreciated!

The object gets recreated and values are bound to the resulting object. Which means no group objects.
Also those shouldn't be in the object at all. To solve use a #ModelAttribute annotated method, which will be invoked for each request handling method and create an object and fill the list of groups.
#ModelAttribute
public void init(Model model) {
UserForm userForm = new UserForm();
userForm.setUser(new User());
model.addAttribute("userform", userForm);
model.addAtrribute("groups", userGroupService.getAllEnabledGroups());
}
#RequestMapping(value = "/admin/users/CreateUser", method = RequestMethod.GET)
public String createUsetGet () {
return "/admin/users/CreateUser";
}
#RequestMapping(value = "/admin/users/CreateUser", method = RequestMethod.POST)
public String createUserPost (#Valid #ModelAttribute("userform") UserForm userForm, BindingResult result) {
if (result.hasErrors() == true) {
return "/admin/users/CreateUser";
}
userForm.getUser().setPassword(passwordEncoder.encode(userForm.getUser().getPassword()));
userGroupService.saveUser(userForm.getUser());
return "redirect:/admin/users/ViewUsers";
}
Ofcourse your jsp has to change slightly also.
<form:select path="user.groups" multiple="true">
<form:options items="${groups}" itemValue="id" itemLabel="name" />
</form:select>
There is one drawback of using this approach now the userGroupService.getAllEnabledGroups() is called for each incoming request. This might not be needed. You could store those in the session using the #SessionAttributes annotation on the class.
#Controller
#SessionAttributes("groups")
public class UserController {
#Autowired
UserGroupService userGroupService;
#Autowired
BCryptPasswordEncoder passwordEncoder;
#InitBinder
public void initBinder (WebDataBinder binder) {
binder.registerCustomEditor(Set.class, "userform.user.groups", new GroupListEditor(userGroupService));
}
#ModelAttribute("groups")
public List<Group> groups() {
return userGroupService.getAllEnabledGroups();
}
#ModelAttribute("userform")
public UserForm userform() {
UserForm userForm = new UserForm();
userForm.setUser(new User());
return userForm;
}
#RequestMapping(value = "/admin/users/CreateUser", method = RequestMethod.GET)
public String createUsetGet () {
return "/admin/users/CreateUser";
}
#RequestMapping(value = "/admin/users/CreateUser", method = RequestMethod.POST)
public String createUserPost (#Valid #ModelAttribute("userform") UserForm userForm, BindingResult result, SessionStatus status) {
if (result.hasErrors() == true) {
return "/admin/users/CreateUser";
}
userForm.getUser().setPassword(passwordEncoder.encode(userForm.getUser().getPassword()));
userGroupService.saveUser(userForm.getUser());
status.setComplete();
return "redirect:/admin/users/ViewUsers";
}
}
You will then need, on success, to tell the SessionStatus that you are finished. If you don't do this your session might pollute.

It's because the information about the validation errors is lost after redirect.
You can solve this using RedirectAttributes. Check this tutorial.

Related

NullPointException while uploading file in spring

I try to upload a .csv file's data into database, but when i upload and submit it, it throws nullpointexception. Means, when I print name in controller, name is printed, but when i try to get the file, it show null.
FileUpload model class
public class FileUpload {
private CommonsMultipartFile[] files;
private String name;
// Getters and setters
}
Controller
#RequestMapping(value = "uploadPage", method = RequestMethod.GET)
public ModelAndView uploadPage() {
ModelAndView model = new ModelAndView("upload_page");
FileUpload formUpload = new FileUpload();
model.addObject("formUpload", formUpload);
return model;
}
#RequestMapping(value = "/doUpload", method = RequestMethod.POST)
public String doUpload(#ModelAttribute("formUpload") FileUpload fileUpload, BindingResult result) throws IOException, JAXBException {
System.out.println("myfirl "+fileUpload.getFiles()); // output is null
System.out.println("name "+fileUpload.getName()); // name is displaying
//other stuffs
}
upload_page
<spring:url value="/doUpload" var="doUploadURL"/>
<form:form method="post" modelAttribute="formUpload" action="${doUploadURL }" enctype="multipart/form-data">
<form:input path="files" type="file" multiple="multiple"/>
<form:input path="name" type="text"/>
<button type="submit">Upload</button>
</form:form>
WebConfig
#Bean(name="multipartResolver")
public CommonsMultipartResolver getResolver(){
CommonsMultipartResolver commonsMultipartResolver=new CommonsMultipartResolver();
commonsMultipartResolver.setMaxUploadSizePerFile(20*1024*1024);
return commonsMultipartResolver;
}
I tried to sort it out in many ways, but failed. Anyone try to sort it out? Thanks in advance
#RequestMapping(value = "/uploadFile", method = RequestMethod.POST)
#ResponseBody
public String uploadFileHandler(#RequestParam("file") MultipartFile file) {
if (!file.isEmpty()) {
System.out.println(file.getName);
}
}
or you can do that
#RequestMapping(value = "/uploadFile", method = RequestMethod.POST)
public String uploadFileHandler(MultipartHttpServletRequest request) {
Iterator<String> itr = request.getFileNames();
while (itr.hasNext()){
System.out.println(itr.next().toString());
}
....
}

Getting CommandBean or form bean object with out #ModelAttribute in spring mvc

I have a requirement of getting commandbean or form bean object into the controller without using #ModelAttribute either from ModelMap or HttpServletRequest or anything else.
My code is:
JSP:
<form:form commandName="user" method="POST"
action="${pageContext.request.contextPath}/user/createUser">
Name:<form:input path="name" />
Password:<form:input path="password" />
<input type="submit"/>
</form:form>
Controller:
#Controller
#RequestMapping("/user")
public class UserController {
#RequestMapping(method = RequestMethod.GET)
public String setupForm(ModelMap model) {
modelMap.addAttribute("user", new User());
return "userRegistration";
}
#RequestMapping(value = "/createUser", method = RequestMethod.POST)
public String createUser(ModelMap model,HttpServletRequest request) {
User user=(User)model.get("user");// Retruns null
//Tried using request object but user object is not available in it.
return "message";
}
}
I tried different ways but nothing worked out.
You can implement a HandlerMethodArgumentResolver to create the bean manually, then (for example) use a WebMvcConfigurerAdapter to declare it. The argument resolvers supportsParameter method should check the expected type of the parameter. After that you can add a parameter in your Controller that is of the desired type.
You can do it by hand like you would do if you did not know the magic of Spring : just the the HttpServletRequest and gets its parameters to feed your User. It could look like :
#RequestMapping(value = "/createUser", method = RequestMethod.POST)
public String createUser(ModelMap model,HttpServletRequest request) {
User user= new User();
String name = request.getParameter("name");
String password = request.getParameter("password");
if (name != null) {
user.setName(name);
}
if (password!= null) {
user.setPassword(password);
}
model.addAttribute("user", user);
//Tried using request object but user object is not available in it.
return "message";
}
Or in a more terse way : user.setName(request.getParameter("name");

Spring 4.0 missing validation errors

Not sure what am I doing wrong - but after I submit the person form without any value
I am unable to see any validation error in the html output.
When I add a breakpoint in the controller I am able to see the "errors"
So it is going to result.hasErrors() tried to add * form:errors path="*" - still nothing
but still errors are not on the form.
Get method:
Person class is a POJO with no annotations.
#RequestMapping(value="/person/add" , method = RequestMethod.GET)
public ModelAndView personAdd() {
ModelAndView modelAndView = new ModelAndView("personAdd");
Person person = new Person();
person.setCreationDate(new Date());
modelAndView.addObject(person);
return modelAndView;
}
post method to save the new person
#RequestMapping(value="/person/add" , method = RequestMethod.POST)
public ModelAndView processSubmit(#ModelAttribute("person") Person person,BindingResult result) {
personValidator.validate(person, result);
if (result.hasErrors()) {
ModelAndView modelAndView = new ModelAndView("personAdd");
modelAndView.addObject(person);
return modelAndView;
} else {
ModelAndView modelAndView = new ModelAndView("refreshParent");
dao.persist(person);
return modelAndView;
}
}
The personValidator:
#Override
public void validate(Object target, Errors errors) {
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "personName","required.personName", "Name is required.");
The Person form (for simplicity only name is there)
<form:form method="POST" modelAttribute="person" action="${pageContext.request.contextPath}/person/add">
<form:errors path="*" cssClass="errorblock" element="div"/>
<form:errors path="*" />
<div class="form-group">
<label class="control-label" for="inputError">Person Name:</label>
<form:input path="personName" class="form-control" placeholder="personName"/>
<form:errors path="personName" cssClass="error" />
</div>
<form:form>
ModelAndView modelAndView = new ModelAndView("personAdd");
This line constructs a a new ModelAndView and by doing that you dismiss the current model. You add only the object in the line following this, but you have effectivly destroyed the binding results already available. If you use this construct pass in the model from the BindingResults. And you don't have to add the model object anymore as that is already included.
ModelAndView modelAndView = new ModelAndView("personAdd", result.getModel());
However with the annotation driven #Controller you don't have to return a ModelAndView in this case a simple String would suffice.
#RequestMapping(value="/person/add" , method = RequestMethod.POST)
public ModelAndView processSubmit(#ModelAttribute("person") Person person,BindingResult result) {
personValidator.validate(person, result);
if (result.hasErrors()) {
return "personAdd";
} else {
dao.persist(person);
return "refreshParent";
}
}
This will render the correct view and leave the current model (which contains the errors) intact.
You could even apply automatic validation by adding #Valid to your model attribute argument and include a #InitBinder annotated method.
#RequestMapping(value="/person/add" , method = RequestMethod.POST)
public ModelAndView processSubmit(#Valid #ModelAttribute("person") Person person,BindingResult result) {
if (result.hasErrors()) {
return "personAdd";
} else {
dao.persist(person);
return "refreshParent";
}
}
#InitBinder
public void initBinder(WebDataBinder dataBinder) {
dataBinder.setValidator(personValidator);
}
Try to put this configuration:
#RequestMapping(value="/owners/{ownerId}/pets/{petId}/edit", method = RequestMethod.POST)
public String processSubmit(#Valid #ModelAttribute("pet") Pet pet, BindingResult result) {
if (result.hasErrors()) {
return "petForm";
}
// ...
}
And put this in your person class
#NotNull
String personName;
And do not forget to place in your Spring XML file or the same but in your java configuration
<mvc:annotation-driven/>
All this will do the validation

Sprint MVC #ModelAttribute POST not working

I have this problem where I call API with get and it works fine while it gives empty object in case of POST. Below are the code snippets.
#Controller
#RequestMapping("/demo")
public class DemoController {
#RequestMapping(value = "/create")
public ModelAndView createUser(#ModelAttribute User user) {
...
...
}
}
GET: localhost:8080/demo/create.json?name=test&title=this works fine
POST using form-data is not working. I am getting empty object.
public class User implements Serializable {
private String name;
private String title;
...
}
I would suggest the following code.
#RequestMapping(value = "/create", method = RequestMethod.GET)
public ModelAndView createUser() {
ModelAndView modelAndView = new ModelAndView("/createuser.jsp");
modelAndView.addObject("user", new User());
return modelAndView;
}
#RequestMapping(value = "/create", method = RequestMethod.POST)
public ModelAndView createUserProcess(#ModelAttribute("user") User user,) {
// save user to db
}
Just out of curiosity. Shouldn't you be displaying the form with createUser and processing it with createUserProcess? Also make sure your form has the following:
<form:form commandName="user" modelAttribute="user">
</form:form>
Thanks for your kind replies. I was using Postman to validate my controller. I found I need to add multipart resolver.
Below is the line that fixed it.
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver"/>
Thanks!

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

I have next working code in my SpringMVC controller:
#RequestMapping(value = "/register", method = RequestMethod.GET)
public void registerForm(Model model) {
model.addAttribute("registerInfo", new UserRegistrationForm());
}
#RequestMapping(value = "/reg", method = RequestMethod.POST)
public String create(
#Valid #ModelAttribute("registerInfo") UserRegistrationForm userRegistrationForm,
BindingResult result) {
if (result.hasErrors()) {
return "register";
}
userService.addUser(userRegistrationForm);
return "redirect:/";
}
In short create method try to validate UserRegistrationForm. If form has errors, it leaves user on the same page with filled form fields where error message will be shown.
Now I need to apply the same behaviour to another page, but here I have a problem:
#RequestMapping(value = "/buy/{buyId}", method = RequestMethod.GET)
public String buyGet(HttpServletRequest request, Model model, #PathVariable long buyId) {
model.addAttribute("buyForm", new BuyForm());
return "/buy";
}
#RequestMapping(value = "/buy/{buyId}", method = RequestMethod.POST)
public String buyPost(#PathVariable long buyId,
#Valid #ModelAttribute("buyForm") BuyForm buyForm,
BindingResult result) {
if (result.hasErrors()) {
return "/buy/" + buyId;
}
buyForm.setId(buyId);
buyService.buy(buyForm);
return "redirect:/show/" + buyId;
}
I faced with issue of dynamic url. Now if form has errors I should specify the same page template to stay on current page, but also I should pass buyId as a path variable. Where are a conflict in this two requirements. If I leave this code as is, I get an error (I'm using Thymeleaf as a template processor):
Error resolving template "/buy/3", template might not exist or might not be accessible by any of the configured Template Resolvers
I can write something like return "redirect:/buy/" + buyId, but in this case I lose all data and errors of form object.
What should I do to implement in buyPost method the same behaviour as in create method?
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";
}
......
}
*I'm using Hibernate Validator APIs to validate my beans. To preserve form data along with displaying error messages, you need to do these 3 things:
Annotate your bean (eg. #NotEmpty, #Pattern, #Length, #Email etc.)
Inside controller:
#Controller
public class RegistrationController {
#Autowired
private RegistrationService registrationService;
#RequestMapping(value="register.htm", method=RequestMethod.GET, params="new")
public String showRegistrationForm(Model model) {
if (!model.containsAttribute("employee")) {
model.addAttribute("employee", new Employee());
}
return "form/registration";
}
#RequestMapping(value="register.htm", method=RequestMethod.POST)
public String register(#Valid #ModelAttribute("employee") Employee employee, BindingResult bindingResult, RedirectAttributes redirectAttributes) {
if (bindingResult.hasErrors()) {
redirectAttributes.addFlashAttribute("org.springframework.validation.BindingResult.employee", bindingResult);
redirectAttributes.addFlashAttribute("employee", employee);
return "redirect:register.htm?new";
}
registrationService.save(employee);
return "workspace";
}
// ....
}
Update your view/jsp to hold error messages:
This article can surely be helpful.
You can change your POST implementation to this:
#RequestMapping(value = "/buy/{buyId}", method = RequestMethod.POST)
public String buyPost(#PathVariable long buyId,
#Valid #ModelAttribute("buyForm") BuyForm buyForm,
BindingResult result) {
buyForm.setId(buyId); // important to do this also in the error case, otherwise,
// if the validation fails multiple times it will not work.
if (result.hasErrors()) {
byForm.setId(buyId);
return "/buy/{buyId}";
}
buyService.buy(buyForm);
return "redirect:/show/{buyId}";
}
Optionally, you can also annotate the method with #PostMapping("/buy/{buyId}") if you use Spring 4.3 or higher.

Resources