I have a session attribute : user, and I have a url that I want to be viewed by both logged in users and publically by people not logged in as a user.
So what I want to do is this :
#Controller("myController")
#SessionAttributes({"user"})
public class MyController {
#RequestMapping(value = "/MyPage/{id}", method = RequestMethod.GET)
public ModelAndView getPage(#PathVariable Integer id) {
return modelandview1;
}
#RequestMapping(value = "/MyPage/{id}", method = RequestMethod.GET)
public ModelAndView getPage(#PathVariable Integer id, #ModelAttribute User user){
return modelandview2;
}
However, I have a feeling its not going to work ... suggestions very welcome.
You only need the second method, the one that takes the User agument as well. When it's called without request attributes available to populate the User model, you'll just get a User instance with all null (or all default) field values, then in the body of the method you treat each situation accordingly
I don't think it's a right case for #SessionAttributes. This annotation is usually used to keep original instance of a form-backing object, to avoid passing irrelevant parts of its state via hidden form fields.
Your sceanrio is completely different, thus it would be better to use HttpSession explicitly:
#RequestMapping(value = "/MyPage/{id}", method = RequestMethod.GET)
public ModelAndView getPage(#PathVariable Integer id, HttpSession session) {
User user = (User) session.getAttribute(...);
if (user != null) {
...
} else {
...
}
}
Also note that #ModelAttribute is a subject to data binding - user can change its fields by passing request parameters. You definitely don't want it in this case.
Related
I have two post calls. The first takes User object as a parameter, is mapped to /login and returns a List:
#RequestMapping(value="/login", method = RequestMethod.POST)
public ResponseEntity<List<Login>> login(#RequestBody User user) {
return new ResponseEntity<List<Login>>(HttpStatus.OK);
}
the second takes ApiToken object as a parameter, is mapped to /login/apitoken and returns a String:
#RequestMapping(value="/login/apitoken", method = RequestMethod.POST)
public ResponseEntity<String> autheticateApiToken(#RequestBody ApiToken apiToken) {
return new ResponseEntity<String>(authenticateApiToken, HttpStatus.OK);
}
I feel it is not a good design, so I want to combine it into one request mapping.
#RequestMapping(value={"/v1/login", "/v1/login/apitoken"}, method = RequestMethod.POST)
But I do not know how to continue, since the two endpoints have different RequestBody and return method. Does anyone know how to combine these two request mappings into one? Or is the old function good enough?
Question about Spring MVC #ModelAttribute methods, Setting model attributes in a controller #RequestMapping method verses setting attribute individually with #ModelAttribute methods, which one is considered better and is more used?
From design point of view which approach is considered better from the following:
Approach 1
#ModelAttribute("message")
public String addMessage(#PathVariable("userName") String userName, ModelMap model) {
LOGGER.info("addMessage - " + userName);
return "Spring 3 MVC Hello World - " + userName;
}
#RequestMapping(value="/welcome/{userName}", method = RequestMethod.GET)
public String printWelcome(#PathVariable("userName") String userName, ModelMap model) {
LOGGER.info("printWelcome - " + userName);
return "hello";
}
Approach 2
#RequestMapping(value="/welcome/{userName}", method = RequestMethod.GET)
public String printWelcome(#PathVariable("userName") String userName, ModelMap model) {
LOGGER.info("printWelcome - " + userName);
model.addAttribute("message", "Spring 3 MVC Hello World - " + userName);
return "hello";
}
The #ModelAttribute annotation serves two purposes depending on how it is used:
At Method level
Use #ModelAttribute at the method level to provide reference data for the model. #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.
In other words; a method annotated with #ModelAttribute will populate the specified “key” in the model. This happens BEFORE the #RequestMapping
At Method Parameter level
At Method Parameter level
When you place #ModelAttribute on a method parameter, #ModelAttribute maps a model attribute to the specific, annotated method parameter. This is how the controller gets a reference to the object holding the data entered in the form.
Examples
Method Level
#Controller
public class MyController {
#ModelAttribute("productsList")
public Collection<Product> populateProducts() {
return this.productsService.getProducts();
}
}
So, in the above example, “productsList” in the Model is populated before the the #RequestMapping is performed.
Method parameter level
#Controller
public class MyController {
#RequestMapping(method = RequestMethod.POST)
public String processSubmit(#ModelAttribute("product") Product myProduct, BindingResult result, SessionStatus status) {
new ProductValidator().validate(myProduct, result);
if (result.hasErrors()) {
return "productForm";
}
else {
this.productsService.saveProduct(myProduct);
status.setComplete();
return "productSaved";
}
}
}
Look here for detailed information with examples.
One is not better then the other. They both serve another purpose.
Method: If you need the model for a particular controller to be always populated with certain attributes the method level #ModelAttribute makes more sense.
Parameter: Use it on a parameter when you want to bind data from the request and add it to the model implicitly.
To answer your question on the better approach
I would say approach 2 is better since the data is specific to that handler.
I have a requirement where the user selects some data from a form and we need to show that selected data on the next page.
At present we are doing this with a session attribute, but the problem with this is that it overwrites the data if the first page is open in another browser tab, where the data is again selected and submitted. So I just want to get rid of this session attribute while transferring data from one controller to another.
Note: I am using an XML based Spring configuration, so please show a solution using XML, not annotations.
Define RedirectAttributes method parameter in the the handler method that handles form submission from first page:
#RequestMapping("/sendDataToNextPage", method = RequestMethod.POST)
public String submitForm(
#ModelAttribute("formBackingObj") #Valid FormBackingObj formBackingObj,
BindingResult result,
RedirectAttributes redirectAttributes) {
...
DataObject data = new DataObject();
redirectAttributes.addFlashAttribute("dataForNextPage", data);
...
return "redirect:/secondPageURL";
}
The flash attributes are saved temporarily before the redirect (typically in the session) and are available to the request after the redirect and removed immediately.
The above redirect will cause the client (browser) to send a request to /secondPageURL. So you need to have a handler method to handle this request, and there you can get access to the DataObject data set in the submitForm handler method:
#RequestMapping(value = "/secondPageURL", method = RequestMethod.GET)
public String gotoCountrySavePage(
#ModelAttribute("dataForNextPage") DataObject data,
ModelMap model) {
...
//data is the DataObject that was set to redirectAttributes in submitForm method
return "pageToBeShown";
}
Here DataObject data is the object that contains data from the submitForm method.
I worked with this requirement and I used RedirectAttributes, then you can add this redirect attributes to your model. This is an example:
#RequestMapping(value = "/mypath/{myProperty}", method = RequestMethod.POST)
public String submitMyForm(#PathVariable Long myProperty, RedirectAttributes redirectAttributes) {
redirectAttributes.addFlashAttribute("message", "My property is: " + myProperty);
return "redirect:/anotherPage";
}
#RequestMapping(method = RequestMethod.GET)
public String defaultPage(Model model, #RequestParam(required = false) String message) {
if(StringUtils.isNotBlank(message)) {
model.addAttribute("message", message);
}
return "myPage";
}
Hope it helps.
You can use RedirectAttributes ; A specialization of the Model interface that controllers can use to select attributes for a redirect scenario.
public interface RedirectAttributes extends org.springframework.ui.Model
Plus this interface also provide a way to store "Flash Attribute" . Flash Attribute is in FlashMap.
FlashMap : A FlashMap provides a way for one request to store attributes intended for use in another. This is most commonly needed when redirecting from one URL to another.
Quick Example is
#RequestMapping(value = "/accounts", method = RequestMethod.POST)
public String handle(RedirectAttributes redirectAttrs) {
// Save account ...
redirectAttrs.addFlashAttribute("message", "Hello World");
return "redirect:/testUrl/{id}";
}
Reference and detail information are here
i hav a home controller within which i hav 2 methods one is
#RequestMapping(value = "/mypage.te", method = RequestMethod.GET)
public String mypage1(Locale locale, Model model){
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String name = auth.getName(); //get logged in username
model.addAttribute("username", name);
model.addAttribute("customGroup",grpDao.fetchCustomGroup());
model.addAttribute("serverTime", formattedDate);
model.addAttribute("username", name);
return "mypage";
}
here in this method actually i call grpDao.fetchCustomGroup() method from a Dao class which performs a native query and fetches data and returns and it is saved in customGroup.
now the same fetchcustomGroup() method is to be used in another method i.e
#RequestMapping(value = "/manageGrps.te", method = RequestMethod.GET)
public String man_grp_connections(#RequestParam("id") Integer groupId,#RequestParam("name") String groupName, Model model) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String name = auth.getName();
System.out.println("I am in the fetchCustomGroup controller");
int profileid=grpDao.getProfileId(name);
//model.addAttribute("customGroup",grpDao.fetchCustomGroup());
model.addAttribute("memberList",grpDao.fetchGroupMembers(groupId,profileid));
model.addAttribute("groupid",groupId);
model.addAttribute("profileid",profileid);
model.addAttribute("groupName",groupName);
System.out.println("groupid="+groupId);
System.out.println("groupName="+groupName);
return "manageGrps";
}
so instead of calling the fetchCustomGroup() in both the methods i just want to call in only one method and use the result in both the methods in the home controller.
so how can i use customGroup in another method to use the result of the fetchCustomGroup()
I think that what you want is to avoid executing the query twice. This can be done in different ways. The easiest way would be to assign the response to a variable in your controller and then use a getter instead of the dao. Controllers are singleton by default. Something like :
private Foo customGroup;
private synchronized Foo getCustomGroup() {
if(customGroup == null) {
customGroup = grpDao.fetchCustomGroup();
}
return customGroup;
}
And then use getCustomGroup() instead of grpDao.fetchCustomGroup()
I don't know what you are using for your persistence but using cache would also be a good idea to avoid executing the query twice.
My controller has a method to return a form backing object:
#ModelAttribute(“userData”)
public UserData formBackingObject() {
return new UserData();
}
When the form submission fails its validation checks, it is redisplayed but when it is re-rendered, the userData object does not contain the user-submitted values - only the values present upon initialiation above.
#RequestMapping(method = RequestMethod.POST)
public void userData(HttpServletRequest request, #ModelAttribute(“userData”) UserData userData, BindingResult bindResult, ModelMap model) {
// do validation checks
if (bindResult.hasErrors()) {
// perform redirect back to same page
}
return "userData";
}
You need to do a model.addAttribute("key", value) . This will help bind the values to the model object check http://static.springsource.org/spring/docs/current/spring-framework-reference/html/mvc.html for sample.
#ModelAttribute at method level is generally given to add reference data kind of data to the model. And these annotations are executed before #RequestMapping; hence your attribute "userData" is refreshed with new object before control reaches your public void userData(). method.
The solution is to add userData to the model within the method which returns your userdata form jsp to browser.