Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

spring multipart file upload form validation

I'm new to spring, and i'm currently struggling with the many pieces required to get a multipart form submit/validation scenario with error beeing displayed in the view.

Here are the files i currently have :

resourceupload.jsp : a view that displays a form to upload the file.

<form:form method="post" action="resource/upload" enctype="mutlipart/form-data">
 <input name="name" type="text"/>
 <input name="file" type="file" />
 <input type="submit"/>
<form:errors path="file" cssClass="errors"/>
</form>

resourceuploadcontroller.java : the controller that handles the form submit, and (unsuccessfuly) tries to post file validation errors back to the view :

@RequestMapping(method = RequestMethod.POST)
public String handleFormUpload( @RequestParam("file") MultipartFile file , @RequestParam("name") String name,Object command, Errors validationErrors){
..perform some stuff with the file content, checking things in the database, etc...
.. calling validationErrors.reject("file","the error") everytime something goes wrong...

return "redirect:upload"; // redirect to the form, that should display the error messages

Now, obviously there's something wrong with this approach:

1/ I had to add a dummy "command" object before the validationErrors parameter, otherwise spring would throw me an error. That doesn't seem really right.

2/ After I added that parameter, the redirect doesn't pass the errors to the view. I tried using @SessionAttribute("file") at the start of the controller, without any luck.

If anyone could help... I've had a look at @ResponseBody annotation, but that doesn't seem to be made to be used with views..

like image 633
Ben G Avatar asked Mar 26 '12 23:03

Ben G


3 Answers

Seems like I found the solution on my own.

First, the link that helped me a lot : http://www.ioncannon.net/programming/975/spring-3-file-upload-example/ and Spring 3 MVC - form:errors not showing the errors which showed a good trick to display all errors, using

<form:errors path="*"/>. 

Now, a list of all the things i changed to make that thing work :

1/ use "rejectValue" instead of "reject".

2/ return the view directly instead of a redirect.

3/ create a "UploadItem" model with a CommonsMultipartFile property

So, all in all, my controller method became

@RequestMapping(method = RequestMethod.POST)
public String handleFormUpload( @ModelAttribute("uploadItem") UploadItem uploadItem, BindingResult errors){
... use errors.rejectValue ... in case of errors (moving everything i could in a UploadItemValidator.validate function)
return "uploadform"

Hope that helped.

like image 122
Ben G Avatar answered Jan 03 '23 12:01

Ben G


Here is a very easy way to do it:

The formBackingObject:

import org.springframework.web.multipart.commons.CommonsMultipartFile;

public class FileImport {

    CommonsMultipartFile  importFile;

    public CommonsMultipartFile getImportFile() {
        return importFile;
    }

    public void setImportFile(CommonsMultipartFile importFile) {
        this.importFile = importFile;
    }
}

The form:

<sf:form method="POST" modelAttribute="fileImport" enctype="multipart/form-data" action="import">
    <table>
        <spring:hasBindErrors name="fileImport">
            <tr>
                <td colspan="2">
                    <sf:errors path="importFile" cssClass="error"/>
                </td>
            </tr>
        </spring:hasBindErrors>
        <tr>
            <th><label for="importFile">Import Workload:</label></th>
            <td><input name="importFile" type="file"></td>
        </tr>
    </table>
    <input type="submit" value="Import Application Data">
</sf:form>

And finally the ControllerClassMethod:

@RequestMapping(value={"/import"}, method=RequestMethod.POST)
public String importWorkload(
        FileImport fileImport, BindingResult bindResult,
        @RequestParam(value="importFile", required=true) MultipartFile importFile ){
    if( 0 == importFile.getSize() ){
        bindResult.rejectValue("importFile","fileImport.importFile");
    }

    if(bindResult.hasErrors()){
        return "admin";
    }
....
}

Easy Peasy!

The reason the @NotNull annotation does not work for files is because the multipart file is never null in the request object. But, it can have 0 bytes. You can take the time to implement a custom validator, but why?

like image 43
user3422025 Avatar answered Jan 03 '23 13:01

user3422025


In Spring 4, you can implement the file validator like below:

@Component
public class FileValidator implements Validator {

    @Override
    public boolean supports(Class<?> clazz) {
        return FileModel.class.isAssignableFrom(clazz);
    }

    @Override
    public void validate(Object target, Errors errors) {
        FileModel fileModel = (FileModel) target;

        if (fileModel.getFile() != null && fileModel.getFile().isEmpty()){
            errors.rejectValue("file", "file.empty");
        }
    }
}

And then inject above validator into the upload controller such as below:

@Controller
@RequestMapping("/")
public class FileUploadController {

    @Autowired
    private FileValidator fileValidator;

    @ModelAttribute
    public FileModel fileModel(){
        return new FileModel();
    }

    @InitBinder
    protected void initBinderFileModel(WebDataBinder binder) {
        binder.setValidator(fileValidator);
    }

    @RequestMapping(method = RequestMethod.GET)
    public String single(){
        return "index";
    }

    @RequestMapping(method = RequestMethod.POST)
    public String handleFormUpload(@Valid FileModel fileModel,
                                   BindingResult result,
                                   RedirectAttributes redirectMap) throws IOException {

        if (result.hasErrors()){
            return "index";
        }

        MultipartFile file = fileModel.getFile();
        InputStream in = file.getInputStream();
        File destination = new File("/tmp/" + file.getOriginalFilename());
        FileUtils.copyInputStreamToFile(in, destination);

        redirectMap.addFlashAttribute("filename", file.getOriginalFilename());
        return "redirect:success";
    }
}

This implementation helps your code more clear and more readable. I found it from the tutorial Spring MVC File Upload Validation Example

like image 27
David Pham Avatar answered Jan 03 '23 14:01

David Pham