Sprint MVC #ModelAttribute POST not working - spring

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!

Related

Tomcat Show HTTP Status 400 Error Page During Validation

I'm learning to make Bean Validation works in Spring MVC with Thymeleaf as default view. Every valid data can be saved properly. But when I tried an invalid data passed, Tomcat just showed HTTP Status 400 Error page. In Tomcat console showed something like validation but just became logging text in Tomcat console. Here is the controller that saves data (item).
#Controller
#RequestMapping("/item")
#SessionAttributes("item")
public class ItemController {
#Autowired
private ItemService itemService;
#Autowired
private ColorService colorService;
#ModelAttribute("allColors")
public List<Color> populateColors() {
return colorService.findAll();
}
#ModelAttribute("allItems")
public List<Item> populateItems() {
return itemService.findAll();
}
#RequestMapping(value = {"/image/{id}", "image/{id}"})
#ResponseBody
public byte[] showImage(#PathVariable("id") String id) {
return itemService.getItem(id).getImage();
}
#RequestMapping(value = {"", "/"}, method = RequestMethod.GET)
public String showAllItems() {
return "itemList";
}
#RequestMapping(value = {"add", "/add"}, method = RequestMethod.GET)
public String showItemAddForm(Model model) {
model.addAttribute("item", new Item());
return "itemAddForm";
}
#RequestMapping(value = {"add", "/add"}, method = RequestMethod.POST)
public String processAddItem(
#ModelAttribute("item") #Valid Item item,
RedirectAttributes model,
BindingResult errors,
SessionStatus session) {
if (errors.hasErrors()) {
return "itemAddForm";
}
itemService.saveItem(item);
session.setComplete();
model.addFlashAttribute("message", "Item has been added");
return "redirect:/item";
}
}
Is any wrong with the controller? How should I to make Bean Validation works with Spring and Thymeleaf?

Spring - Model attributes after BindingResult with errors

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.

Hibernate and #SessionAttribute

Hibernate not updating SessionAttribute parameter.
I populated an entity object and put it in SessionAttribute with a name editGroup. I changed it on jsp page and sent to the controller. In controller I just call service method and give him object from session(editGroup). But entity object not updating.
Controller
#Controller
#SessionAttributes(value = "editGroup")
#RequestMapping(value = "/group")
public class GroupController {
...
#RequestMapping(value = "/{id}/edit", method = RequestMethod.GET)
public String groupEdit(#PathVariable Long id, Model model) {
if (!model.containsAttribute("editGroup")) {
model.addAttribute("editGroup", groupService.findGroup(id));
}
return "group/editGroup";
}
#RequestMapping(value = "/{id}/update", method = RequestMethod.POST)
public String groupUpdate(#ModelAttribute("editGroup") Group editGroup,
#PathVariable Long id, SessionStatus status) {
groupService.updateGroup(editGroup);
status.setComplete();
return "redirect:/group/" + id;
}
}
Service #Transactional
public void updateGroup(Group obj) {
groupDAO.update(group);
}
DAO
public void update(T obj) {
getSession().merge(obj);
}
Hibernate even not sends request. Help me please.
I found answer on my issue.
I had the <tx:annotation-driven/> in a spring configuration. After specify the transaction-manager parameter in this one, program running properly.
<tx:annotation-driven transaction-manager="transactionManager"/>

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");

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