Ben G
Ben G

Reputation: 3975

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..

Upvotes: 5

Views: 42835

Answers (4)

David Pham
David Pham

Reputation: 1803

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

Upvotes: 2

user3422025
user3422025

Reputation: 31

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?

Upvotes: 3

Brad Parks
Brad Parks

Reputation: 72001

I don't think this approach fully embraces spring but it works, is simple, and uses a simple html form, and one method in a controller:

html file to do the post (save as jsp or html, your call)

<html>
  <head>
    <title>File Upload Example</title>
  </head>
  <body>
    <form action="save_uploaded_file.html" method="post" enctype="multipart/form-data">
        Choose a file to upload to the server:
        <input name="myFile" type="file"/><br/>
      <p>
        <input type="submit"/>
        <input type="reset"/>
      </p>
    </form>
  </body>
</html>

in your controller, have the following method:

@RequestMapping("/save_uploaded_file")
public String testUpload(HttpServletRequest request) throws Exception
{
    // get the file from the request
    MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
    MultipartFile multipartFile = multipartRequest.getFile("myFile");

    // Note: Make sure this output folder already exists!
    // The following saves the file to a folder on your hard drive.
    String outputFilename = "/tmp/uploaded_files/file2.txt";
    OutputStream out = new FileOutputStream(outputFilename);
    IOUtils.copy(multipartFile.getInputStream(), out);

    return "/save_uploaded_file";
}

To get larger files to be uploadable (over 40k), you may need to specify the following in spring servlet configuration.... I've put some big #'s there to make sure it works, but of course put "real" numbers there if you need to.

<!-- Configure the multipart resolver -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <!-- one of the properties available; the maximum file size in bytes -->
    <property name="maxUploadSize" value="500000000"/>
    <property name="maxInMemorySize" value="500000000" />
</bean>

Note that Spring's file upload stuff uses Apache Commons FileUpload under the covers. You'll need to download that and include it in your app. This, in turn, also requires Apache Commons IO, so you'll need that also. (quoted from here)

Upvotes: 1

Ben G
Ben G

Reputation: 3975

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.

Upvotes: 3

Related Questions