通过input type=file获取字节数组

46

var profileImage = fileInputInByteArray;

$.ajax({
  url: 'abc.com/',
  type: 'POST',
  dataType: 'json',
  data: {
     // Other data
     ProfileImage: profileimage
     // Other data
  },
  success: {
  }
})

// Code in WebAPI
[HttpPost]
public HttpResponseMessage UpdateProfile([FromUri]UpdateProfileModel response) {
  //...
  return response;
}

public class UpdateProfileModel {
  // ...
  public byte[] ProfileImage {get ;set; }
  // ...
}
<input type="file" id="inputFile" />

我正在使用ajax调用将文件输入类型的byte []值发布到接收byte []格式的web api,但是我遇到了获取字节数组的困难。我希望我们可以通过File API获取字节数组。

注意:在通过ajax调用传递字节数组之前,我需要先将其存储在变量中。


1
你需要分享你的代码... 服务器端和客户端。 - Arun P Johny
@ArunPJohny,我已经更新了代码。请注意,我不允许使用服务器端解决方案在发布之前获取输入文件的字节数组。 - stacknist
请访问此链接:https://dev59.com/tV0Z5IYBdhLWcg3wdQEy - sebu
相关 - https://stackoverflow.com/questions/47574218/converting-from-blob-to-binary-to-save-it-to-mongodb/49660839#49660839 - vapcguy
根据Arun的观点,这里实际上没有获取输入字节的任何内容。没有尝试过。上面的代码只是发送/接收代码,与这个问题大体上无关。对我来说,这应该在问题上被否决。这是一个重要的问题,值得回答,但它需要一个漫长的答案才能让一个人跟上步伐。这就像问“我如何驾驶飞机”而不是“我需要知道哪些参数才能知道是否可以起飞?” - vapcguy
还有相关的内容-https://dev59.com/f1oU5IYBdhLWcg3wxI2A#49676679 - vapcguy
7个回答

68

[编辑]

如上方评论所述,尽管在某些UA实现上仍然存在,readAsBinaryString 方法未被纳入规范,不应在生产环境中使用。相反,使用 readAsArrayBuffer 并循环遍历其 buffer 以获取二进制字符串:

document.querySelector('input').addEventListener('change', function() {

  var reader = new FileReader();
  reader.onload = function() {

    var arrayBuffer = this.result,
      array = new Uint8Array(arrayBuffer),
      binaryString = String.fromCharCode.apply(null, array);

    console.log(binaryString);

  }
  reader.readAsArrayBuffer(this.files[0]);

}, false);
<input type="file" />
<div id="result"></div>

为了更稳健地将你的arrayBuffer转换为二进制字符串,你可以参考这个答案


[旧版回答] (修改)

是的,文件API确实提供了一种将你的文件(在<input type="file"/>中)转换为二进制字符串的方法,感谢FileReader对象及其方法readAsBinaryString
[但不要在生产环境中使用!]

document.querySelector('input').addEventListener('change', function(){
    var reader = new FileReader();
    reader.onload = function(){
        var binaryString = this.result;
        document.querySelector('#result').innerHTML = binaryString;
        }
    reader.readAsBinaryString(this.files[0]);
  }, false);
<input type="file"/>
<div id="result"></div>

如果您想要一个数组缓冲区,那么可以使用readAsArrayBuffer()方法:

document.querySelector('input').addEventListener('change', function(){
    var reader = new FileReader();
    reader.onload = function(){
        var arrayBuffer = this.result;
      console.log(arrayBuffer);
        document.querySelector('#result').innerHTML = arrayBuffer + '  '+arrayBuffer.byteLength;
        }
    reader.readAsArrayBuffer(this.files[0]);
  }, false);
<input type="file"/>
<div id="result"></div>


似乎 Web API 接受的请求为空。但是,我已经得到了长度显示。如何以警报形式输出 ArrayBuffer 的显示? - stacknist
我的意思是在Web API中,这个.result返回null,当我弹出它时,它显示[object ArrayBuffer]。 - stacknist
那么您可能不需要一个数组缓冲区?没有看到您的服务器端代码,无法确定,但您可能想使用 FormData 直接发送文件。 - Kaiido
1
此方法不支持IE并已被弃用。我建议切换到提到的方法:readAsArrayBuffer。 - Ben Sewards
1
它抛出了“超过最大调用堆栈大小”的错误。 - Aamir Nakhwa
显示剩余9条评论

11

$(document).ready(function(){
    (function (document) {
  var input = document.getElementById("files"),
  output = document.getElementById("result"),
  fileData; // We need fileData to be visible to getBuffer.

  // Eventhandler for file input. 
  function openfile(evt) {
    var files = input.files;
    // Pass the file to the blob, not the input[0].
    fileData = new Blob([files[0]]);
    // Pass getBuffer to promise.
    var promise = new Promise(getBuffer);
    // Wait for promise to be resolved, or log error.
    promise.then(function(data) {
      // Here you can pass the bytes to another function.
      output.innerHTML = data.toString();
      console.log(data);
    }).catch(function(err) {
      console.log('Error: ',err);
    });
  }

  /* 
    Create a function which will be passed to the promise
    and resolve it when FileReader has finished loading the file.
  */
  function getBuffer(resolve) {
    var reader = new FileReader();
    reader.readAsArrayBuffer(fileData);
    reader.onload = function() {
      var arrayBuffer = reader.result
      var bytes = new Uint8Array(arrayBuffer);
      resolve(bytes);
    }
  }

  // Eventlistener for file input.
  input.addEventListener('change', openfile, false);
}(document));
});
<!DOCTYPE html>
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
</head>
<body>

<input type="file" id="files"/>
<div id="result"></div>
</body>
</html>


1
请注意,IE 11之前的版本不支持使用Promise对象。 - vapcguy
嗨,Sebu。感谢您的回答。这对我很有用。但是,“arrayBuffer”和“bytes”中哪个是字节数组?如果“bytes”是(根据名称),那么“arrayBuffer”是什么?@vapcguy,在这里,如果您能帮助我,我将非常感激。 - Harshith Rai
@Rai arrayBuffer 可能是你想要的,但当我在看这个问题时,我也无法弄清楚,所以我用了自己的方法 reader.readAsDataURL(f);(而不是 reader.readAsArrayBuffer(fileData); ),其中我使用了 var f = files[0];,它类似于上面的 fileData,只是我没有先将其转换为 Blob。我也在这个问题上提供了一个答案,最初是为 React 编写的,但仔细阅读它将给你一个原生 JS 版本。 - vapcguy

9
现代浏览器现在在Blob上有arrayBuffer方法:
document.querySelector('input').addEventListener('change', async (event) => {
  const buffer = await event.target.files[0].arrayBuffer()
  console.log(buffer)
}, false)


6
这是一篇很长的文章,但我厌倦了所有这些例子都不能正常工作,因为它们使用Promise对象或者一个errant this,当您使用Reactjs时,它的含义就不同了。我的实现使用了DropZone和reactjs,当其他方法都无法使用时,我使用了类似于下面这个网站发布的框架来获取字节:https://www.mokuji.me/article/drop-upload-tutorial-1。对我而言,有两个关键点:
  1. 您必须使用FileReader的onload函数从事件对象中获取字节。
  2. 我尝试了各种组合,但最后起作用的是:

    const bytes = e.target.result.split('base64,')[1];

其中,e是事件。React需要const,您可以在普通JavaScript中使用var。但是这给了我Base64编码的字节字符串。
因此,我将只包括适用于集成的适用行,就像您正在使用React一样,因为那是我构建它的方式,但是要尝试将其概括,并在必要时添加注释,使其适用于vanilla JavaScript实现-附带注意我没有在这样的结构中使用它来测试它。
这些将是您在React框架(与vanilla JavaScript实现无关)中的顶部绑定:
this.uploadFile = this.uploadFile.bind(this);
this.processFile = this.processFile.bind(this);
this.errorHandler = this.errorHandler.bind(this);
this.progressHandler = this.progressHandler.bind(this);

如果您在使用React,您需要在DropZone元素中添加onDrop={this.uploadFile}。如果您没有使用React,这相当于添加当您点击“上传文件”按钮时要运行的onclick事件处理器。

<button onclick="uploadFile(event);" value="Upload File" />

接下来是函数(适用的行...我会省略重置上传进度指示器等操作):

uploadFile(event){
    // This is for React, only
    this.setState({
      files: event,
    });
    console.log('File count: ' + this.state.files.length);

    // You might check that the "event" has a file & assign it like this 
    // in vanilla Javascript:
    // var files = event.target.files;
    // if (!files && files.length > 0)
    //     files = (event.dataTransfer ? event.dataTransfer.files : 
    //            event.originalEvent.dataTransfer.files);

    // You cannot use "files" as a variable in React, however:
    const in_files = this.state.files;

    // iterate, if files length > 0
    if (in_files.length > 0) {
      for (let i = 0; i < in_files.length; i++) {
      // use this, instead, for vanilla JS:
      // for (var i = 0; i < files.length; i++) {
        const a = i + 1;
        console.log('in loop, pass: ' + a);
        const f = in_files[i];  // or just files[i] in vanilla JS

        const reader = new FileReader();
        reader.onerror = this.errorHandler;
        reader.onprogress = this.progressHandler;
        reader.onload = this.processFile(f);
        reader.readAsDataURL(f);
      }      
   }
}

有一个问题是关于 vanilla JS 的语法,如何获取文件对象:

JavaScript/HTML5/jQuery Drag-And-Drop Upload - "Uncaught TypeError: Cannot read property 'files' of undefined"

请注意,如果您在构造函数中添加files: []this.state = { .... },React 的 DropZone 已经将文件对象放入this.state.files中。我添加了那篇文章上的答案中的语法,告诉你如何获取文件对象。它应该可以工作,或者还有其他的帖子可以帮助。但是所有的 Q/A 只是告诉我如何获取File对象,而不是 blob 数据本身。即使我像 sebu 的回答中一样添加了fileData = new Blob([files[0]]);,它没有告诉我如何读取这个 blob 的内容,并且如何在没有 Promise 对象的情况下执行它。所以这就是 FileReader 的作用,尽管我实际上尝试并发现我无法使用他们的readAsArrayBuffer

您将需要拥有与此结构相关的其他函数——一个处理onerror,一个处理onprogress(两者都显示在下面),然后是主要的函数onload,一旦在最后一行调用reader上的方法,它就会实际执行工作。从我可以看到的内容来看,基本上是将您的event.dataTransfer.files[0]直接传递到该onload函数中。

所以onload方法调用了我的processFile()函数(只适用于相关行):

processFile(theFile) {
  return function(e) {
    const bytes = e.target.result.split('base64,')[1];
  }
}

需要将 bytes 转换为 base64 字节。

附加功能:

errorHandler(e){
    switch (e.target.error.code) {
      case e.target.error.NOT_FOUND_ERR:
        alert('File not found.');
        break;
      case e.target.error.NOT_READABLE_ERR:
        alert('File is not readable.');
        break;
      case e.target.error.ABORT_ERR:
        break;    // no operation
      default:
        alert('An error occurred reading this file.');
        break;
    }
  }

progressHandler(e) {
    if (e.lengthComputable){
      const loaded = Math.round((e.loaded / e.total) * 100);
      let zeros = '';

      // Percent loaded in string
      if (loaded >= 0 && loaded < 10) {
        zeros = '00';
      }
      else if (loaded < 100) {
        zeros = '0';
      }

      // Display progress in 3-digits and increase bar length
      document.getElementById("progress").textContent = zeros + loaded.toString();
      document.getElementById("progressBar").style.width = loaded + '%';
    }
  }

适用的进度指示器标记:

<table id="tblProgress">
  <tbody>
    <tr>
      <td><b><span id="progress">000</span>%</b> <span className="progressBar"><span id="progressBar" /></span></td>
    </tr>                    
  </tbody>
</table>

还有CSS:

.progressBar {
  background-color: rgba(255, 255, 255, .1);
  width: 100%;
  height: 26px;
}
#progressBar {
  background-color: rgba(87, 184, 208, .5);
  content: '';
  width: 0;
  height: 26px;
}

结语:

processFile() 函数中,由于某种原因,我无法将 bytes 添加到我在 this.state 中设置的变量中。 因此,我直接将其设置为我的 JSON 对象 RequestForm 中的变量 attachments,这个变量也是我使用的 this.state 相同的对象。 attachments 是一个数组,所以我可以推送多个文件。 这就像这样:

  const fileArray = [];
  // Collect any existing attachments
  if (RequestForm.state.attachments.length > 0) {
    for (let i=0; i < RequestForm.state.attachments.length; i++) {
      fileArray.push(RequestForm.state.attachments[i]);
    }
  }
  // Add the new one to this.state
  fileArray.push(bytes);
  // Update the state
  RequestForm.setState({
    attachments: fileArray,
  });

因为this.state已经包含了RequestForm,所以:

this.stores = [
  RequestForm,    
]

从那时起,我可以将其称为this.state.attachments。这是React中的一个特性,在普通的JS中不适用。你可以在纯JavaScript中使用全局变量构建类似的结构,并相应地推入(push),但是这样做要容易得多:

var fileArray = new Array();  // place at the top, before any functions

// Within your processFile():
var newFileArray = [];
if (fileArray.length > 0) {
  for (var i=0; i < fileArray.length; i++) {
    newFileArray.push(fileArray[i]);
  }
}
// Add the new one
newFileArray.push(bytes);
// Now update the global variable
fileArray = newFileArray;

然后您只需引用fileArray,枚举其中的任何文件字节字符串,例如var myBytes = fileArray[0];表示第一个文件。


4
这是将文件转换为Base64的简单方法,避免了当文件过大时出现“FileReader.reader.onload导致栈大小超过最大值”的问题。

document.querySelector('#fileInput').addEventListener('change',   function () {

    var reader = new FileReader();
    var selectedFile = this.files[0];

    reader.onload = function () {
        var comma = this.result.indexOf(',');
        var base64 = this.result.substr(comma + 1);
        console.log(base64);
    }
    reader.readAsDataURL(selectedFile);
}, false);
<input id="fileInput" type="file" />


2

document.querySelector('input').addEventListener('change', function(){
    var reader = new FileReader();
    reader.onload = function(){
        var arrayBuffer = this.result,
array = new Uint8Array(arrayBuffer),
  binaryString = String.fromCharCode.apply(null, array);

console.log(binaryString);
      console.log(arrayBuffer);
        document.querySelector('#result').innerHTML = arrayBuffer + '  '+arrayBuffer.byteLength;
        }
    reader.readAsArrayBuffer(this.files[0]);
  }, false);
<input type="file"/>
<div id="result"></div>


0

这里有一个获取实际最终字节数组的答案,只需使用FileReaderArrayBuffer

 const test_function = async () => {

      ... ... ...

      const get_file_array = (file) => {
          return new Promise((acc, err) => {
              const reader = new FileReader();
              reader.onload = (event) => { acc(event.target.result) };
              reader.onerror = (err)  => { err(err) };
              reader.readAsArrayBuffer(file);
          });
       }
       const temp = await get_file_array(files[0])
       console.log('here we finally ve the file as a ArrayBuffer : ',temp);
       const fileb = new Uint8Array(fileb)

       ... ... ...

  }

其中file直接是您想要读取的File对象,这必须在async函数中完成...


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