Upload multipart form data with filename in Request Payload
Asked Answered
R

2

8

I am still confused about different method of uploading files. The backend server is not under my control but I can upload a file using Swagger page or Postman. That means the server is functioning OK. But when I use AngularJS to do the upload, it doesn't work.

Here is what works using Postman to test. I am just using form-data:

enter image description here

Notice that Request Headers has Content-Type as multipart/form-data. But the Request Payload has filename and Content-Type as image/png.

Here is my code:

$http({
  method: 'POST',
  url: ApiUrlFull + 'Job/Item?smartTermId=0&name=aaa1&quantity=1&ApiKey=ABC',
  headers: { 'Content-Type': undefined },
  transformRequest: function(data) { 
    var fd = new FormData();
    fd.append('file', params.imageData);
    return fd; 
  }
})

params is just an object with file url in imageData.

My code also send similar URL params (so we can ignore that causing issues). But the Request Payload is base64 and it looks different as it is missing the filename field.

enter image description here

I have zero control of the backend and it is written in .NET.

So I guess my question is: Using Angular (either $http or $resource), how do I modify the request so that I am sending the correct Request Payload as how Postman does it? I cannot figure out how to reverse engineer this.

I have tried this https://github.com/danialfarid/ng-file-upload and it actually did OPTIONS request first before POST (assuming CORS issue). But the server gave 405 error for OPTIONS.

Ramification answered 1/6, 2015 at 1:40 Comment(4)
You need an actual File(or just a Blob) object to send to get the effect in the first request, params.imageData is a string.Medawar
params.imageData is just file:// URL from the input type=file. Or do you mean something else that I am not thinking?Ramification
From what you posted params.imageData is a base64 encoded png, which is a string. To get FormData to post the data the way you want it has to be a File object or a Blob object. Ifd.append('file', blob_from_imageData); search SO for question to convert a data url to a blob.Medawar
How did you solve it? Facing the same problem. Please help!Foresail
A
2

You can use something along the line of:

<input type="file" name="file" onchange="uploadFile(this.files)"/>

And in your code:

$scope.uploadFile = function(files) {
    var fd = new FormData();
    //Take the first selected file
    fd.append("file", files[0]);
    var uploadUrl = ApiUrlFull + 'Job/Item?smartTermId=0&name=aaa1&quantity=1&ApiKey=ABC';
    $http.post(uploadUrl, fd, {
        withCredentials: true,
        headers: {'Content-Type': undefined },
        transformRequest: angular.identity
    }).success( ...all right!... ).error( ..damn!... );

};
Azurite answered 9/2, 2017 at 2:44 Comment(0)
M
0

My need was a follows.

  • In the form there is a default picture.
  • Clicking the picture opens a file select window.
  • When the user selects a file, it is uploaded right away to the server.
  • As soon as I get a response that the file is valid display the picture to the user instead of the default picture, and add a remove button next to it.
  • If the user clicks on an existing picture, the file select window reopens.

I tried to use a few code snippets on github that didn't solve the problem, but guided me in the right way, And what I ended up doing is as so:

Directive

angular.module("App").directive('fileModel', function ($parse) {
    return {
        restrict: 'A',
        link: function (scope, element, attrs) {
            scope.files = {};
            var model = $parse(attrs.fileModel);
            var modelSetter = model.assign;

            // I wanted it to upload on select of file, and display to the user.
            element.bind('change', function () {
                scope.$apply(function () {
                    modelSetter(scope, element[0].files[0]);
                });

                // The function in the controller that uploads the file.
                scope.uploadFile();
            });
        }
    };
});

Html

<div class="form-group form-md-line-input">
    <!-- A remove button after file has been selected -->
    <span class="icon-close pull-right"
          ng-if="settings.profile_picture"
          ng-click="settings.profile_picture = null"></span>
    <!-- Show the picture on the scope or a default picture -->
    <label for="file-pic">
        <img ng-src="{{ settings.profile_picture || DefaultPic }}"
             class="clickable" width="100%">
    </label>

    <!-- The actual form field for the file -->
    <input id="file-pic" type="file" file-model="files.pic" style="display: none;" />
</div>

Controller

$scope.DefaultPic = '/default.png';

$scope.uploadFile = function (event) {
        var filename = 'myPic';
        var file = $scope.files.pic;
        var uploadUrl = "/fileUpload";

        file('upfile.php', file, filename).then(function (newfile) { 
            $scope.settings.profile_picture = newfile.Results;
            $scope.files = {};
        });
};

function file(q, file, fileName) {
    var fd = new FormData();
    fd.append('fileToUpload', file);
    fd.append('fn', fileName);
    fd.append('submit', 'ok');

    return $http.post(serviceBase + q, fd, {
        transformRequest: angular.identity,
        headers: { 'Content-Type': undefined }
    }).then(function (results) {
        return results.data;
    });
}

Hope it helps.

P.S. A lot of code was striped from this example, if you need clarification just comment.

Mcdevitt answered 14/2, 2017 at 23:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.