multipart/form-data binding pour ModelAttribute échoue si le fichier en plusieurs parties paramètre est null

J'ai un contrôleur mappé pour le traitement des fichiers téléchargés

Contrôleur

  @RequestMapping(value = "/careers/pursue", method = RequestMethod.POST)
  public Callable<String> pursue(
      final @RequestParam("g-recaptcha-response") String captchaResponse,
      final @RequestParam("file") MultipartFile file,
      final @ModelAttribute("jobapplication") @Valid JobApplication application, final BindingResult bindingResult,
      final Model model)  

forme

<form name="jobsForm" id="jobsForm" novalidate="novalidate" action="#" th:action="@{/careers/pursue}"
th:object="${jobapplication}" method="post" enctype="multipart/form-data">
<div class="control-group form-group">
<div class="controls">
<label>First Name:</label>
<input type="text" class="form-control" id="firstName" th:field="*{firstName}" required="required" data-validation-required-message="Please enter your name." />
<p class="help-block"></p>
</div>
</div>
<div class="control-group form-group">
<div class="controls">
<label>Last Name:</label>
<input type="text" class="form-control" id="lastName" th:field="*{lastName}" required="required" data-validation-required-message="Please enter your name." />
<p class="help-block"></p>
</div>
</div>                    
<div class="control-group form-group">
<div class="controls">
<label>Phone Number:</label>
<input type="tel" class="form-control" id="phone" th:field="*{phone}" required="required" data-validation-required-message="Please enter your phone number." />
</div>
</div>
<div class="control-group form-group">
<div class="controls">
<label>Email Address:</label>
<input type="email" class="form-control" id="email" th:field="*{email}" required="required" data-validation-required-message="Please enter your email address."/>
</div>
</div>
<div class="control-group form-group">
<div class="controls">
<label>Role:</label>
<input type="email" class="form-control" id="role" th:field="*{role}" required="required" data-validation-required-message="Please enter your email address."/>
</div>
</div>                    
<div class=" control-group form-group">
<div class="g-recaptcha" data-sitekey="ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"></div>                    
</div>
<div class=" control-group form-group">
<span class="btn btn-primary btn-file">
Add your Resumé <input type="file" name="file" id="file" required="required"/>
</span>
</div>
<div id="success"></div>
<!-- For success/fail messages -->
<button type="submit" class="btn btn-primary">Apply!</button>
</form>

Maintenant, si une personne manque d'attacher un fichier à un formulaire avant de le soumettre,

-----------------------------749526091303082321866336941
Content-Disposition: form-data; name="firstName"
Anadi
-----------------------------749526091303082321866336941
Content-Disposition: form-data; name="lastName"
Misra
-----------------------------749526091303082321866336941
Content-Disposition: form-data; name="phone"
9845420420
-----------------------------749526091303082321866336941
Content-Disposition: form-data; name="email"
foo@bar.com
-----------------------------749526091303082321866336941
Content-Disposition: form-data; name="role"
open.project
-----------------------------749526091303082321866336941
Content-Disposition: form-data; name="g-recaptcha-response"
03AHJ_Vuv9i7WQ_4zCipfnyrLNl6467l_cZgGIhkdpLjS1M0YmWvwQMOWQeRcrAHFh8s3-jO13NQs7019lzI7UobwNeHKIhBmcLMiVGPk38Iy8BjrEi2glI4QGjE4VTvRhV_-WWYsmlzV_7PRPE5Y8L0NboPXYoG9JSabMOL8V958w74pOzkxabsoR4wouCSa0gzo0EbOsLiCWjd0MAvZiCcKJGdwIlMp0WIjxcufB-RfG2F0rwv65yrgL-By0bdMewkWULY_aRvC-FRSqOEM9X5Qg4gviA-cvc5IY2XnRtaUALOPlR_QbwjgUKl2mJEFNab6Pks3MlsivuEZFkba4isDFlrJ4jXwBBQ
-----------------------------749526091303082321866336941
Content-Disposition: form-data; name="file"; filename=""
Content-Type: application/octet-stream
-----------------------------749526091303082321866336941--

ou tente de soumettre sans valider le captcha, j'ai cette exception

Caused by: org.thymeleaf.exceptions.TemplateProcessingException: Error during execution of processor 'org.thymeleaf.spring4.processor.attr.SpringInputGeneralFieldAttrProcessor' (jobs:91)
....
....
Caused by: java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'jobapplication' available as request attribute
at org.springframework.web.servlet.support.BindStatus.<init>(BindStatus.java:144)
at org.thymeleaf.spring4.util.FieldUtils.getBindStatusFromParsedExpression(FieldUtils.java:396)
at org.thymeleaf.spring4.util.FieldUtils.getBindStatus(FieldUtils.java:323)
at org.thymeleaf.spring4.util.FieldUtils.getBindStatus(FieldUtils.java:289)
at org.thymeleaf.spring4.processor.attr.AbstractSpringFieldAttrProcessor.processAttribute(AbstractSpringFieldAttrProcessor.java:98)
at org.thymeleaf.processor.attr.AbstractAttrProcessor.doProcess(AbstractAttrProcessor.java:87)
at org.thymeleaf.processor.AbstractProcessor.process(AbstractProcessor.java:212)
... 66 common frames omitted

Ce que j'attends, c'est que j'obtiens des valeurs vides pour le captcha et réponse de fichier et puis ma méthode de contrôleur doit être en mesure de le manipuler, et d'envoyer l'utilisateur vers le formulaire avec un message d'erreur spécifique. Il fonctionne comme sur des formes sans multipart de données, c'est à dire que je n'ai pas de liaison des erreurs, mais des valeurs null dans les arguments du contrôleur. Je vois ce problème uniquement quand j'utilise Multipart des données de Formulaire, la liaison va bien si toutes les données sont remplies, c'est à dire un utilisateur vérifie captcha, et attache un fichier.

Faire de ces paramètres en option ou à l'aide de RequestPart n'a pas aidé (j'avoue je n'ai vraiment pas obtenir ce qui est le but de RequestPartannotation) Donc, en changeant le contrôleur (réflexe expérimentations ;-))

  @RequestMapping(value = "/careers/pursue", method = RequestMethod.POST)
public Callable<String> pursue(
final @RequestPart(value = "g-recaptcha-response", required = false) String captchaResponse,
final @RequestPart(value = "file", required = false) MultipartFile file,
final @ModelAttribute("jobapplication") @Valid JobApplication application, final BindingResult bindingResult,
final Model model) 

n'aide pas non plus. Dois-je prolonger StandardServletMultipartResolver ou est-il quelque chose à changer/réparer dans SpringInputGeneralFieldAttrProcessor, ou ai-je raté quelques petits détails ici?

Mise à jour

Ajout de la méthode du Contrôleur

  @RequestMapping(value = "/careers/pursue", method = RequestMethod.POST)
public Callable<String> pursue(final @ModelAttribute("jobapplication") @Valid JobApplication application,
final BindingResult bindingResult, final Model model,
final @RequestParam(value = "g-recaptcha-response", required = false) String captchaResponse,
final @RequestPart(value = "file", required = false) MultipartFile file) {
return new Callable<String>() {
@Override
public String call() throws Exception {
try {
model.asMap().clear();
GoogleCaptchaResponseData response = captchaVerifier.isCaptchaResponseValid(captchaResponse).get();
model.addAttribute("recaptcha", response.isSuccess());
model.addAttribute("recaptchamessage", response.getErrorCodes());
if (response.isSuccess() && !file.isEmpty()) {
byte[] bytes = file.getBytes();
LOGGER.info("Found file of type {}", file.getOriginalFilename());
ByteArrayInputStream inputBytes = new ByteArrayInputStream(bytes);
mailApi.sendMail(mailApi.buildJobApplicationEmail(application, new BufferedInputStream(inputBytes)));
model.asMap().clear();
model.addAttribute("uploadsuccess", true);
model.addAttribute("resource_host", resourceHost);
model.addAttribute("jobapplication", new JobApplication());
}
} catch (InterruptedException | ExecutionException e) {
LOGGER.error(e.getMessage(), e);
model.asMap().clear();
model.addAttribute("jobapplication", application);
model.addAttribute("resource_host", resourceHost);
model.addAttribute("uploadsuccess", false);
}
return "jobs";
}
};
}

OriginalL'auteur Anadi Misra | 2015-02-27