web-dev-qa-db-fra.com

Obtenir un tableau d'octets via input type = file

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

J'utilise un appel ajax pour publier une valeur d'octet [] d'une entrée type = fichier dans une API Web qui reçoit au format octet []. Cependant, j'ai des difficultés à obtenir un tableau d'octets. Je m'attends à ce que nous puissions obtenir le tableau d'octets via l'API de fichier.

Remarque: je dois d'abord stocker le tableau d'octets dans une variable avant de passer par un appel ajax.

20
stacknist

[Modifier]

Comme indiqué dans les commentaires ci-dessus, la méthode readAsBinaryString n'est toujours pas conforme aux spécifications et bien qu'elle soit encore utilisée dans certaines UA, elle ne doit pas être utilisée en production. A la place, utilisez readAsArrayBuffer et parcourez buffer pour récupérer la chaîne binaire:

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>

Pour un moyen plus robuste de convertir votre arrayBuffer en chaîne binaire, vous pouvez vous référer à cette réponse .


[ancienne réponse] (modifié)

Oui, l'API de fichier fournit un moyen de convertir votre fichier, dans le <input type="file"/>, En une chaîne binaire, grâce à l'objet FileReader et à sa méthode readAsBinaryString .
[ Mais ne l'utilisez pas en production! ]

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>

Si vous voulez un tampon de tableau, vous pouvez utiliser la méthode 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>
39
Kaiido
$(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>
5
sebu

C'est un long post, mais j'en avais marre de tous ces exemples qui ne fonctionnaient pas pour moi parce qu'ils utilisaient des objets Promise ou un this errant qui a un sens différent lorsque vous utilisez Reactjs. Mon implémentation utilisait une DropZone avec reactjs, et j'ai obtenu les octets en utilisant un cadre similaire à ce qui est affiché sur ce site, quand rien d’autre ne fonctionnerait: https://www.mokuji.me/article/ drop-upload-tutorial-1 . Il y avait 2 clés, pour moi:

  1. Vous devez obtenir les octets de l'objet événement, à l'aide de et pendant la fonction onload de FileReader.
  2. J'ai essayé diverses combinaisons, mais au final, ce qui a fonctionné a été:

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

e est l'événement. React nécessite const, vous pouvez utiliser var en Javascript simple. Mais cela m'a donné la chaîne d'octets codée en base64.

Je vais donc simplement inclure les lignes applicables pour l’intégrer comme si vous utilisiez React, car c’est ainsi que je le construisais, mais essayez également de généraliser cela, et d’ajouter des commentaires le cas échéant, pour le rendre applicable à un Javascript Vanilla. implémentation - à condition que je ne l'utilise pas comme ça dans une telle construction pour le tester.

Ce sont vos liaisons au sommet, dans votre constructeur, dans un cadre React (non pertinent pour une implémentation Javascript de Vanilla):

this.uploadFile = this.uploadFile.bind(this);
this.processFile = this.processFile.bind(this);
this.errorHandler = this.errorHandler.bind(this);
this.progressHandler = this.progressHandler.bind(this);

Et vous auriez onDrop={this.uploadFile} Dans votre élément DropZone. Si vous faites cela sans React, cela revient à ajouter le gestionnaire d'événement onclick que vous voulez exécuter lorsque vous cliquez sur le bouton "Télécharger le fichier".

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

Puis la fonction (lignes applicables ... je laisserai de côté mon indicateur de progression du téléchargement, etc.):

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);
      }      
   }
}

Il y avait cette question sur cette syntaxe, pour Vanilla JS, sur la façon d'obtenir cet objet de fichier:

Chargement par glisser-déplacer JavaScript/HTML5/jQuery - "TypeError non capturé: impossible de lire les 'fichiers' de propriété 'des fichiers non définis"

Notez que DropZone de React mettra déjà l'objet File dans this.state.files Pour vous, tant que vous ajoutez files: [], À votre this.state = { .... } Dans votre constructeur. J'ai ajouté la syntaxe d'une réponse sur ce post sur la façon d'obtenir votre objet File. Cela devrait fonctionner, ou d’autres publications pourraient vous aider. Mais tout ce que Q/A m'a dit était comment obtenir l'objet File, pas les données blob, elles-mêmes. Et même si je faisais fileData = new Blob([files[0]]); comme dans la réponse de sebu, qui n'incluait pas var avec elle pour une raison quelconque, cela ne me disait pas comment lire le contenu de ce blob ni comment faites-le sans objet Promise. C’est donc là que le FileReader est entré en jeu, bien que j’ai en fait essayé et découvert que je ne pouvais pas utiliser leur readAsArrayBuffer.

Vous devrez disposer des autres fonctions associées à cette construction - une pour gérer onerror, une pour onprogress (les deux présentées plus bas), puis la principale, onload, qui fait le travail une fois qu'une méthode sur reader est invoquée dans cette dernière ligne. Fondamentalement, vous passez votre event.dataTransfer.files[0] Directement dans cette fonction onload, d'après ce que je peux dire.

Donc, la méthode onload appelle ma fonction processFile() (lignes applicables uniquement):

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

Et bytes devrait avoir les octets base64.

Fonctions supplémentaires:

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 + '%';
    }
  }

Et balisage d’indicateur de progrès applicable:

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

Et 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;
}

ÉPILOGUE:

Dans processFile(), pour une raison quelconque, je ne pouvais pas ajouter bytes à une variable que j'ai sculptée dans this.state. Donc, au lieu de cela, je l'ai défini directement sur la variable attachments, qui était dans mon objet JSON, RequestForm - le même objet que mon this.state Utilisait. attachments est un tableau pour pouvoir envoyer plusieurs fichiers. Ça s'est passé comme ça:

  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,
  });

Ensuite, parce que this.state Contenait déjà RequestForm:

this.stores = [
  RequestForm,    
]

Je pourrais le référencer comme this.state.attachments À partir de là. React qui ne s'applique pas à Vanilla JS. Vous pouvez créer une construction similaire en JavaScript simple avec une variable globale, et Push, en conséquence, mais beaucoup plus facile:

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;

Ensuite, vous référencez toujours fileArray, vous l'énumérez pour toutes les chaînes d'octets de fichier, par exemple. var myBytes = fileArray[0]; Pour le premier fichier.

3
vapcguy

C'est un moyen simple de convertir des fichiers en Base64 et d'éviter "la taille maximale de la pile d'appels dépassée à FileReader.reader.onload" avec le fichier a une grande taille.

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" />
1
Văn Minh Nguyên