Resend request parameters in ModelAndView - java

I have a thymeleaf template that reads a URL parameter:
http://xxxx:8080/department?action=edit
In this way:
<input type="text" th:placeholder="#{department.id}" class="form-control" th:field="*{id}" th:readonly="${param.action[0] == 'edit'}">
Basically this let's you edit if action=edit is in the URL. This works fine, but when I handle the POST method, the modelAndView redirect to /departent alone without the parameters when there are errors:
#RequestMapping(value = "/department", method = RequestMethod.POST)
public ModelAndView department(HttpServletRequest request, #Valid Department department,
BindingResult bindingResult) {
ModelAndView modelAndView = new ModelAndView();
if (bindingResult.hasErrors()) {
// data with errores, try again
modelAndView.setViewName("department");
} else {
// all ok. Save and continue
departmentService.updateDepartment(department);
modelAndView.setViewName("redirect:departments");
}
return modelAndView;
}
When the page reload, I have the following error message:
Caused by: org.thymeleaf.exceptions.TemplateProcessingException: Exception evaluating SpringEL expression: "param.action[0] == 'edit'" (template: "department" - line 24, col 103)
The reason is that the new URL is:
http://xxxx:8080/department
The think that I need to use a URL parameters is because the link is generated by A HREF link.
I've tried:
modelAndView.getModelMap().put("action", "edit");
But this doesn't work.

I would simplify this by making param.action[0] == 'edit' a variable and simply adding that to the model. Like:
model.addAttribute("isReadOnly", someVariableHereThatMakesItReadOnly);
and
th:readonly="${isReadOnly}" in your form.
This makes your view less complex and allows you to unit test the value of isReadOnly on the server-side. Then you can do:
#PostMapping("/department")
public String postDepartment(#Valid Department department,
BindingResult result) {
if (result.hasErrors()) {
//add error information here
model.addAttribute("isReadOnly", true);
return "department"
}
departmentService.updateDepartment(department);
return "redirect:/departments";
}
There are likely multiple ways you can do this. This is just one way.
You could also probably do return "redirect:/department?action=edit" in your post method, but then you'd need to get creative about how to display any error messages.

If you don't want to change too much your code, you add to html:
<input type="hidden" name="requestedAction" th:value="${param.action[0]}">
And change the RequestMapping method to:
#RequestMapping(value = "/department", method = RequestMethod.POST)
public ModelAndView department(HttpServletRequest request, #Valid Department department, BindingResult bindingResult, #RequestParam String requestedAction) {
ModelAndView modelAndView = new ModelAndView();
if (bindingResult.hasErrors()) {
// data with errores, try again
modelAndView.setViewName("department?action=" + requestedAction);
} else {
// all ok. Save and continue
departmentService.updateDepartment(department);
modelAndView.setViewName("redirect:departments");
}
return modelAndView;
}

Related

Redirect ModelAndView with message

My application has a method to update a conference. After doing so I have a modelandview with a redirect to the main conference list. This all works fine although the message which I add as an object to the modelandview does not display.
My method in my controller:
#PostMapping("/updateConference")
public ModelAndView updateConference(
#ModelAttribute("conference") #Valid ConferenceDto conferenceDto, BindingResult result) {
if(result.hasErrors()){
return new ModelAndView("updateConference","conferenceDto", conferenceDto);
}
try {
conferenceService.updateConference(conferenceDto);
} catch (ConferenceAlreadyExistException uaeEx) {
ModelAndView mav = new ModelAndView("updateConference","conferenceDto", conferenceDto);
mav.addObject("message", uaeEx.getMessage());
return mav;
}
ModelAndView mav = new ModelAndView("redirect:/teacher/configure"); // Problem is here
mav.addObject("message", "Successfully modified conference.");
return mav;
}
In my html I have the line:
<div th:if="${message != null}" th:align="center" class="alert alert-info" th:utext="${message}">message</div>
After updating the conference it goes back to configure.html although the message does not show. In the url I can see http://localhost:8080/teacher/configure?message=Successfully+modified+conference
I have looked at this thread although it did not help.
I tried to experiment by setting ModelAndView mav = new ModelAndView("configure") and the message displays but my conference list is empty and the url is http://localhost:8080/teacher/updateconference
Any tips is highly appreciated!
EDIT
I have tried to use RedirectAttributes as crizzis pointed out & this page and have this now:
#PostMapping("/updateConference")
public String updateConference(
#ModelAttribute("conference") #Valid ConferenceDto conferenceDto, BindingResult result, RedirectAttributes attributes) {
if(result.hasErrors()){
attributes.addFlashAttribute("org.springframework.validation.BindingResult.conferenceDto", result);
attributes.addFlashAttribute("conferenceDto", conferenceDto);
return "redirect:/teacher/updateConference";
}
try {
conferenceService.updateConference(conferenceDto);
} catch (ConferenceAlreadyExistException uaeEx) {
attributes.addFlashAttribute("conferenceDto", conferenceDto);
attributes.addFlashAttribute("message", uaeEx.getMessage());
return "redirect:/teacher/updateConference";
}
attributes.addFlashAttribute("message", "Successfully modified conference.");
return "redirect:/teacher/configure";
}
My get method:
#GetMapping(path = "/updateConference/{id}")
public String showUpdateConferenceForm(#PathVariable(name = "id") Long id, Model model){
Optional<Conference> conference = conferenceService.findById(id);
if (!model.containsAttribute("ConferenceDto")) {
model.addAttribute("conference", new ConferenceDto());
}
return "updateConference";
}
This works as intended and my message is shown on my configure.html . However, when I have an error in BindingResults the application goes to an error page and I get this in the console:
Resolved [org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'GET' not supported]
Use RedirectAttributes which has addFlashAttribute method. You can set the success or failure message like you did and access that message through the key in the redirected page as you need.
when the error occurs you are redirecting to the same method instead of this you can just render the template in case there is error. I do this way.

Spring - Is not possible using #ModelAttribute without name when the parameter type is String?

I am developing web site using the spring-boot and thymeleaf.
On the url "/copy" I added an attribute named "type" to the model and forwarding "return test/form". I submit the "type" value from the form (at this time, send the "type" value to the server using input hidden element.) The server sends the error message to the frontend if a specific key value is already stored in the DB.
The important thing is that when I first call "/copy" and go to "/test/form", the html stores the type value well. But after forwarding, in the html the value of type as null.
Here's an example of the code below.
public class TestController {
// ... some codes
#PostMapping("/copy")
public String copyAndPaste(Model model) {
try {
model.addAttribute("myObj", myObj);
model.addAttribute("type", COPY);
} catch (IOException e) {
e.printStackTrace();
}
return "test/form";
}
#PostMapping("/save")
public String save(
#ModelAttribute #Valid MyObj myObj, // it works good.
String type, // it doesn't work.
BindingResult bindingResult,
Model model) throws Exception {
if (!type.equals(EDIT) && checkCondition(myObj) {
model.addAttribute("myObj", myObj);
model.addAttribute("error", "An error occurred while registering.");
return "test/form";
}
// some logics
}
and test/form html file.
<html>
<body>
...html source
<input type="hidden" name="type" th:value="${type}"/>
<th:block layout:fragment="scripts">
<script th:inline="javascript">
var type = [[${type}]];
</script>
</th:block>
</body>
</html>
in save method, the value of a parameter - String type - is "COPY" (correct value). But in the if statement, in test/form the "type" is null value!!
So I changed my save method like this.
#PostMapping("/save")
public String save(
#ModelAttribute #Valid MyObj myObj, // it works good.
#ModelAttribute String type, // it doesn't work.
BindingResult bindingResult,
Model model) throws Exception {
if (!type.equals(EDIT) && checkCondition(myObj) {
model.addAttribute("myObj", myObj);
model.addAttribute("error", "An error occurred while registering.");
return "test/form";
}
// some logics
}
but not working, too.
Finally, I made this change.
#PostMapping("/save")
public String save(
#ModelAttribute #Valid MyObj myObj, // it works good.
#ModelAttribute("type") String type, // it works good.
BindingResult bindingResult,
Model model) throws Exception {
if (!type.equals(EDIT) && checkCondition(myObj) {
model.addAttribute("myObj", myObj);
model.addAttribute("error", "An error occurred while registering.");
return "test/form";
}
// some logics
}
Why not working this when I just attach #ModelAttribute or don't attach anything?
Why just working this when I attach #ModelAttribute with name (#ModelAttribute("type"))?
WHAT IS DIFFERENCE BETWEEN TWO?

spring mvc #ExceptionHandler method get same view

My problem is that I want to create an #ExceptionHandler method that will capture all un-handled exceptions. Once captured I would like to redirect to the current page instead of specifying a separate page just to display error.
Basically how do I get the value of someview returned by somemethod and set it dynamically in the method unhandledExceptionHandler below.
#ExceptionHandler(Exception.class)
protected ModelAndView unhandledExceptionHandler(Exception ex){
System.out.println("unhandle exception here!!!");
ModelAndView mv = new ModelAndView();
mv.setViewName("currentview");
mv.addObject("UNHANDLED_ERROR", "UNHANDLED ERROR. PLEASE CONTACT SUPPORT. "+ex.getMessage());
return mv;
}
#RequestMapping(value = "/somepage", method = RequestMethod.GET)
public String somemethod(HttpSession session) throws Exception {
String abc = null;
abc.length();
return "someview";
}
So in JSP I can render this error message back into the current page something like that.
<c:if test="${not empty UNHANDLED_ERROR}">
<div class="messageError"> ${UNHANDLED_ERROR}</div>
</c:if>
I don't think there is way to do what you are asking for, because in the exception handler method unhandledExceptionHandler there is no way to find out what the name of the view that the handler method somemethod would have returned.
The only way is for you to introduce some sort of meta data scheme so that when you end up in the exception handler you can figure out what view to map it to. But I think this meta data scheme would be fairly complex. You can implement such a scheme by finding out what was the original url being accessed when the exception was thrown, this can be done with the code snippet below.
(ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest()
Once you know what the original request URL you can redirect to it, maybe using flash attribute to store the fact that there was an exception and what the error is.
The main problem wit the metadata will occur when you have a handler method that select between different views something like.
#RequestMapping(value = "/somepage", method = RequestMethod.GET)
public String somemethod(HttpSession session) throws Exception {
String abc = null;
if(someCondition) {
abc.length();
return "someview";
} else {
// do some stuff here.
return "someOtherView";
}
}
Even knowing that somemethod was the source of the error leaves you not knowing which branch in the if statement caused the exception.
I dont think you can do this without modifying all of your handler methods. However you can try to do this in a "pretty" way:
1) You can define your own annotation which will accept target view name as a parameter (e.g. #ExceptionView)
2) Next thing to do is marking your handler methods with it, e.g.:
#ExceptionView("someview")
#RequestMapping(value = "/somepage", method = RequestMethod.GET)
public String somemethod(HttpSession session) throws Exception {
String abc = null;
abc.length();
return "someview";
}
3) After that you can do something like this in exception handler:
#ExceptionHandler(Exception.class)
protected ModelAndView unhandledExceptionHandler(Exception ex, HandlerMethod hm) {
String targetView;
if (hm != null && hm.hasMethodAnnotation(ExceptionView.class)) {
targetView = hm.getMethodAnnotation(ExceptionView.class).getValue();
} else {
targetView = "someRedirectView"; // kind of a fallback
}
ModelAndView mv = new ModelAndView();
mv.setViewName(targetView);
mv.addObject("UNHANDLED_ERROR", "UNHANDLED ERROR. PLEASE CONTACT SUPPORT. "+ex.getMessage());
return mv;
}
Rather than sending the error on a separate page, you can you just put the error in the ModelAndView object. In your case you can just put the try/catch in your controller method and return the same view like so:
#RequestMapping(value = "/somepage", method = RequestMethod.GET)
public String somemethod(ModelAndView mv,HttpSession session) throws Exception {
mv.setViewName("someview");
try{
String abc = null;
abc.length();
} catch(Exception e) {
mv.addObject("UNHANDLED_ERROR", "UNHANDLED ERROR. PLEASE CONTACT SUPPORT. "+ex.getMessage());
}
return mv;
}
So add the ModelAndView to your method and return it.
I have not tried this out, but based on the documentation here, we can get the request object in the exception handler. We may not be able to get the view linked to the URL. Getting the view from the URL, and the state/model of the view will be the tricky part.
#ExceptionHandler(Exception.class)
public ModelAndView handleError(HttpServletRequest req, Exception ex) {
logger.error("Request: " + req.getRequestURL() + " raised " + ex);
ModelAndView mav = new ModelAndView();
mav.addObject("exception", ex);
mav.addObject("url", req.getRequestURL());
mav.setViewName("error");
return mav;
}
Create a controller method annotated with #RequestMethod("/server-error")
Create a controller method annotated with #ExceptionHandler which will return "forward:/server-error";

How to pass errors in Spring Controller/Model to a view file

How do I pass errors to a view file from a Controller implemented using Spring MVC? These errors are not form errors. Just business logic errors that will be shown inside a div in the "JSP" view.
Here is the controller action I have:
#RequestMapping(method = RequestMethod.POST)
public String processLoginForm(HttpServletRequest request, LoginForm loginForm,
BindingResult result, #SuppressWarnings("rawtypes") Map model)
{
loginForm = (LoginForm) model.get("loginForm");
String gotoURL = request.getParameter("gotoURL");
if (gotoURL == null || gotoURL == "")
{
String errorMessage = "No Redirect URL Specified";
return "loginerror";//loginerror is the view file I want to pass my error to.
}
model.put("loginForm", loginForm);
return "loginsuccess";
}
Thanks,
Change your method signature :
public String processLoginForm(HttpServletRequest request, LoginForm loginForm,
BindingResult result, ModelMap model)
You can put the error message in the ModelMap and forward it to the loginerror page.
if (gotoURL == null || "".equals(gotoURL))
{
final String errorMessage = "No Redirect URL Specified";
modelMap.addAttribute("errorMessage ", errorMessage);
return "loginerror";//loginerror is the view file I want to pass my error to.
}
You can fetch that in the div using EL.
<div>${errorMessage}</div>
Your method is
public String processLoginForm(HttpServletRequest request, LoginForm
loginForm, BindingResult result, #SuppressWarnings("rawtypes") Map
model)
The method #The New Idiot explained is
public String processLoginForm(HttpServletRequest request, LoginForm
loginForm,
BindingResult result, ModelMap model)
See that the Map model is replaced with ModelMap model
If you use this method, then you can use model.addAttribute to add error messages
You can use Spring support for exception handling..
HandlerExceptionResolver or #ExceptionHandler
#adarshr
Link
Hope it will be of some use.

page cannot load, BindingResult nor plain target object for bean name ' ' available as request attribute"

"Neither BindingResult nor plain target object for bean name 'relationForm' available as request attribute"
I have some issues with the error above, I'll post the important part of the code right here.
Any idea that you guys have is appreciated
The controller.java
#RequestMapping(value = "addremoverelation/{caseId}", method = RequestMethod.POST)
public ModelAndView addRelation( #ModelAttribute("relationForm") CaseCompleteForm ccf,
#PathVariable Long caseId,HttpServletRequest request, BindingResult result) {
//my code
}
The jsp-file
<form:form action="${pageRoot}case/addremoverelation/${caseBase.id}" method="post" id="CaseCompleteForm" modelAttribute="relationForm"> <div>....</div>
You are probably not passing the "relationForm" object to the form when the page gets created. Try to add it as a model attribute when you prepare the page.
#RenderMapping
public String viewPage(Model model)
{
if(relForm == null)
{
RelationForm relForm = new RelationForm();
model.addAttribute("relationForm", relForm);
}
return "your/view/jsp/file";
}
Hope you know what I mean. If you experience any further problems respond in comments, please.

Categories