AngularJS中的多部分请求

22

我有一个API端点,必须向其发送一个由两部分组成的多部分HTTP请求:file(文件系统文件)和data(JSON对象)。

经过一些研究,我发现如何在AngularJS中进行多部分请求:

$http({
   method: 'POST',
   url: url,
   headers: {
      'Content-Type': 'multipart/form-data'
   },
   data: {
      data: model,
      file: file
   },
   transformRequest: customFormDataObject
});

1) customFormDataObject 函数最初的形式如下:

customFormDataObject formDataObject = function (data, headersGetter) {
   var fd = new FormData();
   angular.forEach(data, function (value, key) {
      fd.append(key, value);
   });

   var headers = headersGetter();
   delete headers['Content-Type'];

   return fd;
};

这种实现的结果是请求的各个部分没有设置contentType

2) 在阅读了更多信息之后(https://dev59.com/OWAf5IYBdhLWcg3wizME#24535293),我尝试使用Blob来解决这个问题,customFormData对象看起来像这样(有点混乱,基本上第一部分将是contentType application/json,第二部分将是image/png):

customFormDataObject = function (data, headersGetter) {
    var fd = new FormData();

    var contentType = 'application/json';
    angular.forEach(data, function (value, key) {
         fd.append(key, new Blob([JSON.stringify(value)], {
             type: contentType
         }));
         contentType = 'image/png';
    });

    var headers = headersGetter();
    delete headers['Content-Type'];

    return fd;
 };

第二种方法为请求的每个部分设置了正确的contentType,但它并未为这些部分设置任何值。

基本上,以下是发生的情况:1)在多部分中设置了正确的值,但没有设置contentType。 与2)设置了contentType,但是没有设置多部分的值。

我是否漏掉了什么? 这个功能难道不应该像这样工作吗?

谢谢!


你尝试过在请求中设置内容类型而不是每个表单数据元素吗? - kooker
是的,当执行 delete headers['Content-Type']; 时,请求的内容类型会自动设置。 - Adrian Marinica
仅供参考:我推荐使用https://github.com/nervgh/angular-file-upload,并且我也上传了blob: https://github.com/nervgh/angular-file-upload/issues/208。 - Betty St
1
file 已经是一个 BlobJSON.stringify 不会序列化文件内容,而是会序列化 file 的属性(如果有的话)。 - a better oliver
5个回答

35

在Angular中上传文件的最简单方法:

var fd = new FormData();
fd.append('file', file);
fd.append('data', 'string');
$http.post(uploadUrl, fd, {
   transformRequest: angular.identity,
   headers: {'Content-Type': undefined}
})
.success(function(){
})
.error(function(){
});

以下是配置对象必须具备的两个绝对必要属性:

transformRequest: angular.identity

覆盖了Angular默认的序列化方式,保留了我们的数据。

headers: {'Content-Type': undefined }

让浏览器检测到正确的Content-Type,即multipart/form-data,并填充正确的分界线。

没有其他方法适用于我!感谢Lady Louthan的精彩博客文章


1
很棒的答案。简单而有效的解决方案。 - Daniel Buhai
1
这是答案。 - Stephane
谢谢!有时候使用XMLHttpRequest会很有帮助。 - xiaoyu2er
太棒了!尝试了很多其他的,但只有这个有效! - zeroflaw

4

你尝试过像这样的方法吗:

$httpProvider.defaults.transformRequest = function(data) {
  if (data === undefined){
    return data;
  }

  var formData = new FormData();
  for (var key in data){
    if(typeof data[key] == 'object' && !(data[key] instanceof File)){
      formData.append(key, JSON.stringify(data[key]));
    }else{
      formData.append(key, data[key]);
    }
  }
  return formData;
};

4

我刚刚解决了完全相同的问题。

经过一些测试,我得到了你所描述的情况:

基本上,出现了以下两种情况:1)多部分中设置了正确的值,但未设置contentType; 2)设置了contentType,但未设置多部分的值。

为了解决这个问题,我必须使用Blob和Post Ajax而不是$http Post

似乎在这种情况下$http无法正常工作。

代码:

    var formData = new FormData();

    var blobId = new Blob([100], { 'type':'text/plain' });
    formData.append('testId', blobId);

    var blobImage = fileService.base64ToBlob(contentToDecode, 'image/jpeg');
    formData.append('file', blobImage, 'imagem' + (i + 1) + '.jpg');

请求:

    $.ajax({
      url: url,
      data: formData,
      cache: false,
      contentType: false,
      processData: false,
      type: 'POST',
      success: function(response) {
        deferred.resolve(response);
        $rootScope.requestInProgress = false;
      },
      error: function(error) {
        deferred.reject(error);
        $rootScope.requestInProgress = false;
      }
    });

请发一些代码以帮助用户朝正确的方向前进。 - MatthewD
是的,一些代码会对我很有帮助。我已经完全推迟了这个功能,但我想在某个时候让它开始运行。 - Adrian Marinica

2
你可以使用 https://github.com/danialfarid/ng-file-upload/ 进行文件上传。
在这个文件上传器中,有一个功能可以像你在上面的问题中提到的那样,将文件和数据(以JSON格式)分开发送。
例如:
var upload = $upload.upload({
                 url: url,
                 file: file,
                 method: 'POST' or 'PUT', default POST,
                 headers: {'Content-Type': 'multipart/form-data'}, // only for html5
                 fileName: 'doc.jpg',
                 fileFormDataName: 'myFile',
                 data: {'data': model}
             });

在上面的代码中,您可以使用“multipart/form-data”格式发送POST或PUT请求,并将文件和数据对象分别作为JSON发送。
如需更多信息,请访问上面的链接并查看插件的ReadMe.md。
我知道我的方法与您目前所遵循的方法有些不同,但目标是相同的。

0

我解决这个问题的方法是。

var formData = new FormData(document.getElementById('myFormId'));

然后在我的服务中

                var deferred = $q.defer();
                $http.post('myurl', formData, {
                    cache: false,
                    contentType: false,
                    processData: false,
                })
                    .success(function (response) {
                        deferred.resolve(response);
                    })
                    .error(function (reject) {
                        deferred.reject(reject);
                    });
                return deferred.promise;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接