How do I pass ModelAttribute between Controllers? - java

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";
}

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.

Thymeleaf Neither BindingResult nor plain target object for bean name 'person' available as request attribute

From what I can tell this is set up correctly but I am getting the following error:
java.lang.IllegalStateException: Neither BindingResult nor plain target
object for bean name 'person' available as request attribute
Form
<form action="#" th:action="#{/person}" th:object="${person}" method="post" th:required="required">
<input type="text" th:field="*{subject}" class="contact col-md-6" placeholder="Name *" th:required="required"/>
<input type="text" th:field="*{name}" class="contact col-md-6" placeholder="Name *" th:required="required"/>
<input type="text" th:field="*{lastName}" class="contact col-md-6" placeholder="Name *" th:required="required"/>
<input type="email" th:field="*{email}" class="contact noMarr col-md-6" placeholder="E-mail address *" th:required="required"/>
<textarea name="comment" class="contact col-md-12" th:field="*{message}" placeholder="Message *"></textarea>
<input type="submit" id="submit" class="contact submit" value="Send message"/>
</form>
Person.java
public class Person {
private int id;
private String name;
private String lastName;
private String email;
private String subject;
private String message;
....
}
Controller
#Controller
public class ApplicationController {
#RequestMapping(value = "/", method = RequestMethod.GET)
public String indexPage() {
return "index";
}
#RequestMapping(value="/person", method=RequestMethod.GET)
public String contactForm(Model model) {
model.addAttribute("person", new Person());
return "index";
}
#RequestMapping(value="/person", method=RequestMethod.POST)
public String contactSubmit(#ModelAttribute Person person, Model model) {
model.addAttribute("person", person);
return "result";
}
}
I looked at Spring-boot and Thmeleaf setup and it looks like my setup is identical.
--------------------- Update 1 -----------------------
I have changed my post method to include BindingResult with no success.
#RequestMapping(value="/person", method=RequestMethod.POST)
public String contactSubmit(#Valid #ModelAttribute Person person, BindingResult bindingResult, Model model) {
if(bindingResult.hasErrors()){
System.out.println("There was a error "+bindingResult);
System.out.println("Person is: "+ person.getEmail());
return "index";
}
model.addAttribute("person", person);
return "result";
}
You forgot to add BindingResult after your #ModelAttribute :
#RequestMapping(value="/person", method=RequestMethod.POST)
public String contactSubmit(#ModelAttribute Person person, BindingResult bindingResult, Model model) {
if (bindingResult.hasErrors()) {
//errors processing
}
model.addAttribute("person", person);
return "result";
}
I'm already have answered to question like this :
html form validation using thymeleaf not working spring boot
Model attribute has to be initialized (using GET method) before calling post method.
In your case you need one more method in controller which does model.addAttribute("person",new Person()); and it has to be called before post.
Refer below link:
https://spring.io/guides/gs/handling-form-submission/
OR
http://forum.thymeleaf.org/Neither-BindingResult-nor-plain-target-object-for-bean-name-miniDoniie-available-as-request-attribute-td4027859.html
It has GetMapping as well as PostMapping in controller.
You need declare a #ModelAttribute for "person". Something like:
#ModelAttribute("Myperson")
public Person newPerson() {
return new Person();
}
Then, declare you #PostMapping and set in #ModelAttribute the name "person".
#RequestMapping(value="/person", method=RequestMethod.POST)
public String contactSubmit(#ModelAttribute Person person, Model model) {
model.addAttribute("person", person);
return "result";
}
I hope it works for whoever reads this :)
First I had the form in index.html
#RequestMapping(value = "/", method = RequestMethod.GET)
public String indexPage(){
return "index";
}
So when my form:
<form th:action="#{/person}" th:object="${person}" method="post" >
<input type="text" th:field="*{subject}" class="contact col-md-6" placeholder="Subject *" />
<input type="text" th:field="*{name}" class="contact col-md-6" placeholder="Name *" />
<input type="text" th:field="*{lastName}" class="contact col-md-6" placeholder="Last Name *" />
<input type="email" th:field="*{email}" class="contact noMarr col-md-6" placeholder="E-mail address *" />
<textarea name="comment" class="contact col-md-12" th:field="*{message}" placeholder="Message *" ></textarea>
<input type="submit" id="submit" class="contact submit" value="Submit" />
<input type="reset" value="Reset" />
</form>
Was looking for / it was hitting the above method, NOT:
#RequestMapping(value="/", method=RequestMethod.GET)
public String contactForm(#Valid #ModelAttribute("person") Person person, BindingResult bindingResult,
HttpServletRequest request, Model model) throws IOException {
if(bindingResult.hasErrors()){
System.out.println("There was a error "+bindingResult);
return "index";
}
model.addAttribute("person", new Person());
return "index";
}
Which was correct!
I had to remove the first method and it worked.

How to fix erasing fields when navigating between forms?

I can't figure out why my field values of User class is disappearing when I'm trying to navigate between forms.
#Controller
public class WizardController {
#RequestMapping(value = "/", method = RequestMethod.GET)
public String userForm(#ModelAttribute("user") User user) {
return "form/userForm";
}
#RequestMapping(value = "/user", method = RequestMethod.POST)
public String processedForm(#ModelAttribute("user") User user, BindingResult result,
ModelMap model) {
if (result.hasErrors()) {
return "form/userForm";
}
ArrayList<String> p = new ArrayList<String>();
p.add("add_user");
p.add("delete_user");
model.addAttribute("permissions", p);
model.addAttribute("user", user);
return "form/permissionForm";
}
#RequestMapping(value = "/show", method = RequestMethod.POST)
public String show(#ModelAttribute("user") User user, BindingResult result) {
if (result.hasErrors()) {
System.out.println("Error");
return "show";
} else {
System.out.println(user);
return "show";
}
}
}
My permissionForm.jsp
<form:form action="/show" method="post" modelAttribute="user">
<form:errors path="*" cssClass="errorblock" element="div"/>
<p>
<form:select path="permissions">
<form:options items="${permissions}"/>
</form:select>
</p>
<p><input type="submit" value="Add"/></p>
</form:form>
userForm.jsp
<form:form action="/user" method="post" modelAttribute="user">
<form:errors path="*" cssClass="errorblock" element="div"/>
<p><form:input path="name" placeholder="Name"/></p>
<p><form:input path="age" placeholder="Age"/></p>
<p><form:input path="email" placeholder="Email"/></p>
<p><form:input path="password" placeholder="Password"/></p>
<p><input type="submit"></p>
</form:form>
On show.jsp I'm rendering my user
${user}
And POJO class
public class User implements Serializable {
private String name;
private String email;
private String password;
private Integer age;
private List<String> permissions;
// getters, setters, toString
}
Output: User{name='null', email='null', password='null', age=null, permissions=[delete_user]}. And I need a full object, somthing like this:
User{name='John', email='john#gmail.com', password='12345', age=21, permissions=[delete_user]}.
Thanks for any help.
Because in permissionForm.jsp, the form has inputs for the permissions field only, hence when submiting it, it will pass on a User object with the permissions field only populated, the rest will be null.
If you need to have values in the rest of the fields, add hidden inputs like this:
<form:form action="/show" method="post" modelAttribute="user">
<form:errors path="*" cssClass="errorblock" element="div"/>
<p>
<form:select path="permissions">
<form:options items="${permissions}"/>
</form:select>
<form:hidden path="name"/>
<form:hidden path="age"/>
<form:hidden path="email"/>
</p>
<p><input type="submit" value="Add"/></p>
</form:form>
However, for the password field, I wouldn't advise adding a hidden field in the JSP because it will expose the password for anyone who can read html source.
Another and better solution would be using the session, by storing and modifying the user in the session between forms, first store the user on the first submit:
#RequestMapping(value = "/user", method = RequestMethod.POST)
public String processedForm(#ModelAttribute("user") User user, BindingResult result,
ModelMap model,HttpSession session) {
if (result.hasErrors()) {
return "form/userForm";
}
session.setAttribute("user",user);
ArrayList<String> p = new ArrayList<String>();
p.add("add_user");
p.add("delete_user");
model.addAttribute("permissions", p);
model.addAttribute("user", user);
return "form/permissionForm";
}
#RequestMapping(value = "/show", method = RequestMethod.POST)
public String show(#ModelAttribute("user") User user, BindingResult result,HttpSession session) {
User userInSession = (User)session.getAttribute("user");
userInSession.setPermissions(user.getPermissions());
if (result.hasErrors()) {
System.out.println("Error");
return "show";
} else {
System.out.println(userInSession);
return "show";
}
}

how to show fetched values from database in spring <form:input>?

I need to show the fetched values from database which are stored in an arraylist using spring form:input tag. However i found that the 'value' attribute isn't supported. Please help!
I guess you are expecting something like this.
//Assumes you have the following in your class
public class Students{
private String name;
private List<String> Departments;
/* getters/setters */
}
In the HTML would be.
<form:input path="departments[0]" />
<form:input path="departments[1]" />
For more details about click http://www.javacodegeeks.com/2013/07/spring-mvc-form-handling-vol-5-select-option-options-tags.html
Please first retrieve the list from the datebase and set the list on the model attribute in the controller see the example set the
#Controller
public class UserController {
#RequestMapping(method = RequestMethod.GET)
public String userHome(Model model, EventBean event, UserService userService,ImageBean image)
{
List<Event> events = userService.viewNews(); //retrieve the list from datebase
model.addAttribute("event", event); //add bean object
model.addAttribute("events", events); //add list in model attribute
return "home";
}
}
your jsp page
<form:form modelAttribute="event"> <!--set the model attribute here -->
<form:input path="news" value="${events.get(0).news}" />
</form:form>
This is my code,please have a look and say what i might be doing wrong,
JAVA
public ModelAndView userEditProfile(#ModelAttribute("userDetails") UserFormbean registration,BindingResult result,HttpServletRequest request){
ModelAndView mav=null;
HttpSession httpSession=null;
List userProfileList=new ArrayList();
httpSession=request.getSession();
if (httpSession != null) {
UserFormbean formbean=(UserFormbean)httpSession.getAttribute("UserRegistrationFormBean");
userProfileList= userRegistrationService.getUserProfileInfo(formbean);
mav=new ModelAndView("EditProfile");
mav.addObject("userProfileInfoList", userProfileList);
}
return mav;
}
JSP::
-----
<c:if test="${not empty userProfileInfoList}">
<c:forEach var="temp" items="${userProfileInfoList}">
<div>
<form:label path="userRegistration.email"><spring:message code="label.email"/></form:label>
<form:input path ="userRegistration.email" value="${temp.get(0).UserRegistration.email}"/>
<form:errors path="userRegistration.email"/>
</div>
<div>
<form:label path="userRegistration.firstName"><spring:message code="label.firstname"/></form:label>
<form:input path ="userRegistration.firstName" value="${temp.get(0).UserRegistration.firstName}"/>
<form:errors path="userRegistration.firstName"/>
</div>
<div>
<form:label path="userRegistration.lastName"><spring:message code="label.lastname"/></form:label>
<form:input path ="userRegistration.lastName" value="${temp.get(0).UserRegistration.lastName}"/>
<form:errors path="userRegistration.lastName"/>
</div>
</c:forEach>
</c:if>

Problems getting spring:bind status.errorMessage to populate

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.

Categories

Resources