I have a controller mapped for processing uploaded files
Controller
@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)
form
<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>
Now, if a person misses attaching a file to the form before submit,
-----------------------------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"
[email protected]
-----------------------------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--
or tries to submit without validating captcha, I get this 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
What I expect is that I get empty values for captcha response and file and then my controller method should be able handle it, and send user back to the form with specific error message. It works like so on forms without multipart data, i.e. I do not get binding errors but null values in the controller arguments. I see this issue only when I use Multipart Form data, the binding goes all fine if all the data is populated, i.e. a user verifies captcha, and attaches a file.
Making these params optional or using RequestPart hasn't also helped (I admit I really do not get what is the purpose of RequestPart
annotation) So, changing the controller to this (knee jerk experimentations ;-))
@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)
does not help either. Do I have to extend StandardServletMultipartResolver or is it something to change/fix in SpringInputGeneralFieldAttrProcessor, or am I missing some minor detail here?
Update
Adding Controller method
@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";
}
};
}
@RequestPart relies on HttpMessageConvertors and content-type to bind the multipart data to method param, whereas @RequestParam relies on registered convertors to do the conversion. Spring mvc provides certain convertors by default. You can use either @RequestParam or @RequestPart to bind the file data. Most of the applications use commons file upload for uploading file and register
org.springframework.web.multipart.commons.CommonsMultipartResolver
for multi part resolving. When this is registered, spring check every request for multi part data and use it to resolve this to method arg. check here
http://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-multipart
There are couple of items you can try. Make sure both of your captcha and file params are optional like below in your controller. I switched to @RequestParam for captcha.
@RequestMapping(value = "/careers/pursue", method = RequestMethod.POST)
public Callable<String> pursue(
final @RequestParam(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)
Hope this helps.
@RequestMapping
isn't mapped to the same path as for form@RequestParam
@RequestParam
doesn't pass a null notice I'm looking for length() == 0
and file.isEmpty()
Also you may want to take a look at the spring guide for file upload
Controller
@RequestMapping(value = "/upload", method = RequestMethod.POST)
public 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)
{
if (bindingResult.hasErrors() || captchaResponse.length() == 0 || file.isEmpty())
{
return "form";
}
return "redirect:/";
}
Form
<form name="jobsForm" id="jobsForm" novalidate="novalidate" action="#" th:action="@{/upload}"
th:object="${jobapplication}" method="post" enctype="multipart/form-data">
<span>First Name: </span>
<input id="firstname" th:field="*{firstName}" type="text"/><br/>
<span>Last Name: </span>
<input id="lastname" th:field="*{lastName}" type="text"/><br/>
<span>Phone: </span>
<input id="phone" th:field="*{phone}" type="text"/><br/>
<span>Email: </span>
<input id="email" th:field="*{email}" type="text"/><br/>
<span>Role: </span>
<input id="role" th:field="*{role}" type="text"/><br/>
<span></span>
<input type="text" name="g-recaptcha-response"/><br/>
<span>File: </span>
<input type="file" name="file"/><br/>
<input id="submit" type="submit"/>
</form>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With