Spring MVC validation form attribute name problem - java

Just got a strange behavior with validation of a very simple HTML form with Spring MVC and Thymleaf.
I have this methods for edit page rendering and submitted form handle.
#GetMapping("/{id}")
public String editPage(#PathVariable("id") Long id, Model model) {
model.addAttribute("user", userService.findById(id)
.orElseThrow(NotFoundException::new));
return "user_form";
}
#PostMapping("/update")
public String update(#Valid UserRepr user, BindingResult result, Model model) {
logger.info("Update endpoint requested");
if (result.hasErrors()) {
return "user_form";
}
userService.save(user);
return "redirect:/user";
}
The wired thing in the update() method is that in case of a validation error the attribute of the model with form content has the name "userRepr" but not "user" as I expect and the thymleaf form view failed because of that.
It's easy to fix the problem by renaming the attribute but Is there some contract about such attribute naming? Is it changeable?

You can do this using #ModelAttribute.
#ModelAttribute is used to map/bind a a method parameter or method return type to a named model attribute. See #ModelAttributes JavaDoc. This is a Spring annotation.
#PostMapping("/update")
public String update(#Valid #ModelAttribute("user") UserRepr user, BindingResult result, Model model) {
logger.info("Update endpoint requested");
if (result.hasErrors()) {
return "user_form";
}
userService.save(user);
return "redirect:/user";
}
Why does it look like this?
Model object auto generates attribute names and then forward the above method calls to addAttribute(Object attributeValue).
Following are the rules of name generation strategy:
For an object which is not a collection, short class name is generated. For example for java.lang.String, 'string' will be
generated.
For collections/arrays, 'List' is appended after the type of elements in it e.g. 'stringList'. The collection/array should not be
empty because the logic uses the first element to find it's type.
You can check this link for more details. https://www.logicbig.com/tutorials/spring-framework/spring-web-mvc/spring-model-attribute-generated-names.html

Related

Spring-MVC form validation highlight input field after DAO validation

I'm converting Struts 1.3 project to Spring. Instead of struts form fields, I'm using spring form.
I have used ActionErrors in struts to highlight the field using errorStyleClass attribute.
Similarly, in spring cssErrorClass is available. But, How to use it after the dao validation?
#RequestMapping(value = "/login", method = RequestMethod.POST)
public String login(#ModelAttribute("login") #Validated Login login, BindingResult result, Model model) {
if (result.hasErrors()) {
//THIS VALIDATION DONE BY ANNOTATION AND HIGHLIGHTING THE FIELD
//USING "cssErrorClass"
return HOMEPAGE;
}
boolean checkAuthentication = authService.checkAuthentication(login);
if(!checkAuthentication){
// HOW TO SET THE ERROR HERE?
// Is there any way to set the error like
// error.setMessage("userId","invalid.data");
// so that, is it possible to display error message by
// highlighting the fields using "cssErrorClass"?
}
return HOMEPAGE;
}
You need to annotate your entities using Java Bean Validation framework JSR 303, like this
public class Model{
#NotEmpty
String filed1;
#Range(min = 1, max = 150)
int filed2;
....
}
And add #Valid to your controller, like this
public class MyController {
public String controllerMethod(#Valid Customer customer, BindingResult result) {
if (result.hasErrors()) {
// process error
} else {
// process without errors
}
}
You can find more examples for it here and here
EDIT:
If you want to register more errors based on custom validation steps in code, you can use rejectValue() method in the BindingResult instance, like this:
bindingResult.rejectValue("usernameField", "error code", "Not Found username message");

What is the difference between #ModelAttribute, model.addAttribute in spring?

i am new Spring learner.i'm really confused about what is the difference between two concept:
#ModelAttribute
model.addAttribute
in below there are two "user" value.Are these same thing?Why should I use like this?
Thank you all
#RequestMapping(method = RequestMethod.GET)
public String setupForm(ModelMap model) {
model.addAttribute("user", new User());
return "editUser";
}
#RequestMapping(method = RequestMethod.POST)
public String processSubmit( #ModelAttribute("user") User user, BindingResult result, SessionStatus status) {
userStorageDao.save(user);
status.setComplete();
return "redirect:users.htm";
}
When used on an argument, #ModelAttribute behaves as follows:
An #ModelAttribute on a method argument indicates the argument should be retrieved from the model. If not present in the model, the argument should be instantiated first and then added to the model. Once present in the model, the argument’s fields should be populated from all request parameters that have matching names. This is known as data binding in Spring MVC, a very useful mechanism that saves you from having to parse each form field individually.
http://docs.spring.io/spring/docs/4.1.0.BUILD-SNAPSHOT/spring-framework-reference/htmlsingle/#mvc-ann-modelattrib-method-args
That's a very powerful feature. In your example, the User object is populated from the POST request automatically by Spring.
The first method, however, simply creates an instance of Userand adds it to the Model. It could be written like that to benefit from #ModelAttribute:
#RequestMapping(method = RequestMethod.GET)
public String setupForm(#ModelAttribute User user) {
// user.set...
return "editUser";
}

Can I add value in a BindingResult before checking Errors in Spring?

Can I add value in a BindingResult before checking Errors in Spring?
#InitBinder("memberrequest")
public void initMemberRequestBinder(WebDataBinder binder) {
binder.setValidator(new MemberRequestValidator());
}
#PreAuthorize("isAuthenticated()")
#RequestMapping(value = "/save", method = RequestMethod.POST)
public ModelAndView saveRequest(#Valid #ModelAttribute("memberrequest") MemberRequest mr, BindingResult result, HttpSession session) {
session.setAttribute("phone", mr.getPhonenumber());
mr.setWelfare((String)session.getAttribute("welfare"));
mr.setSchool((String)session.getAttribute("school"));
mr.setTitle((String)session.getAttribute("title"));
mr.setDistrict((String)session.getAttribute("district"));
mr.setName((String)session.getAttribute("name"));
mr.setFile((String)session.getAttribute("file"));
mr.setQueue((String)session.getAttribute("queue"));
mr.setRequestor(getUser());
mr.setSchool_id((String)session.getAttribute("school_id"));
mr.setBorough_id((String)session.getAttribute("borough_id"));
mr.setRetiree((String)session.getAttribute("retiree"));
if (result.hasErrors()) {
LOGGER.debug("Pages had errors on it... returning to input page");
return new ModelAndView("w-question");
} else {
I have the above code in my Spring controller but the issue is that I need to take some values out of the session and move them into the BindingResult (Bean) before Validator runs on it..
Can this be done someone? the issues is some of the values I keep in the session.. please me know if this can be dont and how is the best way to do it..
In your controler define method for creating your model atribute and annotate it with #ModelAttribute annotation.
Actually you will not modify the binding result object itself but the validation target and then you can change your validator behavior to change binding result as you need.
#ModelAttribute("memberrequest")
public MemberRequest getMemberRequest(HttpSession session) {
MemberRequest mr = new MemberRequest();
mr.setWelfare((String)session.getAttribute("welfare"));
mr.setSchool((String)session.getAttribute("school"));
mr.setTitle((String)session.getAttribute("title"));
mr.setDistrict((String)session.getAttribute("district"));
mr.setName((String)session.getAttribute("name"));
mr.setFile((String)session.getAttribute("file"));
mr.setQueue((String)session.getAttribute("queue"));
mr.setRequestor(getUser());
mr.setSchool_id((String)session.getAttribute("school_id"));
mr.setBorough_id((String)session.getAttribute("borough_id"));
mr.setRetiree((String)session.getAttribute("retiree"));
return mr;
}
this method will be called before the binding ocures, but have in mind that this method will be called before each controler method wich is using #ModelAttribute("memberrequest") as parameter.

2 Request Handlers for POST (ResponseBody + "Normal")

I like to implement a REST-API into my SpringMVC application. At the moment, I have one method to handle POST-Requests, which "returns" a rendered ViewScript.
#RequestMapping(method=RequestMethod.POST)
public String onSubmit(User user, Model model)
{
return "success";
}
It would be nice, to add a second method with the #ResponseBody Annotation for POST-Requests, e.g. to send a JSON-Response.
Furthermore, the old Method still has to exists, to handle "normal" Requests.
But a code like this doesn't work:
#RequestMapping(method=RequestMethod.POST)
public String onSubmit(User user, Model model)
{
return "success";
}
#RequestMapping(method=RequestMethod.POST)
#ResponseBody
public Object add(User user, Model model)
{
// [...]
return myObject;
}
With this code, I'm getting a 405 (Method Not Allowed) Error from Tomcat. How can I fix this?
As it stands, Spring has no way to differentiate between these two requests: same URL, same request method.
You can further differentiate by mimetype:
#RequestMapping(method=RequestMethod.POST, headers="content-type=application/json")
Although there are several mimetypes associated with JSON :/ The headers value takes an array, however, so you can narrow/widen it as necessary.
See the headers docs.
Dont USE TWO ANNOTATION. It is a poor option. Just have one more method without annotation. But the method from the old method by checking the below condition.
JUST PASS ONE MORE ARGUMENT FROM UI by query parameter(request="JSON_Request").
#RequestMapping(method=RequestMethod.POST)
public String onSubmit(User user, Model model)
{
if(request="JSON_Request") {
newMethod(user, model);
}
return "success";
}
private Object newMethod(User user, Model model)
{
// [...]
return myObject;
}

Spring MVC and JSR-303 hibernate conditional validation

I've a form I want to validate. It contains 2 Address variables. address1 has always to be validated, address2 has to be validated based on some conditions
public class MyForm {
String name;
#Valid Address address1;
Address address2;
}
public class Address {
#NotEmpty
private String street;
}
my controller automatically validates and binds my form obj
#RequestMapping(...)
public ModelAndView edit(
#ModelAttribute("form")
#Valid
MyForm form,
BindingResult bindingResult,
...)
if(someCondition) {
VALIDATE form.address2 USING JSR 303
the problem is that if I use the LocalValidatorFactoryBean validator i can't reuse the BinidingResult object provided by Spring. The bind won't work as the target object of 'result' is 'MyForm' and not 'Address'
validate(form.getAddress2(), bindingResult) //won't work
I'm wondering what's the standard/clean approach to do conditional validation.
I was thinking in programmatically create a new BindingResult in my controller.
final BindingResult bindingResultAddress2 = new BeanPropertyBindingResult(address2, "form");
validate(form.getAddress2(), bindingResultAddress2);
but then the List of errors I obtain from bindingResultAddress2 can't be added to the general 'bindingResult' as the field names are not correct ('street' instead of 'address2.street') and the binding won't work.
Some dirty approach would be to extend BeanPropertyBindingResult to accept some string to append to the fields name.. do you have a better approach?
The standard approach for validating hierarchical structures is to use pushNestedPath()/popNestedPath(), though I'm not sure how it plays with JSR-303:
bindingResult.pushNestedPath("address2");
validate(form.getAddress2(), bindingResult);
bindingResult.popNestedPath();
I've never tried myself, but I think the correct approach is using validator groups.
First of all, let's see #javax.validation.Valid API
Mark an association as cascaded. The associated object will be validated by cascade.
When Spring framework uses #Valid as a marker to validate its command objects, it corrupts its purpose. Spring should instead create your own specific annotation which specifies the groups which should be validated.
Unfortunately, you should use Spring native Validator API if you need to validate some groups
public void doSomething(Command command, Errors errors) {
new BeanValidationValidator(SomeUserCase.class, OtherUserCase.class)
.validate(command, errors);
if(errors.hasErrors()) {
} else {
}
}
BeanValidationValidator can be implemented as
public class BeanValidationValidator implements Validator {
javax.validation.Validator validator = ValidatorUtil.getValidator();
private Class [] groups;
public BeanValidationValidator(Class... groups) {
this.groups = groups;
}
public void validate(Object command, Errors errors) {
Set<ConstraintViolation<Object>> constraintViolationSet = validator.validate(command, groups);
for(ConstraintViolation<Object> constraintViolation: constraintViolationSet) {
errors.rejectValue(constraintViolation.getPropertyPath().toString(), null, constraintViolation.getMessage());
}
}
}

Categories