Hayi
Hayi

Reputation: 6236

Issue with sending 2 dimensional array of files

I am sending multiple files with formData like this

form data structure

In my Spring MVC Controller

@PostMapping(value = "/marches")
public Integer saveMarches(
        @RequestPart("formJson") FooBean formJson, 
        @RequestPart("attachOs") MultipartFile[][] attachOs
        ) throws IOException {
    ...
}

My conf :

@Bean(name = "multipartResolver")
public CommonsMultipartResolver multipartResolver() {
    CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
    multipartResolver.setMaxUploadSize(30000000);
    return multipartResolver;
}

But i got a 400 Bad Request in the browser

enter image description here

And in the IDE I got :

Resolved [org.springframework.web.multipart.support.MissingServletRequestPartException:
Required request part 'attachOs' is not present]

and if i try @RequestPart("attachOs[][]") MultipartFile[][] attachOs i got always a bad request with Required request part 'attachOs[][]' is not present

The problem is obvious : spring is searching just for attachOs part (@RequestPart("attachOs")) but i am sending attachOs[0][0], attachOs[0][1] ...

When i send just the formJson part without files or if i send just a single file @RequestPart("attachOs") MultipartFile attachOs or one dimension array of files @RequestPart("attachOs") MultipartFile[] attachOs everything works fine.

Javascript code :

const formData = new FormData();

for (const [i, os] of formJson.os.entries()) {
    if(os.attachment) {
        for (const [j, file] of [...os.attachment].entries()) {
            formData.append(`attachOs[${i}][${j}]`, file );
        }
    }
}
...
formData.append('formJson', 
           new Blob([JSON.stringify(formJson)], {type:'application/json'}));
...
axios({
    url: ...,
    method: 'POST',
    data: formData,
})
...

My formJson structure is

{
    // form fields
    ...
    os: [
        {
            // os form fields
            ...
            attachment: [{ /* File type */ }, ...], // multiple files per os
        },
        ...
    ]
}

I know that files cannot be sent along with JSON that's why i am constructing the formData above and after that i will delete the attachment property from JSON structure

So my questions :

1. How to fix the bad request issue ?

2. is there another approach or design pattern to handle this use case ?

Upvotes: 8

Views: 1871

Answers (4)

Mustafa
Mustafa

Reputation: 6154

If you want to send multiple file attachments per OS you can use a List instead of a 2-dimensional array in the spring controller.

@PostMapping(value = "/marches")
public Integer saveMarches(
        @RequestPart("formJson") FooBean formJson, 
        @RequestPart("attachOs") List<MultipartFile> files
        ) throws IOException {

    // Associate files with their os using the index in their name.
}

and in your angular code append the os index in the name of the file.

for (const [i, os] of formJson.os.entries()) {
    if (os.attachment) {
        for (const [j, file] of [...os.attachment].entries()) {
            formData.append(`attachOs`, file, file.name + ":" + i );
        }
    }
}

Upvotes: 2

bur&#230;quete
bur&#230;quete

Reputation: 14688

I found a solution by utilizing a @ModelAttribute (from here);

First create a model class like;

public class MyRequest {
    private FooBean formJson;
    private MultipartFile[][] attachOs = new MultipartFile[2][2];

    // getters/setters
}

Then add it to your controller like;

@PostMapping(value = "/marches", consumes = "multipart/form-data")
public Integer saveMarches(@ModelAttribute MyRequest request) throws IOException {
    // some code
}

The important part was the initialization of the MultipartFile[][] attachOs, otherwise it does not work with multidimensional arrays due to some internal initialization issue.


Or you can use the following type in the model class;

private List<MultipartFile>[] attachOs;

which works without an initialization.

Upvotes: 1

s7vr
s7vr

Reputation: 75934

Spring supports binding all multivalue map and single value map of multipart and part files in SPR-17405

Adding Map<String, MultipartFile> will map the multipart value to key.

Something like attachOs[0][0], attachs[0][1]

@PostMapping(value = "/marches")
public Integer saveMarches(
        @RequestPart("formJson") FooBean formJson, 
        @RequestParam Map<String, MultipartFile> attachOs
        ) throws IOException {
    ...
}

Another variation would be send multiple multipart values per row. For that you could use MultiValueMap<String, MultipartFile>. For this variation you have to update your angular code.

Something like attachOs[0], attachs[1]

 @PostMapping(value = "/marches")
    public Integer saveMarches(
            @RequestPart("formJson") FooBean formJson, 
            @RequestParam MultiValueMap<String, MultipartFile> attachOs
            ) throws IOException {
        ...
    }

If you would like to use second variant you can give unique name per os and append files. There is no need to make it appear like array. You can call data.append multiple append with same name and it will add them as array of files.

for (const [i, os] of formJson.os.entries()) {
    if(os.attachment) {
        for (const [j, file] of [...os.attachment].entries()) {
            formData.append(os.name, file);
        }
    }
}

Upvotes: 2

ema
ema

Reputation: 951

You need to create custom converter @Component, which implements HttpMessageConverter

Upvotes: 0

Related Questions