【问题标题】:Getting byte array through input type = file通过输入类型=文件获取字节数组
【发布时间】:2023-03-20 03:31:01
【问题描述】:

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 调用将输入类型的字节 [] 值 = 文件输入发布到以字节 [] 格式接收的 web api。但是,我在获取字节数组时遇到了困难。我期待我们可以通过 File API 获取字节数组。

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

【问题讨论】:

  • 你需要共享你的代码...服务器端和客户端
  • @ArunPJohny 我已经更新了代码。请注意,我不允许在发布之前使用服务器端解决方案来获取输入文件的字节数组
  • 在 Arun 看来,这里实际上没有任何东西可以获取输入字节。没有尝试。上面的代码只是发送/接收代码,这在很大程度上与这个问题无关。对我来说,这值得对这个问题投反对票。这是一个重要的问题,值得回答,但它需要一个冗长的答案才能让一个人跟上进度。这就像问“我如何驾驶飞机”而不是“我需要知道哪些参数才能知道我是否可以起飞?”

标签: javascript html fileapi


【解决方案1】:

这是获得实际最终字节数组的一个答案,只需使用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 函数中完成...

【讨论】:

    【解决方案2】:

    现代浏览器现在在Blob 上拥有arrayBuffer 方法:

    document.querySelector('input').addEventListener('change', async (event) => {
      const buffer = await event.target.files[0].arrayBuffer()
      console.log(buffer)
    }, false)
    

    ??

    【讨论】:

      【解决方案3】:

      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>

      【讨论】:

        【解决方案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);
        &lt;input id="fileInput" type="file" /&gt;

        【讨论】:

          【解决方案5】:

          这是一篇很长的文章,但我厌倦了所有这些对我不起作用的示例,因为它们使用了 Promise 对象或错误的 this,当您使用 Reactjs 时,它们具有不同的含义。我的实现是使用带有 reactjs 的 DropZone,并且我使用类似于以下站点上发布的框架获取字节,而上面的其他任何方法都不起作用:https://www.mokuji.me/article/drop-upload-tutorial-1。对我来说,有 2 把钥匙:

          1. 您必须在 FileReader 的 onload 函数中使用和期间从事件对象中获取字节。
          2. 我尝试了各种组合,但最终奏效的是:

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

          e 是事件。 React 需要 const,你可以在纯 Javascript 中使用 var。但这给了我base64编码的字节字符串。

          因此,我将包含用于集成它的适用行,就像您使用 React 一样,因为这就是我构建它的方式,但也尝试概括这一点,并在必要时添加 cmets,使其适用于一个普通的 Javascript 实现 - 需要注意的是,我没有在这样的构造中使用它来测试它。

          这些将是您在顶部的绑定,在您的构造函数中,在 React 框架中(与普通 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);
          

          您的 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 就会为您将 File 对象放入 this.state.files。我从该帖子的答案中添加了有关如何获取 File 对象的语法。它应该可以工作,或者那里有其他帖子可以提供帮助。但 Q/A 告诉我的只是如何获取 File 对象,而不是 blob 数据本身。即使我在 sebu 的答案中做了fileData = new Blob([files[0]]);,由于某种原因没有包含var,它也没有告诉我如何读取该 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 中提取的变量中。因此,我将其直接设置为变量attachments,它位于我的JSON 对象RequestForm 中——与我的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。不适用于 vanilla JS 的 React 功能。你可以用一个全局变量在纯 JavaScript 中构建一个类似的结构,然后相应地推送,但是要容易得多:

          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]; 第一个文件。

          【讨论】:

            【解决方案6】:

            $(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>

            【讨论】:

            • 请注意,我相信 11 之前的 IE 版本不支持使用 Promise 对象。
            • hi sebu.. 感谢您的回答.. 这似乎对我有用.. 但是,arrayBufferbytes 中的哪一个是字节数组?如果bytes 是(根据名称),那么arrayBuffer 是什么? @vapcguy,如果你能在这里帮助我,我将非常感激..
            • @Rai arrayBuffer 可能是你想要的那个,但是当我看到这个的时候,我也想不通,我用我自己的方式选择了reader.readAsDataURL(f);(而不是reader.readAsArrayBuffer(fileData); ),我使用了var f = files[0];,这类似于上面的fileData,除了我没有先将它转换为Blob。我也提供了这个问题的答案,它最初是为 React 编写的,但仔细阅读它也会给你一个香草 JS 版本。
            【解决方案7】:

            [编辑]

            正如上面 cmets 中所述,虽然仍然在一些 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 转换为二进制字符串的更可靠的方法,您可以参考this answer


            [旧答案] (修改)

            是的,文件 API 确实提供了一种将 &lt;input type="file"/&gt; 中的文件转换为二进制字符串的方法,这要归功于 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>

            【讨论】:

            • 如何分配 readAsArrayBuffer 结果并通过 ajax 调用?
            • 在阅读器加载事件中,使用 arraybuffer 作为参数调用你的 ajax 函数(reader.onload = function(){ yourAwesomeAjaxFunction(this.result)}; 然后function yourAwesomeAjaxFunction( profileImage ){ $.ajax({ url: 'abc.com/',...
            • 好像web api接受的请求为null。但是,我得到了长度显示。如何以警报形式输出 arraybuffer 的显示?
            • arrayBuffer.byteLength它在我给你的代码sn-p中
            • 这不支持 ie 并且已被弃用。我建议切换到上述方法:readAsArrayBuffer
            猜你喜欢
            • 2021-12-04
            • 2019-03-29
            • 2019-02-16
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2016-11-04
            • 2014-06-05
            相关资源
            最近更新 更多