Skip to content

Commit caaf83b

Browse files
binchoorstoyanchev
authored andcommitted
Add tests for binding to a Part field
See gh-27830
1 parent b30f4d7 commit caaf83b

File tree

3 files changed

+112
-5
lines changed

3 files changed

+112
-5
lines changed

spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/MultipartControllerTests.java

Lines changed: 106 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@
3939
import org.springframework.stereotype.Controller;
4040
import org.springframework.test.web.servlet.MockMvc;
4141
import org.springframework.ui.Model;
42+
import org.springframework.util.StreamUtils;
43+
import org.springframework.validation.BindException;
44+
import org.springframework.validation.BindingResult;
4245
import org.springframework.web.bind.annotation.RequestMapping;
4346
import org.springframework.web.bind.annotation.RequestMethod;
4447
import org.springframework.web.bind.annotation.RequestParam;
@@ -56,6 +59,7 @@
5659
/**
5760
* @author Rossen Stoyanchev
5861
* @author Juergen Hoeller
62+
* @author Jaebin Joo
5963
*/
6064
public class MultipartControllerTests {
6165

@@ -225,7 +229,7 @@ public void multipartRequestWithOptionalFileListNotPresent() throws Exception {
225229
}
226230

227231
@Test
228-
public void multipartRequestWithServletParts() throws Exception {
232+
public void multipartRequestWithParts_resolvesMultipartFileArguments() throws Exception {
229233
byte[] fileContent = "bar".getBytes(StandardCharsets.UTF_8);
230234
MockPart filePart = new MockPart("file", "orig", fileContent);
231235

@@ -240,6 +244,50 @@ public void multipartRequestWithServletParts() throws Exception {
240244
.andExpect(model().attribute("jsonContent", Collections.singletonMap("name", "yeeeah")));
241245
}
242246

247+
@Test
248+
public void multipartRequestWithParts_resolvesPartArguments() throws Exception {
249+
byte[] fileContent = "bar".getBytes(StandardCharsets.UTF_8);
250+
MockPart filePart = new MockPart("file", "orig", fileContent);
251+
252+
byte[] json = "{\"name\":\"yeeeah\"}".getBytes(StandardCharsets.UTF_8);
253+
MockPart jsonPart = new MockPart("json", json);
254+
jsonPart.getHeaders().setContentType(MediaType.APPLICATION_JSON);
255+
256+
standaloneSetup(new MultipartController()).build()
257+
.perform(multipart("/part").part(filePart).part(jsonPart))
258+
.andExpect(status().isFound())
259+
.andExpect(model().attribute("fileContent", fileContent))
260+
.andExpect(model().attribute("jsonContent", Collections.singletonMap("name", "yeeeah")));
261+
}
262+
263+
@Test
264+
public void multipartRequestWithParts_resolvesMultipartFileProperties() throws Exception {
265+
byte[] fileContent = "bar".getBytes(StandardCharsets.UTF_8);
266+
MockPart filePart = new MockPart("file", "orig", fileContent);
267+
268+
standaloneSetup(new MultipartController()).build()
269+
.perform(multipart("/multipartfileproperty").part(filePart))
270+
.andExpect(status().isFound())
271+
.andExpect(model().attribute("fileContent", fileContent));
272+
}
273+
274+
@Test
275+
public void multipartRequestWithParts_cannotResolvePartProperties() throws Exception {
276+
byte[] fileContent = "bar".getBytes(StandardCharsets.UTF_8);
277+
MockPart filePart = new MockPart("file", "orig", fileContent);
278+
279+
Exception exception = standaloneSetup(new MultipartController()).build()
280+
.perform(multipart("/partproperty").part(filePart))
281+
.andExpect(status().is4xxClientError())
282+
.andReturn()
283+
.getResolvedException();
284+
285+
assertThat(exception).isNotNull();
286+
assertThat(exception).isInstanceOf(BindException.class);
287+
assertThat(((BindException) exception).getFieldError("file"))
288+
.as("MultipartRequest would not bind Part properties.").isNotNull();
289+
}
290+
243291
@Test // SPR-13317
244292
public void multipartRequestWrapped() throws Exception {
245293
byte[] json = "{\"name\":\"yeeeah\"}".getBytes(StandardCharsets.UTF_8);
@@ -343,10 +391,13 @@ public String processOptionalFileList(@RequestParam Optional<List<MultipartFile>
343391
}
344392

345393
@RequestMapping(value = "/part", method = RequestMethod.POST)
346-
public String processPart(@RequestParam Part part,
394+
public String processPart(@RequestPart Part file,
347395
@RequestPart Map<String, String> json, Model model) throws IOException {
348396

349-
model.addAttribute("fileContent", part.getInputStream());
397+
if (file != null) {
398+
byte[] content = StreamUtils.copyToByteArray(file.getInputStream());
399+
model.addAttribute("fileContent", content);
400+
}
350401
model.addAttribute("jsonContent", json);
351402

352403
return "redirect:/index";
@@ -357,8 +408,60 @@ public String processMultipart(@RequestPart Map<String, String> json, Model mode
357408
model.addAttribute("json", json);
358409
return "redirect:/index";
359410
}
411+
412+
@RequestMapping(value = "/multipartfileproperty", method = RequestMethod.POST)
413+
public String processMultipartFileBean(MultipartFileBean multipartFileBean, Model model, BindingResult bindingResult)
414+
throws IOException {
415+
416+
if (!bindingResult.hasErrors()) {
417+
MultipartFile file = multipartFileBean.getFile();
418+
if (file != null) {
419+
model.addAttribute("fileContent", file.getBytes());
420+
}
421+
}
422+
return "redirect:/index";
423+
}
424+
425+
@RequestMapping(value = "/partproperty", method = RequestMethod.POST)
426+
public String processPartBean(PartBean partBean, Model model, BindingResult bindingResult)
427+
throws IOException {
428+
429+
if (!bindingResult.hasErrors()) {
430+
Part file = partBean.getFile();
431+
if (file != null) {
432+
byte[] content = StreamUtils.copyToByteArray(file.getInputStream());
433+
model.addAttribute("fileContent", content);
434+
}
435+
}
436+
return "redirect:/index";
437+
}
360438
}
361439

440+
private static class MultipartFileBean {
441+
442+
private MultipartFile file;
443+
444+
public MultipartFile getFile() {
445+
return file;
446+
}
447+
448+
public void setFile(MultipartFile file) {
449+
this.file = file;
450+
}
451+
}
452+
453+
private static class PartBean {
454+
455+
private Part file;
456+
457+
public Part getFile() {
458+
return file;
459+
}
460+
461+
public void setFile(Part file) {
462+
this.file = file;
463+
}
464+
}
362465

363466
private static class RequestWrappingFilter extends OncePerRequestFilter {
364467

spring-web/src/main/java/org/springframework/web/bind/ServletRequestDataBinder.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,14 @@ public ServletRequestDataBinder(@Nullable Object target, String objectName) {
104104
* HTTP parameters: i.e. "uploadedFile" to an "uploadedFile" bean property,
105105
* invoking a "setUploadedFile" setter method.
106106
* <p>The type of the target property for a multipart file can be MultipartFile,
107-
* byte[], or String. The latter two receive the contents of the uploaded file;
107+
* Part, byte[], or String. The Part binding is only supported when the request
108+
* is not a MultipartRequest. The latter two receive the contents of the uploaded file;
108109
* all metadata like original file name, content type, etc are lost in those cases.
109110
* @param request the request with parameters to bind (can be multipart)
110111
* @see org.springframework.web.multipart.MultipartHttpServletRequest
112+
* @see org.springframework.web.multipart.MultipartRequest
111113
* @see org.springframework.web.multipart.MultipartFile
114+
* @see jakarta.servlet.http.Part
112115
* @see #bind(org.springframework.beans.PropertyValues)
113116
*/
114117
public void bind(ServletRequest request) {

spring-web/src/main/java/org/springframework/web/bind/support/WebRequestDataBinder.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,8 @@ public WebRequestDataBinder(@Nullable Object target, String objectName) {
108108
* HTTP parameters: i.e. "uploadedFile" to an "uploadedFile" bean property,
109109
* invoking a "setUploadedFile" setter method.
110110
* <p>The type of the target property for a multipart file can be Part, MultipartFile,
111-
* byte[], or String. The latter two receive the contents of the uploaded file;
111+
* byte[], or String. The Part binding is only supported when the request
112+
* is not a MultipartRequest. The latter two receive the contents of the uploaded file;
112113
* all metadata like original file name, content type, etc are lost in those cases.
113114
* @param request the request with parameters to bind (can be multipart)
114115
* @see org.springframework.web.multipart.MultipartRequest

0 commit comments

Comments
 (0)