Получение байтового массива через тип ввода = файл

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[] значение типа ввода = ввод файла в web api, который получает в формате byte[]. Однако я испытываю трудности с получением байтового массива. Я ожидаю, что мы можем получить байтовый массив через File API.

Примечание: мне нужно сначала сохранить байтовый массив в переменной, прежде чем проходить через вызов ajax

7 ответов

Решение

[Редактировать]

Как отмечено в комментариях выше, хотя все еще на некоторых реализациях 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>

В современных браузерах теперь есть arrayBuffer метод на Blobs:

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

🎉 🎉

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

Это длинный пост, но я устал от всех этих примеров, которые не работают для меня, потому что они использовали объекты Promise или ошибочные this это имеет другое значение, когда вы используете Reactjs. Моя реализация использовала DropZone с actjs, и я получил байты, используя фреймворк, похожий на тот, что опубликован на следующем сайте, когда ничего другого не сработало бы: https://www.mokuji.me/article/drop-upload-tutorial-1. Для меня было 2 ключа:

  1. Вы должны получить байты из объекта события, используя и во время функции загрузки FileReader.
  2. Я пробовал разные комбинации, но в итоге получилось:

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

куда e это событие. Реагировать требует constВы могли бы использовать var в простом Javascript. Но это дало мне байтовую строку в кодировке base64.

Так что я просто собираюсь включить соответствующие строки для интеграции этого, как если бы вы использовали React, потому что именно так я его собирал, но попробуйте также обобщить это и добавить комментарии, где это необходимо, чтобы сделать его применимым к ванильному 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);

И вы бы onDrop={this.uploadFile} в вашем элементе DropZone. Если вы делали это без 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: Невозможно прочитать свойства" файлы "с неопределенным"

Обратите внимание, что DropZone от React уже поместит объект File в this.state.files для вас, пока вы добавляете files: [], на ваш this.state = { .... } в вашем конструкторе. Я добавил синтаксис из ответа на этот пост о том, как получить объект File. Это должно работать, или есть другие сообщения, которые могут помочь. Но все, что Q/A сказал мне, было, как получить File сам объект, а не данные BLOB. И даже если бы я сделал 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. Вы можете создать аналогичную конструкцию в обычном 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]; для первого файла.

This is simple way to convert files to Base64 and avoid "maximum call stack size exceeded at FileReader.reader.onload" with the file has big size.

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" />

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>

Вот один ответ, чтобы получить фактический окончательный массив байтов, просто используя FileReaderа также ArrayBuffer:

       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функция...

Другие вопросы по тегам