I'm trying to use the #InitBind annotation to map only certain fields on the object in the request body.
I have a spring controller defined in this way:
#RequestMapping(value = "addAddress", method = RequestMethod.POST)
public Object addAddressToPerson(
HttpServletRequest request,
HttpServletResponse res,
#RequestParam(value = "name", required = false) String name,
#RequestParam(value = "surname", required = false) String surname,
#RequestBody personDTO personJson,BindingResult result) {
The client request will be a a json representing a personDTO, but I don't want that field except the address to be mapped in the object for security reasons.
The input will be something like:
{ "address":"123 Street","........}
The personDTO contains many fields, and since spring map all of them directly in a DTO, that can be a problem.
I've seen that a solution is to use a Binder to declase the allowed or Disallowed field, but if I check the personDTO inside the controller, other fields are populate (for example if pass "id":"1234").
Any Hints?
The binder code is the following:
#InitBinder("orderJson")
protected void orderJsonBinder(WebDataBinder binder){
binder.setAllowedFields(new String[]{"address"});
}
Am I missing something?
Best Regards,
Luca.
But you are not binding request parameters to a model attribute bean, you are just asking spring to use an appropriate MessageConverter to convert the request body. As you say it is Json, you will use a MappingJackson2HttpMessageConverter (or MappingJacksonHttpMessageConverter with Jackson 1.x). The Spring Reference Manual says for this converter :
[This is an] HttpMessageConverter implementation that can read and write JSON using Jackson's ObjectMapper. JSON mapping can be customized as needed through the use of Jackson's provided annotations. When further control is needed, a custom ObjectMapper can be injected through the ObjectMapper property for cases where custom JSON serializers/deserializers need to be provided for specific types. By default this converter supports (application/json).
#InitBinder can only configure binding of #ModelAttribute annotated parameters. It is useless here. If Jackson annotations are not enough, you will have to use a custom object mapper.
And I am surprised that you can use a BindingResult after a #RequestBody parameter, because the documentation says that it should follow a #ModelAttribute one.
Related
I created a spring RestController to process fileUpload from a react js UI.
To be able to use custom validations by #Validated annotation, had to wrap the MultipartFile into a class, I named it as UploadedFile. Created my Post Handler method with argument as #ModelAttribute.
Everything works fine.
validate(target, error) method in my custom validator is called and inside POST handler method, UploadedFile object has the multipart file containing the uploaded document..
Here is a perfectly working code
#PostMapping("/file")
public ResponseEntity<?> receiveFile(#Validated #ModelAttribute UploadedFile file) {
}
#Getter
#Setter
public class UploadedFile {
MultipartFile file;
}
// one CustomValidator class and webDataBinder.addValidators(customValidator) in controller
multipart.enabled=true //in application.properties
So far everything works as expected, Problem arise when
Someone asked me that, #ModelAttribute is a spring MVC construct, as this is a microservice, which in future, apart from my React UI, will cater to other api requests too, so I should use #RequestParam instead of #ModelAttribute.
So I changed #ModelAttribute to #RequestParam as follows
#PostMapping("/file")
public ResponseEntity<?> receiveFile(#Validated #RequestParam(name = "file") UploadedFile file)
But now I get below exception
Failed to convert value of type
'org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile'
to required type 'com.x.x.x.UploadedFile';
nested exception is java.lang.IllegalStateException:
Cannot convert value of type
'org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile'
to required type 'com.x.x.x.UploadedFile':
no matching editors or conversion strategy found
If, for #RequestParam, I change type as MultipartFile instead of UploadedFile then my posthandler gets called but the custom Validator doesn't gets called by spring before handler call,
I tried to use instanceOf MultiPartFile in validator supports method by still no avail.
#PostMapping(value="/file" )
public ResponseEntity<?> receiveFile(#Validated #RequestParam(name = "file") MultipartFile file)
I've the work around to explicitly call the custom validator method by code as first line in POST handler, so I dont need a solution MY QUESTION IS
How come, without adding any custom #Bean or any extra external dependencies everything works fine with #ModelAttribute, but merely changing the annotation #RequestParam doesn't work.
Im have #RestController and this method. how can I get any json and then select the handler depending on the method and pass it there for processing
PS. I use GSON instead of JACKSON
You can use #RequestBody in your method and take a String parameter:
public AbstractJsonResponse(#PaqthVariable String method, #RequestBody String json) {
...
}
See here: https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/bind/annotation/RequestBody.html
Mainly the part that says:
Annotation indicating a method parameter should be bound to the body of the web request
I'm trying to test a method with this signature:
#RequestMapping(value="/Employee/{id}", method=RequestMethod.PUT, consumes="application/json")
#Transactional
public #ResponseBody Map update(#PathVariable Integer id,
#RequestBody HashMap<String, Object> information) {
}
The problem is that MockMvc param attributes accept only String parameters, is there a way to pass a HashMap or an instance class object to the RequestBody as parameter?
When I try to pass a HashMap as a string, I get a MismatchException.
You need to use Jackson for this. The idea is to deserialize your objects (doesn't matter that it's HashMap) into JSON string and pass it into MockMvc.
Here is tutorial how to do that. Just search for TestClass there and take a look how it is used. Skip the unit testing of GET requests.
I have a form, but when I submit it, my initbinder doesn't intercept my post request.
This is my initbinder:
#InitBinder(value="confermaDto")
protected void initBinderDto(final WebDataBinder binder, final Locale locale) {
binder.registerCustomEditor(MyClass.class, myClassEditor);
}
And this is my method to intercept the post:
#RequestMapping(value="confermaDati", method = RequestMethod.POST)
public String confermaDati(#Valid final ConfermaDatiAttrezzaturaDto confermaDto,
final BindingResult bindingResult, final Model uiModel, final HttpServletRequest httpServletRequest) {
if (bindingResult.hasErrors()) {
uiModel.addAttribute("message", "Errore Salvataggio");
uiModel.addAttribute("dto", confermaDto);
}
uiModel.asMap().clear();
return "redirect:/";
}
I think, that it should work because the value in initbinder, and the name of my model attribure are equal.
So why don't it work??
Thank you
The names of command/form attributes and/or request parameters that this init-binder method is supposed to apply to.
Default is to apply to all command/form attributes and all request parameters processed by the annotated handler class. Specifying model attribute names or request parameter names here restricts the init-binder method to those specific attributes/parameters, with different init-binder methods typically applying to different groups of attributes or parameters.
Above is from the javadoc of #InitBinder.
In your code you specify the name of a model attribute to use, namely confermaDto. However in your request handling method there is no notion of a model attribute (i.e. nothing is annotated with #ModelAttribute).
public String confermaDati(#Valid final ConfermaDatiAttrezzaturaDto confermaDto, final BindingResult bindingResult, final Model uiModel, final HttpServletRequest httpServletRequest) { ... }
You have a argument annotated with #Valid which will only trigger validation, Spring will also instantiate this object and put values from the request onto it however it is NOT designated as a model attribute. Next to your #Valid annotation add the #ModelAttribute annotation. (Or remove the name from the #InitBinder annotation so that it will always be applied).
I think, that it should work because the value in initbinder, and the name of my model attribute are equal. So why don't it work??
In short to answer this question, the method argument name is equal but there is no model attribute. Hence no triggering of the #InitBinder method.
If you do not specify a ModelAttribute value into RequestMapping annotated method You have to specify in value attribute of #Initbinder anotation the class required name with first letter NOT capitalized.
I want to access the time-stamp in a If-Modified-Since header, so I can implement conditional GET.
Spring controllers can use the #RequestHeader annotation to indicate that Spring should pass the value of an HTTP header to a handler method as a method argument. Must the argument be a String? Or are other classes permitted? The Spring documentation implies that long values can be converted. But what is the set of classes permitted?
Will the following work (using a Date)?
#RequestMapping(method = RequestMethod.GET, headers = {"If-Modified-Since" })
public final void conditionallyRetrieve(
#RequestHeader("If-Modified-Since")final Date ifModifiedSince,
final HttpServletResponse response) {
...
}
Specifically, in your example, I think you can use DateTimeFormat to drive Spring to do the conversion:
#RequestHeader("If-Modified-Since")
#DateTimeFormat(pattern = "ThePATTERN") final Date ifModifiedSince