I am loading a user object my calling a service and then store this user as a command object in the model on GET in the controller. This user object has many properties that are not mapped in the jsp page. After submitting the form, I am getting the command object i the controller on POST. But strangely, I only see the properties in the command object which are mapped to the jsp page. All the other properties those were there when I load the object are lost. I need all the properties in object to be able to successfully save it in hte database.
Can anybody help me figure this problem? Thanks!
Update
I am adding some code to better understand it. In POST handler, I was expecting the command object to have all the properties that was loaded in GET handler in addition to the properties that are bound with jsp. Instead I am losing all propeties except those are bound to the jsp. Am I doing something wrong here?
#RequestMapping(method = RequestMethod.GET)
public String showForm(ModelMap model, HttpSession session, HttpServletRequest request) throws Exception {
UserBean user = Util.getUser(session);
UserBean command = (UserBean)userProfileService.loadByUserName(user.getUserName());
model.addAttribute("command", command);
return formView;
}
#RequestMapping(method = RequestMethod.POST)
public String onSubmit(#ModelAttribute("command") UserBean command, BindingResult result, HttpSession session) throws Exception {
UserBean user = (UserBean) command;
userProfileService.saveUser(user);
return "successView";
}
Update
I am adding some code to better understand it. In POST handler, I was expecting the command object to have all the properties that was loaded in GET handler in addition to the properties that are bound with jsp. Instead I am losing all propeties except those are bound to the jsp. Am I doing something wrong here?
#RequestMapping(method = RequestMethod.GET) public String showForm(ModelMap model, HttpSession session, HttpServletRequest request) throws Exception { UserBean user = Util.getUser(session); UserBean command = (UserBean)userProfileService.loadByUserName(user.getUserName()); model.addAttribute("command", command); return formView; }
#RequestMapping(method = RequestMethod.POST) public String onSubmit(#ModelAttribute("command") UserBean command, BindingResult result, HttpSession session) throws Exception { UserBean user = (UserBean) command; userProfileService.saveUser(user); return "successView"; }
Update
If I store the command object in session how would the jsp bind the propeties. I thought I needed to store it in model for that?
Could you explain please.
Update
storing the command object in session solves the problem. I was able to store it by using
#SessionAttributes ("command")
Thanks a lot!
That's expected behaviour. Spring does not take your existing object (how would it get it?) - it creates a new one and fills it with data.
You can use the #ModelAttribute annotated-method to specify a method which will load the existing object from storage (db?) by its ID (submitted).
#ModelAttribute annotated methods are executed before the chosen #RequestMapping annotated handler method. They effectively pre-populate the implicit model with specific attributes, often loaded from a database. Such an attribute can then already be accessed through #ModelAttribute annotated handler method parameters in the chosen handler method, potentially with binding and validation applied to it.
See 15.3.2.8 of the MVC docs
The Spring-MVC Model values become Request scope attributes in your JSP. This is a one way translation, Spring-MVC does not restore the Model values when the user POSTs a form from your page.
When I need to store information between a GET and a POST (that is, set something on a GET and read it back on a POST), I set a Session attribute.
In your case, I believe that you will need to do one of the following:
Call Util.getUser(session); in the onSubmit method.
Store the command object in the session in the showForm and retrieve it in the onSubmit method>
#ModelAttribute is used to direclty set the values in the Student object from the jsp, other wise in the servlet you have to get the properties using request.getattribute() and than call student setter method.
so u can use both the keywords in jsp page.
<form action="grade" method="get" name="contact1" modelAttribute="contact1">
</form>
or
Related
I have kept a map in the page model. While invoking the page, am fetching few items from database and will keep that in the map so that i will use it in the JSP. Mainly these items are used for populating options in dropdown.
#RequestMapping(value = "/initSystemPage")
public String initSystemPage(#ModelAttribute("systemModel") SystemModel systemModel, ModelMap modelMap) {
Map<String, List<DropdownVO>> data = ** fetch items from database **;
systemModel.setData(data);
return "system";
}
Upon invoking the screen, i can get the items from the model and populate the values in the dropdown. So far fine. but the issue happens if i do any action like submitting the form. As am not keep an element in the JSP corresponding to the data attribute, upon submitting the form dropdown data is not mapped to model hence it is not available in the JSP after page refresh.
I dont want to populate the items in model in every action methods. if i keep the data in session attributes, is it possible to do the populate in a common method that need to be invoked in all actions? something like init-binder
If you want to keep your current design you can make use of #SessionAttributes annotation at class level to ensure that your systemModel attribute is stored in session and is accessible for subsequent requests. However make sure that you clear the attribute when you have completed form processing using SessionStatus. For example
#Controller
#SessionAttributes("systemModel")
public SystemPageController{
#RequestMapping(value = "/initSystemPage" , method = RequestMethod.GET)
public String initSystemPage(#ModelAttribute("systemModel") SystemModel systemModel, ModelMap modelMap) {
Map<String, List<DropdownVO>> data = ** fetch items from database **;
systemModel.setData(data);
return "system";
}
}
Alternatively you can use #ModelAttribute annotation to indicate methods which should be called for every request for the Controller to add model data.
#ModelAttribute("systemModel")
public SystemModel populateSystemModel(){
//code to populate and return
}
The signature is very flexible. You can include most parameters used with #RequestMapping methods such as HttpServletRequest #RequestParam , #PathVaribale etc
I have many pages with the same form where I bind my object using commandName. I try to put the object to session with name "myObject" and use it for form (commandName = "myObject"). But system throws exception (Neither BindingResult nor plain target object for bean name "myObject").
How can I bind the object to session for any controllers and requests?
That error is typical when your use a form:form tag that targets a command object that is not available in the request.
A good approach is to combine #ModelAttribute annotated method with #SessionAttributes on a Controller that intially forwards to the view that contains the form, something like
#Controller
#SessionAttributes("myObject")
public class FormController {
#ModelAttribute("myObject")
public MyObject createMyObjectBean() {
return new MyObject();
}
...
}
The initally createMyObjectBean will be called with whatever is the first request to the controller methods, but it won't be called on subsequent request as the myObject value will come from session due to #SessionAttributes
just note that for this approach to work, you must have a controller that forwards to the view that contains your form
I'm using in my project Spring MVC and Thymeleaf. Let's say I want to achieve very simple usage - editing user's form. I use SessionAttributes:
#Controller
#RequestMapping(value="/admin/")
#SessionAttributes(value={"user"})
public class UsersController implements Serializable
in request GET I,ve got simple mapping:
#RequestMapping(value={"user/{id}", "{user/{id}/}"}, method=RequestMethod.GET)
public String edit(Model model, #PathVariable(value="id") Long id, RedirectAttributes redirectAttributes){
String username = SecurityUtils.getLoggedUsername(); //for example, Spring Security
User user = userService.getByIdAndUsername(id, username);
model.addAttribute("user", user);
return "admin/user";
}
Simply enough - check if session logged user has permission to edit user with specified ID (in this example - only his ID). Field with id will not be populate on his HTML form via "hidden" field, it will be stored in SessionAttributes and merged after calling POST method.
So far so good. But there comes a problem. What if - lets say - "very smart" user insert in his HTML debugger / generate a POST request with manually added hidden input with name = "user.id" (or general = "[object name].[object property] and call POST method? SessionAttributes will not be merged, because in my HTTP request property 'ID' exists.
#RequestMapping(value="user/{id}", method=RequestMethod.POST)
public String action(#ModelAttribute("user") User user, BindingResult bindingResult, Model model, RedirectAttributes redirectAttributes){
In this way anyone is able to edit someone's ID (assuming ID of other user is known) and edit other user. How to secure this part of system?
For now I have only one solution - treat anyone user as potential burglar and check POST method in the same way as GET method:
#RequestMapping(value="user/{id}", method=RequestMethod.POST)
public String action(User user, BindingResult bindingResult, Model model, RedirectAttributes redirectAttributes){
String username = SecurityUtils.getLoggedUsername(); //for example, Spring Security
User otherButTheSameUser = userService.getByIdAndUsername(id, username); //remember to evict this user from hibernate session
if(otherButTheSameUser!=null){
userService.update(user);
}
}
This should be secure enough, but here comes SQL performance issue, because of double SQL checking querys. Is this a good way? Is there another way how to achieve that? Maybe merging #SessionAttributes with request object with sessionAttributes priority ? How to achieve that?
As far I understand, you may leverage DataBinder.setAllowedFields() or DataBinder.setDisallowedFields() methods. Excerpt from javadoc:
In the case of HTTP form POST data for example, malicious clients can
attempt to subvert an application by supplying values for fields or
properties that do not exist on the form. In some cases this could
lead to illegal data being set on command objects or their nested
objects. For this reason, it is highly recommended to specify the
allowedFields property on the DataBinder.
So, I suggest to try something like this:
#InitBinder
protected void initBinder(WebDataBinder binder) {
// please check that it's really working
binder.setDisallowedFields("user.id");
}
I am using session.setAttribute to store user object after login. In next controller, I have #SessionAttribute for the same user and #ModelAttribute for same object to be used in the method mapped to a RequestMapping. After login if I click any link in the user home page it give
HttpSessionRequiredException: Session attribute '' required - not found in session
I am not sure what I am doing wrong. I went through many article and question in this site as well but could find any solution. The user object which I am storing in session stores user's account details which are required in all the controller to get different information from DB. I using SessionAttribute is wrong should I use HttpSession instead in all the controller and get the object from session manually or there is a proper way to handle in spring 3.0. Please note that this user object is not backing any form just login, but contains many other details.
As help would be good.
Have a look at my (non-perfect) use of session data:
#Controller
#SessionAttributes("sharedData")
public class RegistrationFormController {
#Autowired
private SharedData sharedData; // bean with scope="session"
#RequestMapping(value = {"/registrationForm"}, method = RequestMethod.GET)
public ModelAndView newForm() {
final ModelAndView modelAndView = new ModelAndView("registrationForm");
modelAndView.addObject("registrationForm", new RegistrationForm());
// I want to render some data from this object in JSP:
modelAndView.addObject("sharedData", sharedData);
return modelAndView;
}
#RequestMapping(value = {"/registrationForm"}, method = RequestMethod.POST)
public String onRegistrationFormSubmitted(HttpServletRequest request,
#ModelAttribute("registrationForm") RegistrationForm registrationForm, BindingResult result) {
if (result.hasErrors()) {
return "registrationForm";
}
// Perform business logic, e.g. persist registration data
return "formSubmitted";
}
}
I'm trying to use an Object as the command object of a <%# taglib uri="http://www.springframework.org/tags/form" prefix="form"%>element.
In the controller's GET method, I add the Object like this:
#RequestMapping(method = RequestMethod.GET)
public String renderForm(ModelMap model, HttpServletRequest request) {
[...]
model.addAttribute("voting", voting);
[...]
}
The rendered form does show the command object correctly when defined like this:
<form:form action="vote" method="PUT" commandName="voting" name="oform">
Now when trying to access the form's command object back in the controller, on the POST method, I have two approaches. First, I declare the #ModelAttribute in the signature:
#RequestMapping(method = RequestMethod.PUT)
public String newVoting(#ModelAttribute("voting") Voting voting, HttpServletRequest request) { [...]}
Or I access the ModelMap and get the value from the underlying map:
#RequestMapping(method = RequestMethod.PUT)
public String newVoting(ModelMap model, HttpServletRequest request) {
Voting voting = (Voting) model.get("voting");
[...]
}
When doing the first, I get the object as it was submitted by the form. Doing the latter, I get the object as it was BEFORE being handled by the form.
Why does the form's submit not change the object in the ModelMap and why does the #ModelAttribute differ from whats in the actual model? I feel like the name 'ModelAttribute' should result in the same object like getting the object directly from the model.
Maybe #ModelAttribute is kind of misleading?
EDIT
Forgot to mention that the object is a #SessionAttributes
#SessionAttributes({"voting", "state"})
Your first approach, using the #ModelAttribute annotation does two things:
Creating the Voting object using the submitted form data;
Exposing the Voting object to the view by adding it to the model (request, or, in your case, session because of the #SessionAttributes({"voting"})).
Your second approach uses just the ModelMap, so it's getting only model attributes (from the request or, in your case, the session, depending on the #SessionAttributes annotation). This approach does not use the data from the submitted form.