I want to inject into the new object a class that contains the user.
#GetMapping
public String listTopic(Principal principal, Model model){
Optional<Users> user = usersService.findByUsername(principal.getName());
if (user.isPresent()){
Topics topic = new Topics();
topic.setUsers(user.get());
model.addAttribute("newTopic", topic);
model.addAttribute("topics", topicsService.listTopics());
return "forum/forum";
}
return "/error";
}
#PostMapping
public String addTopic(#Valid #ModelAttribute("newTopic") Topics topic, BindingResult bindingResult){
if(bindingResult.hasErrors()){
return "forum/forum";
}
topicsService.addTopic(topic);
System.out.println(topic);
return "redirect:/forum";
}
When I pass sysout after setting user obect or adding attribute at getmapping section it shows me the exact object, but when I want to see it at the postmapping it throws nullpointerexception.
Your model is a request scope object. After each request it is lost. You need to pass this information to a session object that is alive through different requests in the same session
https://stackoverflow.com/a/18795626/7237884
Related
I was struggling to get my Spring MVC validation to return to the page submitted page when I had errors. I finally solved the problem by noticing that BindingResult needs to be next to form parameter I'm validating.
For example if I amend the checkPersonInfo method in the spring.io tutorial(http://spring.io/guides/gs/validating-form-input/) to -
#RequestMapping(value="/", method=RequestMethod.POST)
public String checkPersonInfo(#Valid Person person, BindingResult bindingResult, Model model) {
if (bindingResult.hasErrors()) {
return "form";
}
return "redirect:/results";
}
Then it will work and redirect to the form page, but if I change it to -
#RequestMapping(value="/", method=RequestMethod.POST)
public String checkPersonInfo(#Valid Person person, Model model, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return "form";
}
return "redirect:/results";
}
Then it redirects to /errors
What is the cause of this?
The BindingResult has to follow the object that is bound. The reason is that if you have more objects that are bound you must know which BindingResult belongs to which object.
Yeah, Today I took a long time to check why cannot back to the submitted page but goes to a default whitelable error page.
After debugging got the source code
// org.springframework.web.method.annotation.ModelAttributeMethodProcessor#resolveArgument
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new BindException(binder.getBindingResult());
}
if BindingResult does not follow #Valid , causes isBindExceptionRequired(binder, parameter) return true and then directly throw exception so cannot execute code in controller method.
// org.springframework.web.method.annotation.ModelAttributeMethodProcessor#isBindExceptionRequired
protected boolean isBindExceptionRequired(WebDataBinder binder, MethodParameter methodParam) {
int i = methodParam.getParameterIndex();
Class<?>[] paramTypes = methodParam.getMethod().getParameterTypes();
boolean hasBindingResult = (paramTypes.length > (i + 1) && Errors.class.isAssignableFrom(paramTypes[i + 1]));
return !hasBindingResult;
}
You can potentially have multiple model attributes in your request handler, each with their own binding result. To accomodate this, Spring decided to bind binding result parameters to the previous paramater.
I have a Spring web app (Spring 3.2) and I have used following scenario to handle edit pages:
#Controller
#SessionAttributes(value = { "packet" })
public class PacketController {
#RequestMapping(value = "/edit-packet/{packet_id}", method = RequestMethod.GET)
public String editPacketForm(#PathVariable(value = "packet_id") Long packet_id, Model model)
{
model.addAttribute("packet", packetService.findById(packet_id));
return "packets/packetEdit";
}
POST method:
#RequestMapping(value = "/edit-packet/{packet_id}", method = RequestMethod.POST)
public String packetEditAction(Model model, #Valid #ModelAttribute(value = "packet")
Packet packet, BindingResult result, SessionStatus status)
{
if (result.hasErrors())
{
return "packets/packetEdit";
}
packetService.update(packet);
status.setComplete();
return "redirect:/";
}
Now the problem is what if someone tries to open multiple tabs for /edit-packet/{id} with different ids. With every new open tab the session 'packet' object will be overwritten. Then after trying to submit forms on multiple tabs, first tab will be submitted but it actually change the second packet because in session is second object and second tab will cause error because setComplete has been invoked so there is no 'packet' object in session.
(This is known issue https://jira.spring.io/browse/SPR-4160).
I am trying to implement this solution http://duckranger.com/2012/11/add-conversation-support-to-spring-mvc/ to solve this problem. I copied ConversationalSessionAttributeStore.java
ConversationIdRequestProcessor.java classes and in my servlet-config.xml I made this:
<mvc:annotation-driven />
<bean id="conversationalSessionAttributeStore"
class="com.xx.session.ConversationalSessionAttributeStore">
</bean>
<bean name="requestDataValueProcessor" class="com.xx.session.ConversationIdRequestProcessor" />
But it doesn't work, in my POST methods I don't see any new parameters, did I miss something?
UPDATE: Actually, it started working, but maybe someone has a better idea to solve this issue?
My other idea is to force a new session on every new tab, but it's not a nice solution.
Don't use session attributes, make your controller stateless and simply use the path variable to retrieve the correct model attribute.
#Controller
public class PacketController {
#ModelAttribute
public Packet packet(#PathVariable(value = "packet_id") Long packet_id) {
return packetService.findById(packet_id);
}
#RequestMapping(value = "/edit-packet/{packet_id}", method = RequestMethod.GET)
public String editPacketForm() {
return "packets/packetEdit";
}
#RequestMapping(value = "/edit-packet/{packet_id}", method = RequestMethod.POST)
public String packetEditAction(Model model, #Valid #ModelAttribute(value = "packet")
Packet packet, BindingResult result) {
if (result.hasErrors()) {
return "packets/packetEdit";
}
packetService.update(packet);
return "redirect:/";
}
}
Something like that should do the trick.
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 want to write rest like method for entity update. In this case I retrieve entity id from url and data from request body. The issue is in binding id with bean. Because neither EntityManager nor Spring-Data Crud Repo haven't update(id, bean) method. So I can set it myself
#RequestMapping(value = "/{id}", method = RequestMethod.POST)
public String update(#PathVariable("id") Long id, #Valid User user, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
user.setId(id); //Very bad
return "usersEdit";
}
user.setId(id); //Bad
repository.save(user);
return "redirect:/users/" + id;
}
or dismiss DRY and put id in forms as private field to.
Is there are any other solutions?
In Spring 3.1 a #ModelAttribute will be instantiated from a path variable if the path variable and the model attribute names are the same and there is a converter to instantiate the model attribute from the path variable value:
#RequestMapping(value="/{account}", method = RequestMethod.PUT)
public String update(#Valid #ModelAttribute Account account, BindingResult result) {
if (result.hasErrors()) {
return "accounts/edit";
}
this.accountManager.saveOrUpdate(account);
return "redirect:../accounts";
}
The full example is available at:
https://github.com/rstoyanchev/spring-mvc-31-demo
I have a Spring MVC application which communicates with the frontend with AJAX / JSON, and I have in the frontend a web application with HTML.
For adding an element to the database, I do this in the backend system:
#RequestMapping(value="add/", method=RequestMethod.POST)
public #ResponseBody SerializePerson addProject(#RequestBody Person person) {
Person p = this.personService.addPerson(person);
return new SerializePerson(p.getId(), p.getName(), p.getEmail());
}
But now I have the problem (this is a very simple example), that someone can create a project without a name, so the name = "" and a non valid email address.
My problem is, that I want to validate the fields in the backend system.
So I found a Spring MVC showcase here: https://src.springsource.org/svn/spring-samples/mvc-showcase/src/main/java/org/springframework/samples/mvc/validation/
They do this:
#RequestMapping("/validate")
public #ResponseBody String validate(#Valid JavaBean bean, BindingResult result) {
if (result.hasErrors()) {
return "Object has validation errors";
} else {
return "No errors";
}
}
So, is this the best way? So I have to do two steps:
validate the Person object (if no errors occur, go to step 2, otherwise show error message to user)
write the Person object to the datbase
Isn't it possible to combine these two steps in one step? And how can I put the Person POST object from the frontend to the "validate" method in the backend and to see which field fails (name or email), because telling only "Object has validation errors" is not so good :-)?
Best Regards.
I did it shown in this example: http://blog.springsource.com/2010/01/25/ajax-simplifications-in-spring-3-0/
#RequestMapping(method=RequestMethod.POST)
public #ResponseBody Map<String, ? extends Object> create(#RequestBody Account account, HttpServletResponse response) {
Set<ConstraintViolation<Account>> failures = validator.validate(account);
if (!failures.isEmpty()) {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
return validationMessages(failures);
} else {
accounts.put(account.assignId(), account);
return Collections.singletonMap("id", account.getId());
}
}