Search code examples
angularjsspringkotlinspring-web

Angular: how to post a file to the server on form submit


I have a form that's being posted to my back-end (Kotlin, Spring Web). That form had some text inputs and the post worked flawlessly. But when I added a file input, the post stopped working, returning the following error:

{status: 400, error: "Bad Request",…}
error: "Bad Request"
exception: "org.springframework.http.converter.HttpMessageNotReadableException"
message: "Could not read document: No suitable constructor found for type [simple type, class com.test.InsertConfigCommand]: can not instantiate from JSON object (missing default constructor or creator, or perhaps need to add/enable type information?)↵ at [Source: java.io.PushbackInputStream@2cb43211; line: 1, column: 2]; nested exception is com.fasterxml.jackson.databind.JsonMappingException: No suitable constructor found for type [simple type, class com.test.InsertConfigCommand]: can not instantiate from JSON object (missing default constructor or creator, or perhaps need to add/enable type information?)↵ at [Source: java.io.PushbackInputStream@2cb43211; line: 1, column: 2]"

Here are the codes of my stack:

View:

<form ng-submit="insert(config)">
    <input type="text" ng-model="config.name">
    <input type="text" ng-model="config.address">
    <input type="file" ng-model="config.file">
    <button type="submit">Save</button>
</form>

Controller (front-end):

$scope.insert = function (config) {
    $http.post('/config', config)
        .then(function (response) {
            $.snackbar({content: "Success!"});
        }, $scope.showErrorMessage);
};

Controller (back-end):

@RequestMapping(method = arrayOf(RequestMethod.POST))
fun insert(@RequestBody config: InsertConfigCommand) = service.save(config)

InsertConfigCommand

data class InsertConfigCommand (
    val name : String = "",
    val address : String = "",
    val file : MultipartFile
)

I've tried to do the post the following way, it works, but only sends the file:

Controller (front-end):

$scope.insert = function (file) {
    var fd = new FormData();
    fd.append('file', file);

    return $http.post('/config', fd, {
        transformRequest: angular.identity,
        headers: {
            'Content-Type': undefined
        }
    });
};

Controller (back-end):

@RequestMapping(method = arrayOf(RequestMethod.POST))
fun insert(@RequestParam(value = "file", required = true) file: MultipartFile) = service.save(file)

What do I need to change for this post to work? I want to send the input file on the same object that the name and address.


Solution

  • I've used this tutorial which encapsulates the file inside the FormData object, and posts that object https://uncorkedstudios.com/blog/multipartformdata-file-upload-with-angularjs

    $scope.insert = function (config) {
        var fd = new FormData();
        fd.append('name', config.name);
        fd.append('address', config.address);
        fd.append('file', $scope.file);
        $http.post('/config', fd, {
                transformRequest: angular.identity,
                headers: {'Content-Type': undefined}
            })
            .then(function (response) {
                $.snackbar({content: "Success!"});
            }, $scope.showErrorMessage);
    };
    

    And on my Kotlin controller, I receive each attribute as a separate param:

    @RequestMapping(method = arrayOf(RequestMethod.POST))
    fun insert(@RequestParam(value = "name", required = true) name: String,
               @RequestParam(value = "address", required = true) address: String,
               @RequestParam(value = "file", required = false) file: MultipartFile): InsertConfigCommand? {
    
        val command = InsertConfigCommand(
                       name = name,
                       address = address,
                       file = file)
    
        return service.save(command)
    }