1

I have an angular form where I fill fields for a product and then I select images. I've added following configuration in AppConfig in SpringMVC project to enable MultiPart Files.

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

I've also added a transient field in my Product Model for images like this:

@Transient
private List<MultipartFile> productImages;

Since I want to receive the Product object separately inside the controller I'm using @RequestPart to fetch both separately like this:

@RequestMapping(value = "save", method = RequestMethod.POST)
public ResponseEntity addProduct(@Valid @RequestPart Product product, @RequestPart MultipartFile[] images, BindingResult bindingResult, HttpServletRequest 
}

And on the frontend inside Angular, I'm trying to use FormData to help with this.

let formData = new FormData();

formData.append('product', new Blob([JSON.stringify(this.product)],{ type: "application/json" }));
// I iterate and append all the images like this
formData.append('image[]', this.images, this.images.name);

this.http.post(this.appService.getApiUrl() + "api/product/save/", product);

The problem is that whenever I submit the form, I get this exception as a response: HTTP Status 415 – Unsupported Media Type

I had an HttpInterceptor that appended an application/json header for Content-Type, I removed it because of it I was getting HTTP Status 400 – Bad Request. At this point, I do not know what's the problem, I don't know how to debug the front facing spring mvc stuff, I can only debug through breakpoints within a controller method but the problem arises well before.

1 Answers1

1

I think you would have to send a multipart/related request but last time I checked Angular does not support that.

Regardless, you are implementing way more code than you need to there. Why don't you take a look at Spring Content. This is designed to associate content with Spring Data entities, as you are trying to do, using the same (or very similar programming model) as Spring Data.

You might add it to your project as follows:

pom.xml

   <!-- Java API -->
   <dependency>
      <groupId>com.github.paulcwarren</groupId>
      <artifactId>spring-content-fs-boot-starter</artifactId>
      <version>0.7.0</version>
   </dependency>

   <!-- REST API -->
   <dependency>
      <groupId>com.github.paulcwarren</groupId>
      <artifactId>spring-content-rest-boot-starter</artifactId>
      <version>0.7.0</version>
   </dependency>

FilesystemConfiguration.java

@Configuration
public class FilesystemConfiguration {

    @Bean
    File filesystemRoot() {
        try {
            return new File("/path/to/your/product/images");
        } catch (IOException ioe) {}
        return null;
    }

    @Bean
    FileSystemResourceLoader fileSystemResourceLoader() {
        return new FileSystemResourceLoader(filesystemRoot().getAbsolutePath());
    }
}

Product.java

@Entity
public class Product {
   @Id
   @GeneratedValue
   private long id;

   ...other existing fields...

   @ContentId
   private String contentId;
    
   @ContentLength
   private long contentLength = 0L;
    
   @MimeType
   private String mimeType = "text/plain";

   ...
}

ProductContentStore.java

@StoreRestResource(path="productImages")
public interface ProductContentStore extends ContentStore<Product, String> {
}

This is all you need to do to get REST Endpoints that will allow you to store and retrieve content associated with each Product. How this actually works is very much like Spring Data. When your application starts Spring Content will see the spring-content-fs-boot-starter dependencies and know that you want to store content on the filesystem. It will inject a Filesystem-based implementation of the ProductContentStore interface. It will also see the spring-content-rest-boot-starter and will inject REST endpoints that talk to the content store interface. Meaning you don't have to write any of this code yourself.

So, for example:

curl -X POST /productImages/{productId} -F "file=@/path/to/image.jpg"

will store the image on the filesystem and associate it with the product entity whose id is productId.

curl /productImages/{productId}

will fetch it again and so on...supports full CRUD and video streaming too actually BTW.

You could also decide to store the contents elsewhere like in the database (as someone commented), or in S3 by swapping the spring-content-fs-boot-starter dependency for the appropriate Spring Content Storage module. Examples for every type of storage are here.

In summary, angular (and other popular frontends) don't, at time of writing, support multipart/related that is, in my opinion, the correct type of request for uploading content related to entities. In lieu of that Spring Content and your solution will have to use separate requests to; firstly to create the entity and the secondly associate the content with that entity.

HTH

Community
  • 1
  • 1
Paul Warren
  • 2,411
  • 1
  • 15
  • 22