I have a register.jsp page where I have bind the following data to a modelAttribute userform.
<form:form method="post" modelAttribute="userform"
enctype="multipart/form-data" autocomplete="off" class="form">
<spring:bind path="firstName">
<label>First Name: </label>
<form:input path="firstName" placeholder="First Name" />
</spring:bind>
<spring:bind path="lastName">
<label class="control-label">Last Name: </label>
<form:input path="lastName" placeholder="Last Name" />
</spring:bind>
</form:form>
where get and post methods on the controller are:
#RequestMapping(value = "/register", method = RequestMethod.GET)
public String register(#ModelAttribute("userform") Employee employee, Model model) {
List<Role> roleList = roleService.getAllList();
model.addAttribute("roleList", roleList);
model.addAttribute("userform", employee);
return "employee/register";
}
#RequestMapping(value = { "/register" }, method = RequestMethod.POST)
public String processRegistration(#Valid #ModelAttribute("userform") Employee employee, BindingResult result,
Model model, HttpServletRequest request) throws IllegalStateException, IOException {
//(expecting the data from jsp) nothing in the employeee object :(
//dosomething
return "employee/register";
}
Although I have used same name userform and the attributes name on the entity Employee is exactly the same, I am unable to get the form data from JSP to the Controller. I must be doing something wrong here but could not find it. Any help would be appreciated.
I figured out the answer myself.
Since I have used enctype="multipart/form-data"in my JSP form, it needed a configuration of bean called "CommonsMultipartResolver" in the servlet-context.xml. The bean as a whole can be written as
<beans:bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver" />
In addition to #Ralph answer I want to Explain why you need to remove below lines from your jsp
<spring:bind path="firstName">
<spring:bind path="lastName">
As Per Answer given on this link Difference between spring:bind and form:form
Its Unnecessary to use <spring:bind> when you use <form:form> because both does same thing with respect to model attributes.
here <form:form> also generates HTML form markup, whereas with <spring:bind> you need to write markup yourself.
Also you should know how sping handles binding of Domain Object, As #Ralph mention in his answer You need to use form backing object and modify you method like below
#RequestMapping(value = "/register", method = RequestMethod.GET)
public String register(Employee employee, Model model) {
}
Q- What is happening in above code?
A- When Spring MVC finds out domain object is present as a method argument an instance is automatically introduced by Spring MVC,with respect to domain objects this instance is same as instance created by new keyword like below
Employee emp=new Employee();
The properties of Employee domain object are normally uninitialized accept any parameters with same names as Employee object properties available in the URL query String. Spring MVC uses Java reflection to dinf out names of properties of the domain object.
For more information visit Binding and Validation of Handler Parameters in Spring MVC Controllers
Related
My setup is as follows:
I have a form and a table on the same page: localhost:8080/persons?firstname=frank
This shows all persons with firstname accordingly.
From the same page, it is possible to submit a form for creating new persons:
<form action="/persons" method="POST">
<input type="hidden" name="_method" value="POST"/>
<input type="text" name="firstname"/>
<input type="text" name="lastname"/>
<input type="submit" value="Create"/>
</form>
Problem: when I actually POST the form content to the Spring #Controller, the following content gets send:
URL: /persons?firstname=frank
Request Body: _method=post&firstname=&lastname=doe
Spring then magically merges GET-query params and POST form params into one DTO, and results in a PersonDto that has both firstname + lastname set
#PostMapping("/persons")
public void addPerson(PersonDto p) {
//p.firstname => Frank, p.lastname = Doe
}
PersonDto {
public String firstname, lastname, age;
}
Somehow Spring derives the request body values also from the get-query url parameters. Why??
To my knowledge, just like you can map the request body to an Object, you can also do it with query parameters.
The reason might be the following:
In Spring MVC, "request parameters" map to query parameters, form
data, and parts in multipart requests. This is because the Servlet API
combines query parameters and form data into a single map called
"parameters", and that includes automatic parsing of the request body.
Reference documentation: https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/RequestParam.html
Since the parameters are merged into a single map, you can get all of them in a single Object.
I am currently sending a logged in User object to view which will be injected into a form via th:object. I want to update certain attributes in this User object but still maintain the rest of the object's content. However, when I submit this form, the User object contains null values for all values except the one I have set in the thymeleaf page. I know one solution would be to add hidden tags for the values I want to keep but that seems very tedious if the User object is huge.
#RequestMapping(value="/newprofile", method=RequestMethod.GET)
public String newProfile(Model model, Principal principal) {
String email = principal.getName();
User user = userService.findUserByEmail(email);
model.addAttribute("user", user);
return "newprofile";
}
#RequestMapping(value="/newprofile", method=RequestMethod.POST)
public String registerNewProfile(Model model,User user, Principal principal) {
userService.saveProfile(user); //this user object will contain null values
return "redirect:/profile";
}
Here is how the form looks. The user object that comes in is an existing User with its values already set. There are member variables that can be updated.
<form autocomplete="off" action="#" th:action="#{/newprofile}" th:object="${user}" method="post" class="form-signin" role="form">
<h3 class="form-signin-heading">Registration Form</h3>
<div class="form-group">
<div class="">
<input type="text" th:field="*{profile.basicInfo.age}" placeholder="Name" class="form-control" />
</div>
</div>
<div class="form-group">
<div class="">
<button type="submit" class="btn btn-primary btn-block">Update profile</button>
</div>
</div>
</form>
Once the form is submitted, I perform a save of that User object via Spring JPA's save() method. However, if the User object contains nulls, it will incorrectly "update" those values to null. Again, I can do some checking to validate which members should be updated and which should not but that seems incorrect...
#Override
public User saveProfile(User user) {
// TODO Auto-generated method stub
userRepository.save(user);
return user;
}
We can use the bean validation API annotations on model class attributes like below:
#NotNull(message = "Name cannot not be null")
private String name;
Similar use of #NotEmpty, #NotBlank
By this, it won't enter if any of the validation fails.
If copying the existing User bean before update is an option, following api may be used.
BeanUtils.copyProperties
Please do note that there are two popular BeanUtils.copyProperties. One from Apache and the other one mentioned with this post. The order of method parameters for both these api are different.
I can't figure out how to do a simple, 1-field validation where I check the value against a service (or really any other logic check).
Most of the form validation I see uses a class to hold form data using javax.validation and marking up with attributes like #NotNull, #Min(10), etc. And then checking a BindingResult.hasErrors(). Like here: https://spring.io/guides/gs/validating-form-input/
I'm trying to do this for a single field and I want to validate against what a service will return to me rather than one of those validation attributes.
What do I put in my POST handler in the controller to get this going?
This is what I have in my controller to manage the result from the form
#RequestMapping(value = "/myController", method=RequestMethod.POST)
public String checkFieldVal(#RequestParam String valFromForm){
if(!myService.isThisValueGood(valFromForm)) {
//Show the user their value is bad
}
//return to some other page
}
And this is in my JSP (something simple like this):
<form id="form" method="POST">
<label for="valueToCheck">What's your value:</label>
<input type="text" id="valueToCheck" name="valueToCheck"></input>
<input type="submit" value="Submit">
</form >
Try to replace this:
<form id="form" method="POST">
With adding the action like this:
<form action="myController" id="form" method="POST">
And replace this:
#RequestMapping(value = "/myController", method=RequestMethod.POST)
public String checkFieldVal(#RequestParam String valFromForm){
With adding the right name:
#RequestMapping(value = "/myController", method=RequestMethod.POST)
public String checkFieldVal(#RequestParam("valueToCheck") String valueToCheck){
The param name must match in both controller and html.
#RequestParam String valFromForm
<input ... name="valueToCheck">
Option 1: Leaving jsp as it is, you change controller code as,
#RequestParam String valueToCheck
Or you may add qualifier to parameter name as,
#RequestParam("valueToCheck") String valFromForm
Option 2: Leaving controller as it is, you change jsp as,
<input ... name="valFromForm">
I have been looking for this answer here and in google without any success. I will explain what I´m looking for and let´s see if someone can help me.
Using Spring MCV I´m rendering a List of A objects "aList" into my view.JSP.
#RequestMapping(method = RequestMethod.GET)
public final ModelAndView getAList(){
ModelAndView mav = new ModelAndView("view");
List<A> aList = new ArrayList<>();
aList.add(new A("a1");
aList.add(new A("a2");
mav.addObject("aList", aList);
return mav;
}
Where A looks like
public class A{
#Getter
#Setter
private String value;
public A(String value){
this.value=value;
}
}
For every iteration of the list I´m creating a form. The form looks like
<c:forEach var="a" items="${aList}"
varStatus="status">
<form:form id="A${a.id}"
method="post" action="save.do"
modelAttribute="a">
<input type="submit" value="Save"/>
</form:form>
</c:forEach>
so after finish the render I have so many forms as objects in my list.
Every form as you can imagine has his submit button.
Now what I´m trying to do without any success is to send one of this form to my controller, but not the whole list of A that I render but A. So my controller will look something like.
RequestMapping(value = "/save", method = RequestMethod.POST)
public final ModelAndView save(#ModelAttribute("a") A a) {
But I´m receiving a "IllegalStateException: Neither BindingResult nor plain target object for bean name" because the render name of my modelAndView "aList" is not the same than "a".
What I think I understand is that Spring MVC components like form dont allow submit a different class that was used in the render. Even if is a nested class of the render class, what is too bad.
I would like to avoid have to send the aList again.
Any idea?
Regards.
Instead of attempting to post back the entire contents of the selected element,
consider posting back an identifier for the selected element.
For example:
<form:form method="post" action="save.do">
<c:forEach items="${aList}"
var="element"
varStatus="status">
<input type="submit"
name="selectedElementId"
value="${element.id}"/>
</c:forEach>
</form:form>
Ok, it works. The problem if that the <form:xxx> elements (eg <form:input>) have a problem with a path that starts with an element which is not in the model in the render part.
So I slightly modified the exemple given. Here is the form part :
<ul><c:forEach var="a" items="${aList}" varStatus="status">
<li><form:form modelAttribute="a" action="save.do" method="POST" >
<input type="hidden" name ="index" value="${status.index}">
<input type="text" name ="value" value="${a.value}">
<input type="submit" value="Save"/>
</form:form></li>
</c:forEach></ul>
The <li> are only for minimal formatting, but the important part is the use of direct <input> elements so that Spring MVC don't choke on a not being part of the model. So the rendering works.
Now for the save part :
RequestMapping(value = "/save", method = RequestMethod.POST)
public final String save(#ModelAttribute("a") A a, #RequestParam("index") int index){
A aa = aService.getaList().get(index);
aa.setValue(a.getvalue());
return "redirect:/list";
}
There is no problem, because the name of the model attribute is not present in the POST request. Springs simply creates a new A and sets its value attribute. This solution is extensible for complex A classes. The only problem, is that I do not manage errors returned by Spring. They have to be managed at a global level.
(I assumed the URL for the list part was /list)
Is it possible to bind spring form:form to a list element? I've tried this way.
<form:form commandName="products[0]">
<form:input path="name"/>
</form:form>
also
<form:form commandName="products0">
<form:input path="name"/>
</form:form>
Where products list is populated in spring controller.
#RequestMapping(method = RequestMethod.GET)
public String getAll(Map<String, Object> map) {
map.put("products", productService.getAll());
return "products";
}
Received: java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'products[0]' available as request attribute. Which as I understand means that spring haven't found where to bind the form.
No, this is not possible. The value you pass to the commandName attribute is a key and it is not resolved like a normal EL or SpEL expression would be. It is used directly. In other words, with
<form:form commandName="products[0]">
<form:input path="name"/>
</form:form>
Spring will look for a model attribute called products[0] which it won't find.
The alternative is to put the first element of the list in the model directly with a key you will use in your jsp.
Or you can use JSTL, get the first element in the list and create an HTML <form> element yourself.