Problems getting spring:bind status.errorMessage to populate - java

I've got a Groovy project using Spring framework and its validators doing sanity-checking on my forms' input values. I would like to have Spring populate error messages next to my input form fields via the built-in ${status.errorMessage}; however, I can only get it to populate "errorMessages" in my Model object (from the controller.) So let's take a look at some code.
login.jsp:
<form method="post" action="<c:url value="/login" />">
<spring:bind path="request.username">
<label for="username"><fmt:message key="login.username"/>:
<input type="text" id="username" size="20" maxlength="50" name="username" value="${request.username}"/>
</label>
<%-- This part does NOT display the validation errors. --%>
<c:if test="${status.error}"><span class="error">${status.errorMessage}</span></c:if>
</spring:bind>
<spring:bind path="request.password">
<label for="password"><fmt:message key="login.password"/>:
<input type="password" id="password" size="20" maxlength="30" name="password" />
</label>
<%-- This part does NOT display the validation errors. --%>
<c:if test="${status.error}"><span class="error">${status.errorMessage}</span></c:if>
</spring:bind>
<input id="login" type="submit" value="Login"/>
</form>
<%-- This part does display the validation errors. --%>
<c:if test="${ec > 0}">
<p>
<c:forEach items="${errorCodes}" var="error">
<span class="error"><fmt:message key="${error.defaultMessage}"/></span><br/>
</c:forEach>
</p>
</c:if>
LoginController.groovy:
#RequestMapping(method = RequestMethod.GET, value = '/')
ModelAndView defaultView() {
ModelMap model = new ModelMap()
model.addAttribute('request', new LoginRequest())
new ModelAndView('login', model)
}
#RequestMapping(method = RequestMethod.POST, value = '/login')
ModelAndView login(
LoginRequest loginRequest, HttpServletResponse response,
HttpSession session, BindingResult br, ModelMap model
) {
validator.validate(loginRequest, br)
if (br.hasErrors()) {
model.addAttribute('request', loginRequest)
return returnWithError(br, model, 'login')
}
...
}
private ModelAndView returnWithError(BindingResult br, ModelMap model, String redirectTo) {
br.allErrors.each {error ->
log.error(error.toString())
}
def objectErrors = br.allErrors.findAll {e -> e instanceof ObjectError}
model.addAttribute('ec', br.errorCount)
model.addAttribute('errorCodes', objectErrors)
new ModelAndView(redirectTo, model)
}
LoginRequestValidator.groovy:
#Override
void validate(Object o, Errors errors) {
ValidationUtils.rejectIfEmpty(errors, 'username', 'username.empty', 'username.empty')
ValidationUtils.rejectIfEmpty(errors, 'password', 'password.empty', 'password.empty')
}
Which part of the Spring Magic [TM] am I missing?

I think your BindingResult object should be the argument immediately following your LoginRequest object. See http://static.springsource.org/spring/docs/3.0.x/reference/mvc.html#mvc-ann-requestmapping and specifically Example 15.1. Invalid ordering of BindingResult and #ModelAttribute

Review the Javadoc on #RequestMapping as I find it the best on the subject:
http://static.springsource.org/spring/docs/3.0.x/api/org/springframework/web/bind/annotation/RequestMapping.html
Try the following:
ModelAndView login(
#Valid LoginRequest loginRequest, HttpServletResponse response,
HttpSession session, BindingResult br, ModelMap model
) {}
See What does the #Valid annotation indicate in Spring?
If that doesn't work you should try translating some of your code to plain Java and running the debugger to see whats happening (I know its not an answer but just an idea).

Maybe you will find these post from kgiannakakis very usefull.
It explains the purpose of these functionnality and how to deal with is explained in Spring 3.1 documentation.
Adding something like that will probably do the trick, isn't it ?
#Controller
public class MyController {
#InitBinder
protected void initBinder(WebDataBinder binder) {
binder.setValidator(new FooValidator());
}
#RequestMapping("/foo", method=RequestMethod.POST)
public void processFoo(#Valid Foo foo) { ... }
}
Take a look at SpringSource blog and especially these post about new features around data binding. It could add some new look about your validation process.

Related

Method Not Allowed, status=405, HTML form using Thymeleaf

I made a Form using Thymeleaf but running into this issue.
I read many articles but didn't find any solution.
Any solution you can suggest ?
Project Controller ->
#Controller
public class Controllers {
#GetMapping("/home")
public ModelAndView home(){
System.out.println("User is in Homepage");
return new ModelAndView("index");
}
#GetMapping("/service")
public ModelAndView service(){
System.out.println("User is in Service Page");
return new ModelAndView("service");
}
#GetMapping("/about")
public ModelAndView about(){
System.out.println("User is in About page");
return new ModelAndView("about");
}
Here's Controller Class for submitting form ->
#Controller
public class SavingUser{
#Autowired
private UserRepository userRepository;
#PostMapping("/registerUser")
public ModelAndView user(#ModelAttribute Customer customer, ModelMap model){
System.out.println("User in registration page..");
userRepository.save(customer);
model.addAttribute("saveUser", customer);
return new ModelAndView("index");
}
}
And here's my HTML Form -
<div id="form">
<form action="registerUser" th:action="#{/registerUser}" th:object="${saveUser}" method="POST">
<br />
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
<label for="name">Your Name:</label><br />
<input type="text" th:field="*{name}" placeholder="" /><br />
<label for="suburb">Your Suburb</label><br />
<input type="text" th:field="*{suburb}" placeholder="" /><br />
<input class="submit" type="submit" value="Submit" />
<br /><br />
</div>
</form>
</div>
I tried to remove action="", still it didn't work.
Well, from what I see in the code you miss the #GetMapping method in your controller to actually display the page. Not very clear in what step you get 405 status. It would be useful if you also add the relevant exception message from the console.
Edit:
to answer the original question, you get 405 because in the Post Controller you make a POST request to "/services" which dosen't exists (only the GET exists for services).
#PostMapping("/registerUser")
public ModelAndView user(#Valid #ModelAttribute Customer customer, BindingResult result, ModelMap model){
[...]
return new ModelAndView("service"); // this makes a POST to "service" endpoint!
}
To correct that, you must make a redirect to the page like this:
#PostMapping("/registerUser")
public ModelAndView user(#Valid #ModelAttribute Customer customer, BindingResult result, ModelMap model){
[...]
return new ModelAndView("redirect:/service"); // this makes a GET to "service" endpoint
}
Leaving that aside, there are many things that can be imporoved. First of all, you are not using Thymeleaf in your project. No Thymeleaf markup will be processed. To use it you must add first the dependency, then configure Thymeleaf as your HTML resolver. The proper way to do all that is detailed here.
Also, I really recommend reading Thymeleaf documentation and follow a few tutorials to understand how things work.

How to get inputs from thymeleaf without Ambigous Handler error?

I am trying to get a value from thymeleaf input into my java class.
Simple script from thymeleaf
<h1>Form</h1>
<form action="#" th:action="#{/index}" th:object="${emails}" method="post">
<p>Enter Emails: <input type="text" th:field="*{email}" /></p>
<p><input type="submit" value="Submit" /> <input type="reset" value="Reset" /></p>
</form>
How would I be able to retrieve emails into my java class?
Controller
#Controller
#RequestMapping(method = RequestMethod.GET)
public class IndexController {
#RequestMapping(value = "/index", method = RequestMethod.GET)
public ModelAndView getdata() throws IOException {
ModelAndView model = new ModelAndView("index");
model.addObject("emails", new MailModel());
return model;
}
#PostMapping("/index")
public String emailSubmit(#ModelAttribute MailModel emails) {
System.out.println(emails.getEmail());
return "index";
}
Error Message
Ambiguous handler methods mapped for HTTP path 'http://localhost:8080/': {public org.springframework.web.servlet.ModelAndView com.spring.web.controller.IndexController.getdata() throws java.io.IOException, public java.lang.String com.spring.web.controller.IndexController.emailSumbit(com.spring.web.model.MailModel)}
My Application is created with Springboot, Java, and Thymeleaf. What am I doing wrong? Is it possible that ModelandView does not work with PostMapping? I also followed https://spring.io/guides/gs/handling-form-submission/ and I got that sample working, but when I tried to follow the logic and implement into my project. It did not work.
Before declaring your controller, you are setting the RequestMethod to GET everywhere. On the methods you are setting them again, which is ambigous.
Remove the #RequestMapping(method = RequestMethod.GET) in line 2. This should fix the mentioned problem.

how to show validation error in jsp in spring mvc

I want to show validation error in my jsp page.
My object is:
public class MyObjectDTO{ #valid private TextDTO text1; #valid private TextDTO text2 }
public class TextDTO{ #NotBlank private String code;#NotBlank private String label;}
My controller:
#RequestMapping(value = "/create", method = RequestMethod.POST)
public String creationProjet(#Valid #ModelAttribute MyObjectDTO obj, BindingResult result,
Model model) {
if (result.hasErrors()) {
model.addAttribute("hasErrors", true);
return "create";
} else {
....
return "redirect:/list";
}
}
my jsp
<div class="col-md-6 form-group ${requestScope['org.springframework.validation.BindingResult.obj'].hasFieldErrors('text1') ? 'has-error' : ''}">
<label class="col-lg-3 control-label">my label</label>
<div class="col-lg-5">
<form:select class="form-control" name="type" path="text1.code" id="selectType">
<option value="">---------</option>
<c:forEach items="${types }" var="type">
<form:option value="${type.id }">
<c:out value=" ${type.code}"></c:out>
</form:option>
</c:forEach>
</form:select>
<form:errors path="text1.code" class="has-error error"></form:errors>
</div>
</div>
My controller redirects to the page create but the errors are not showing. In debug mode there is one error that indicates text1.code cannot be a blank.
In your Jsp page add following line
<div class="col-md-6 form-group ${requestScope['org.springframework.validation.BindingResult.obj'].hasFieldErrors('text1') ? 'hasErrors' : ''}">
or use hasFieldErrors() instead
<div class="col-md-6 form-group ${requestScope['org.springframework.validation.BindingResult.obj'].hasFieldErrors()}">
And About addAttributes("hasErrors",true), use addFlashAttribute() which is store in flashmap and Object (In your case Error Message will be alive when you navigate to create page or redirect between two controller.) Look at this for more
In your controller Add RedirectAttributes Object like this
#RequestMapping(value = "/create", method = RequestMethod.POST)
public String creationProjet(#Valid #ModelAttribute MyObjectDTO obj,
BindingResult result,
RedirectAttributes redirectAtt,
Model model) {
if (result.hasErrors()) {
redirectAtt.addFlashAttribute("hasErrors","ur message");//instead of true you can write your own message
return "create";
} else {
....
}
return "redirect:/list";
}
}
If you write your own message instead of true
<c:if test="${not empty hasErrors">
<p>${hasErrors}</p>
</c:if>
only you missing is put part to model map.You are putting only flag. But you need to put result.
if(result.hasErrors()){
mm.addAttribute("errors", result);
return "create";
}

Unable to Access Request Parameter in SpringMVC Controller while redirecting to URL

I am having a form which submits new articles to my controller.
JSP Page:
<form class="form-signin" method="post"
action="/articleViewer">
<div class="control-group" style="margin-top: -5px;">
<label class="control-label text-info" for="commentContent"><strong>Post
Comment</strong></label>
<div class="controls">
<textarea class="FormElement" name="area2" id="commentContent"
style="width: 100%;"></textarea>
</div>
</div>
<button type="submit" class="btn btn-primary" style="margin-left: 90%;">Post</button>
</form>
Controller Method:
#RequestMapping(value="/articleViewer", method = RequestMethod.POST)
public String saveArticleComment (HttpServletRequest request, HttpServletResponse response,Principal principal, ModelMap map) throws ServletException, IOException{
//processing request
System.out.println("Link : "+Path.Jsp.ARTICLE_VIEWER_PAGE);
return Path.Jsp.ARTICLE_VIEWER_PAGE; //this ARTICLE_VIEWER_PAGE = /articleViewer
}
Now from the above controller method I wanna redirect to another method where I want to pass
currently saved article id as http://<myurl>/article?articleId="xyz".
Here is my get method code for handling the redirect.
#RequestMapping(value="/articleViewer", method= RequestMethod.GET)
public String articleViewer(HttpServletRequest request, Principal principal,
ModelMap map, HttpServletResponse response)
throws DatabaseException {
//I wanna access article id here.
return Path.Jsp.ARTICLE_VIEWER_PAGE;
}
I wanna know how could I access that passed request parameter in above method?
If you submit the action url without parameter, or use hidden field for this purpose then you should return that parameter back as a result. So, you don't get it lost, or redirect to the page where the parameter is not needed anymore. To pass parameter in the url use
<form class="form-signin" method="post" action="/articleViewer?varArticleID=94">
I Resolved it by using Redirect attribute in return ...
return "redirect:/articleViewer?varArticleID="+getVarArticleID();
So if you need the page like as after submitting change the action value of form like
<form class="form-signin" method="post"
action="/articleViewer?varArticleID={yourArticleid}">
Also after submit you need
return Path.Jsp.ARTICLE_VIEWER_PAGE+"?varArticleID="+request.getParameter("varArticleID");

How do I pass ModelAttribute between Controllers?

I'm trying to create a Home page with 2 functionalities:
Login
Sign Up
I'm mapping each request to a different controller and trying to get the result back to my home.jsp. But I'm having trouble passing only certain specific ModelAttribute around, between Controllers. More specifically, i cant get the changed I make to the ModelMap and BindingResult in one controller to be reflected in others.
I'm sure there's something basically wrong with what I'm doing. Please help.
There are 2 forms in my home.jsp. One for Login:
<form:form name="loginForm" modelAttribute="loginUser" action="login" method="post">
Email: <form:input name="loginEmail" id="loginEmail" value="" path="email"/>
<form:errors path="email" cssClass="error" />
<br/>
password: <form:password name="loginPassword" Id="loginPassword" value="" path="password" />
<form:errors path="password" />
<br/>
<input type="submit" id="id_login" value="Login">
</form:form>
and the other one for Sign Up:
<form:form name="SignUpForm" modelAttribute="signUpUser" action="signup" method="post">
Full Name: <form:input name="name" id="name" value="" path="name"/>
<form:errors path="name" cssClass="error" />
<br/>
Email: <form:input name="signupEmail" id="signupEmail" value="" path="email"/>
<form:errors path="email" cssClass="error" />
<br/>
password: <form:password name="signUpPassword" Id="signUpPassword" value="" path="password" />
<form:errors path="password" />
<input type="submit" id="id_signUp" value="Sign Up">
</form:form>
I have 3 controllers: HomeController.java:
#RequestMapping("/home")
public String showHome(ModelMap model,
#ModelAttribute("loginUser") User loginUser,
BindingResult loginResult,
#ModelAttribute("signUpUser") User signUpUser,
BindingResult signUpResult) {
return "home";
}
AuthenticationController.java:
#RequestMapping("/login")
public String login(#ModelAttribute("loginUser") User user,
BindingResult result, ModelMap model, HttpServletRequest request,
HttpServletResponse response) {
loginFormValidator.validate(user, result);
if(Errors in `result`)
return "forward:/home";
// Authentication Logic
request.getSession().setAttribute("s_user_obj", some_variable);
return "forward:/home";
}
and ProfileController.java:
#RequestMapping("/signup")
public String signUpUser(#ModelAttribute("signUpUser") User user,
BindingResult result, ModelMap model) {
// Do stuff related to Sign Up
// Forward to home.jsp
}
When the request is forwarded to /home, I'm getting same values in both loginUser and signUpUser. Worse, there are no errors i.e. the result variable is not reflecting the ones in the previous controllers.
I'm sure there's a better way of doing this, this is a newbie's attempt at it. Please advice.
The issue that I see here is the way #ModelAttribute("loginUser") and #ModelAttribute("signUpUser") is interpreted for methods by Spring - here is some reference - http://static.springsource.org/spring/docs/3.1.x/spring-framework-reference/html/mvc.html#mvc-ann-modelattrib-method-args
The arguments are retrieved from the model(and instantiated if necessary), but the problem part is that they are repopulated based on the request parameters, which would end up populating both your attributes the same way. The only workaround that I can think of is to explicitly retrieve the model attributes yourself in your method:
#RequestMapping("/home")
public String showHome(ModelMap model) {
model.get("loginUser");...
model.get("signUpUser");...
return "home";
}
Update
Based on your comments, let me recommend an alternate flow and a more explicit flow:
Have a method which sets both your loginUser and signUpUser model attributes this way:
private void populateAttributes(Model model, User loginUser, User signupUser){
if (loginUser==null) loginUser = new User();
if (singupUser==null) singupUser = new User();
model.addAttribute("loginUser", loginUser);
model.addAttribute("signupUser", signupUser);
}
Now in your login flow:
#RequestMapping("/login")
public String login(User user,
BindingResult result, Model model, HttpServletRequest request,
HttpServletResponse response) {
loginFormValidator.validate(user, result);
if(Errors in `result`)
populateAttributes(user, null);
return "home";
// Authentication Logic
request.getSession().setAttribute("s_user_obj", some_variable);
populateAttributes(model, user, null);
return "home";
}
Similar flow in your signup:
#RequestMapping("/signup")
public String signUpUser(User user,
BindingResult result, Model model) {
populateAttributes(model, null, user);
return "home"
}
and showHome:
#RequestMapping("/home")
public String showHome(Model model,) {
populateAttributes(model, null, null);
return "home";
}

Categories

Resources