Carga confiable al almacenamiento de blobs a través de un Control de HTML5
Este artículo se tradujo automáticamente. Para ver el artículo en inglés, active la casilla Inglés. Además, puede mostrar el texto en inglés en una ventana emergente si mueve el puntero del mouse sobre el texto.
Traducción
Inglés

Cargar archivos con confianza en el almacenamiento de blobs a través de un control HTML5

 

Autor: Rahul Rai, consultor asociado, Microsoft Global entrega

Código fuente: http://code.msdn.microsoft.com/Silverlight-Azure-Blob-3b773e26

Use una API de archivos de HTML5, AJAX y MVC 3 para generar un control de carga de archivos sólido y cargar archivos de gran tamaño de manera segura y con confianza en el almacenamiento de blobs de Azure con capacidad para supervisar el progreso de la operación y la cancelación de la operación.

Mecanismos de carga de archivos tradicionales carecen de capacidades de procesamiento del archivo de cliente y, por lo tanto, no se puede hacer fragmentados cargas de archivos. La fragmentación de cargas de archivos le permiten realizar acciones útiles, como reintentar la carga solo de los bloques que no pudieron llegar al servidor, facilitar la supervisión del progreso y cargar archivos grandes.

HTML5 proporciona mejoras en el lenguaje y multimedia. Ahora podemos crear un más errores archivos tolerantes a errores y seguras carga control mediante la API de archivo HTML5 y blob en bloques de Azure.

Para crear el control de carga de archivos, necesitamos desarrollar tres componentes:

  1. JavaScript en el lado del cliente, que acepta y procesa un archivo cargado por el usuario.

  2. Código en el lado del servidor, que procesa los fragmentos del archivo enviados por JavaScript.

  3. Interfaz de usuario en el lado del cliente, que invoca a JavaScript.

Requisitos previos:

  • Explorador compatible con HTML5 (Internet Explorer 10+, FireFox 3.6+ o Google Chrome 7+)

  • MVC 3

  • JQuery

  • API de almacenamiento de Azure

Para generar esta solución, utilice el siguiente algoritmo:

  1. Acepte un archivo del usuario y compruebe la capacidad del explorador para administrar una lista de archivos en HTML5.

  2. Enviar los metadatos del archivo, como el nombre de archivo, el tamaño del archivo, el número de bloques, etc., en el servidor en forma de JQuery XmlHttpRequest, y recibir JSON respuesta del servidor. Si el servidor ha guardado correctamente esta información, comenzará a procesar cada fragmento de archivo.

  3. Hasta que se alcance el final del archivo:

    1. Leer 1 MB de segmento (configurable) del archivo, adjuntar un identificador a la solicitud y enviar al servidor con un identificador de bloque (un número generado de forma secuencial), donde un acción método en el controlador MVC acepta el blob de HTML5 y cargarlo como un blob en bloques en el almacenamiento de Azure.

    2. Obtiene una respuesta en forma de un JSON de mensajes desde el servidor y, si es correcto, procesar el siguiente bloque. Si el JSON mensaje tiene datos de error de operación, representar estos datos en el cliente y anular la operación.

  4. Si se detecta un error en el envío del blob desde JavaScript, detenga la operación durante cinco segundos (configurable) y reinténtela para ese blob tres veces más (configurable). Si no puede cargar el blob, anule la operación.

  5. Si todos los blobs transmitan correctamente al servidor, el Action método en Azure blob mediante el envío de confirmaciones de controlador MVC el Put Block List de solicitud y envía el estado de la operación a JavaScript como un JSON mensaje.

  6. En cualquier momento, puede cancelar la operación y el sistema fuerza una salida de la rutina llamando a abort() en el identificador asociado a la solicitud actual en el paso 3a anterior.

El siguiente diagrama muestra los pasos del proceso:

Proceso de carga de blobs HTML 5

Implementemos ahora la solución dividiendo el algoritmo en pasos. Tenga en cuenta que se han omitido las implementaciones triviales para simplificar el procedimiento:

  1. Aceptar el archivo del usuario y comprobar de las capacidades del explorador.

    1. Cree una vista de MVC compatible con HTML5 con los siguientes elementos (información general de alto nivel):

      <input type="file" id="FileInput" multiple="false"/> <input type="button" id="upload" name="Upload" onclick="startUpload('FileInput', 1048576, 'uploadProgress', 'statusMessage', 'upload', 'cancel');" /> <input type="button" id="cancel" name="Cancel" onclick="cancelUpload();" class="button" />
      
    2. Cree un archivo JavaScript e implemente startUpload() que se invoca desde el cargar botón. Haga clic en y comprobar la compatibilidad del explorador para FileList. Además, dado que el objeto de enumeración no es modificable, se trata de una buena práctica para inmovilizar tales objetos.

      function startUpload(fileElementId, blockLength, uploadProgressElement, statusLabel, uploadButton, cancelButton) { Object.freeze(operationType); uploader = Object.create(ChunkedUploader); if (!window.FileList) { uploader.statusLabel = document.getElementById(statusLabel); uploader.displayLabel(operationType.UNSUPPORTED_BROWSER); return; }...
      
    3. Diseñar un prototipo (según las directrices de ECMAScript5) para ChunkUpload con todos los conocimientos de archivo encapsulado en él.

      var ChunkedUploader = { constructor: function (controlElements) { this.file = controlElements.fileControl.files[0]; this.fileControl = controlElements.fileControl; this.statusLabel = controlElements.statusLabel; this.progressElement = controlElements.progressElement; this.uploadButton = controlElements.uploadButton; this.cancelButton = controlElements.cancelButton; this.totalBlocks = controlElements.totalBlocks; }, ... /*UI functions omitted */
      
    4. Cree una instancia de este prototipo e inicializar sus miembros de datos desde startUpload().

      function startUpload(fileElementId, blockLength, uploadProgressElement, statusLabel, uploadButton, cancelButton) { ... uploader = Object.create(ChunkedUploader); ... uploader.constructor({ "fileControl": document.getElementById(fileElementId), "statusLabel": document.getElementById(statusLabel), "progressElement": document.getElementById(uploadProgressElement), "uploadButton": document.getElementById(uploadButton), "cancelButton": document.getElementById(cancelButton), "totalBlocks": 0 }); ... }
      
      
  2. Enviar los metadatos del archivo al servidor y guardar la información.

    1. Enviar atributos de archivo como un JSON de mensajes al servidor y al recibir un mensaje de confirmación, continuar con la carga fragmentada.

      function startUpload(fileElementId, blockLength, uploadProgressElement, statusLabel, uploadButton, cancelButton) { ... $.ajax({ type: "POST", async: true, url: '/Home/PrepareMetaData', data: { 'blocksCount': uploader.totalBlocks, 'fileName': uploader.file.name, 'fileSize': uploader.file.size }, dataType: "json", error: function () { uploader.displayLabel(operationType.METADATA_FAILED); uploader.resetControls(); }, success: function (operationState) { if (operationState === true) { sendFile(blockLength); } } }); ... }
      
    2. Implemente PrepareMetadata Action en Inicio controlador.

      [HttpPost] public ActionResult PrepareMetaData(int blocksCount, string fileName, long fileSize) { var container = CloudStorageAccount.Parse(ConfigurationManager.AppSettings[Constants.ConfigurationSectionKey]).CreateCloudBlobClient().GetContainerReference(Constants.ContainerName); container.CreateIfNotExist(); var fileToUpload = new FileUploadModel() { BlockCount = blocksCount, FileName = fileName, FileSize = fileSize, BlockBlob = container.GetBlockBlobReference(fileName), StartTime = DateTime.Now, IsUploadCompleted = false, UploadStatusMessage = string.Empty }; Session.Add(Constants.FileAttributesSession, fileToUpload); return Json(true); }
      
  3. Cargar los fragmentos del archivo.

    1. Crear una función que envía fragmentos de archivo al servidor como FormData con un identificador de fragmento incremental. No utilice la XMLHttpRequest para enviar directamente las solicitudes como Equilibrio de carga de red (NLB) puede quitar encabezados de solicitud que la información de fragmento no tiene sentido. Tenga en cuenta que actualmente cada explorador implementa de forma distinta la función de división de HTML5, por lo que se añade el prefijo de proveedor a la función.

      • Para FireFox: mozslice()

      • Cromo: webkitslice()

      var sendFile = function (blockLength) { ... sendNextChunk = function () { fileChunk = new FormData(); uploader.renderProgress(incrimentalIdentifier); if (uploader.file.slice) { fileChunk.append('Slice', uploader.file.slice(start, end)); } else if (uploader.file.webkitSlice) { fileChunk.append('Slice', uploader.file.webkitSlice(start, end)); } else if (uploader.file.mozSlice) { fileChunk.append('Slice', uploader.file.mozSlice(start, end)); } else { uploader.displayLabel(operationType.UNSUPPORTED_BROWSER); return; } jqxhr = $.ajax({ async: true, url: ('/Home/UploadBlock/' + incrimentalIdentifier), data: fileChunk, cache: false, contentType: false, processData: false, type: 'POST', error: function (request, error) { ... }, success: function (notice) { ... } }); }; sendNextChunk(); };
      
    2. Implemente un UploadBlock acción en Inicio controlador que toma un identificador incremental como parámetro, carga el fragmento y envía JSON mensaje a la secuencia de comandos que indica el estado de la operación. Si el identificador se incrementa hasta el último bloque, a continuación, confirma todos los bloques enviando un PutBlockList solicitud.

      [HttpPost] [ValidateInput(false)] public ActionResult UploadBlock(int id) { byte[] chunk = new byte[Request.InputStream.Length]; Request.InputStream.Read(chunk, 0, Convert.ToInt32(Request.InputStream.Length)); if (Session[Constants.FileAttributesSession] != null) { var model = (FileUploadModel)Session[Constants.FileAttributesSession]; using (var chunkStream = new MemoryStream(chunk)) { var blockId = Convert.ToBase64String(Encoding.UTF8.GetBytes(string.Format(CultureInfo.InvariantCulture, "{0:D4}", id))); try { model.BlockBlob.PutBlock(blockId, chunkStream, null, new BlobRequestOptions() { RetryPolicy = RetryPolicies.Retry(3, TimeSpan.FromSeconds(10)) }); } catch (StorageException e) { ... return Json(new { error = true, isLastBlock = false, message = model.UploadStatusMessage }); } } if (id == model.BlockCount) { ... try { var blockList = Enumerable.Range(1, (int)model.BlockCount).ToList<int>().ConvertAll(rangeElement => Convert.ToBase64String(Encoding.UTF8.GetBytes(string.Format(CultureInfo.InvariantCulture, "{0:D4}", rangeElement)))); model.BlockBlob.PutBlockList(blockList); ... } catch (StorageException e) { ... } finally { Session.Clear(); } return Json(new { error = errorInOperation, isLastBlock = model.IsUploadCompleted, message = model.UploadStatusMessage }); } } else { return Json(new { error = true, isLastBlock = false, message = string.Format(Resources.FailedToUploadFileMessage, Resources.SessonExpired) }); } return Json(new { error = false, isLastBlock = false, message = string.Empty }); }
      
  4. Recursivamente llame a la función de JavaScript desde el controlador de eventos de éxito de JQueryXHR en sendNextChunk() hasta que se alcanza el final del archivo. Si el servidor notifica un error, a continuación, analizar el JSON de mensajes y anular la operación. Si el paquete no puede tener acceso al servidor (controlador de eventos de error de JQueryXHR), vuelva a intentar cargar el fragmento de retrasos de hasta que el número máximo de reintentos de visitas y, a continuación, anula la operación.

    sendNextChunk = function () { ... jqxhr = $.ajax({ ... error: function (request, error) { if (error !== 'abort' && retryCount < maxRetries) { ++retryCount; setTimeout(sendNextChunk, retryAfterSeconds * 1000); } if (error === 'abort') { ... } else { if (retryCount === maxRetries) { ... } else { uploader.displayLabel(operationType.RESUME_UPLOAD); } } return; }, success: function (notice) { if (notice.error || notice.isLastBlock) { ... return; } ++incrimentalIdentifier; start = (incrimentalIdentifier - 1) * blockLength; end = Math.min(incrimentalIdentifier * blockLength, uploader.file.size) - 1; retryCount = 0; sendNextChunk(); } }); }; sendNextChunk(); };
    
  5. Si todos los identificadores incrementales alcanza el recuento total de bloque, la UploadBlock acción en el Inicio controlador confirmará los bloques enviando un PutBlockList solicitud.

  6. Para cancelar la operación en cualquier momento, cancelar la solicitud de AJAX actual al que ha asociado un identificador mediante una llamada a abort() en la solicitud.

    var sendFile = function (blockLength) { ... jqxhr = $.ajax({ ... } }); }; ... }; cancelUpload = function () { if (jqxhr !== null) { jqxhr.abort(); }};
    

Escribí una nota de campo sobre la Silverlight versión del archivo de control de carga. Ambos controles solucionan el problema de una carga eficiente a través de diferentes enfoques. Las principales diferencias se resumen en la tabla siguiente:

Diferencia

Versión de Silverlight

Versión de HTML5

Dependencia de explorador

Se ejecuta en todos los exploradores compatibles con Silverlight

Se ejecuta en el menor número posible de exploradores, ya que los estándares de HTML5 apenas están implementados

Modo de carga de archivos

Paralelo

Secuencial

Exposición de las claves de la cuenta de almacenamiento

SAS se expone al control

No tiene conocimiento de las claves de cuenta

Tiempo necesario para representar el control

Alto

Bajo

Componente responsable de la carga

Control en el cliente

Controlador de servidor

Reintentos erróneos en el bloque

Se admite en el cliente

Se admite en el servidor y en el cliente

Requisitos de memoria de cliente

Alto

Bajo

Tiempo necesario para cargar archivo

Bajo

Alto

Utilización del ancho de banda

Alto

Bajo

Tamaño de archivo admitido

Mediana

Alto

  1. Para realizar cargas de fragmento en paralelo en lugar de la implementación secuencial actual, puede enviar todas XmlHttpRequests al mismo tiempo en lugar de comprobar la entrega correcta de cada paquete prenominal aunque esto podría obstruir el servidor si no hay un número elevado de paquetes.

  2. Si se utiliza el almacenamiento en caché de AppFabric para mantener los metadatos de archivo que se va a cargar, el control puede escalar fácilmente sin necesidad de modificaciones.

  3. Se puede agregar compatibilidad para la carga de varios archivos llevando a cabo el proceso de carga para cada archivo.

Mostrar:
© 2016 Microsoft