2015-09-21 14 views
9

L'attività che si desidera eseguire è creare un servizio API Web per caricare un file nell'archivio di Azure. Allo stesso tempo, vorrei avere un indicatore di avanzamento che rifletta i progressi effettivi di caricamento. Dopo alcune ricerche e studiando ho scoperto due cose importanti:- Miglioramento durante il caricamento nell'archivio di Azure

prima è che devo dividere il file manualmente in pezzi, e caricarle in modo asincrono utilizzando il metodo PutBlockAsync da Microsoft.WindowsAzure.Storage.dll.

In secondo luogo, è necessario ricevere il file nel servizio API Web in modalità Streamed e non in modalità Buffered.

Così fino ad ora ho la seguente implementazione:

UploadController.cs

using System.Configuration; 
using System.Net; 
using System.Net.Http; 
using System.Threading.Tasks; 
using System.Web.Http; 
using Microsoft.WindowsAzure.Storage; 
using Microsoft.WindowsAzure.Storage.Blob; 
using WebApiFileUploadToAzureStorage.Infrastructure; 
using WebApiFileUploadToAzureStorage.Models; 

namespace WebApiFileUploadToAzureStorage.Controllers 
{ 
    public class UploadController : ApiController 
    { 
     [HttpPost] 
     public async Task<HttpResponseMessage> UploadFile() 
     { 
      if (!Request.Content.IsMimeMultipartContent("form-data")) 
      { 
       return Request.CreateResponse(HttpStatusCode.UnsupportedMediaType, 
        new UploadStatus(null, false, "No form data found on request.", string.Empty, string.Empty)); 
      } 

      var streamProvider = new MultipartAzureBlobStorageProvider(GetAzureStorageContainer()); 
      var result = await Request.Content.ReadAsMultipartAsync(streamProvider); 

      if (result.FileData.Count < 1) 
      { 
       return Request.CreateResponse(HttpStatusCode.BadRequest, 
        new UploadStatus(null, false, "No files were uploaded.", string.Empty, string.Empty)); 
      } 

      return Request.CreateResponse(HttpStatusCode.OK); 
     } 

     private static CloudBlobContainer GetAzureStorageContainer() 
     { 
      var storageConnectionString = ConfigurationManager.AppSettings["AzureBlobStorageConnectionString"]; 
      var storageAccount = CloudStorageAccount.Parse(storageConnectionString); 

      var blobClient = storageAccount.CreateCloudBlobClient(); 
      blobClient.DefaultRequestOptions.SingleBlobUploadThresholdInBytes = 1024 * 1024; 

      var container = blobClient.GetContainerReference("photos"); 

      if (container.Exists()) 
      { 
       return container; 
      } 

      container.Create(); 

      container.SetPermissions(new BlobContainerPermissions 
      { 
       PublicAccess = BlobContainerPublicAccessType.Container 
      }); 

      return container; 
     } 
    } 
} 

MultipartAzureBlobStorageProvider.cs

using System; 
using System.Collections.Generic; 
using System.Diagnostics; 
using System.IO; 
using System.Linq; 
using System.Net.Http; 
using System.Text; 
using System.Threading; 
using System.Threading.Tasks; 
using Microsoft.WindowsAzure.Storage.Blob; 

namespace WebApiFileUploadToAzureStorage.Infrastructure 
{ 
    public class MultipartAzureBlobStorageProvider : MultipartFormDataStreamProvider 
    { 
     private readonly CloudBlobContainer _blobContainer; 

     public MultipartAzureBlobStorageProvider(CloudBlobContainer blobContainer) : base(Path.GetTempPath()) 
     { 
      _blobContainer = blobContainer; 
     } 

     public override Task ExecutePostProcessingAsync() 
     { 
      const int blockSize = 256 * 1024; 
      var fileData = FileData.First(); 
      var fileName = Path.GetFileName(fileData.Headers.ContentDisposition.FileName.Trim('"')); 
      var blob = _blobContainer.GetBlockBlobReference(fileName); 
      var bytesToUpload = (new FileInfo(fileData.LocalFileName)).Length; 
      var fileSize = bytesToUpload; 

      blob.Properties.ContentType = fileData.Headers.ContentType.MediaType; 
      blob.StreamWriteSizeInBytes = blockSize; 

      if (bytesToUpload < blockSize) 
      { 
       var cancellationToken = new CancellationToken(); 

       using (var fileStream = new FileStream(fileData.LocalFileName, FileMode.Open, FileAccess.ReadWrite)) 
       { 
        var upload = blob.UploadFromStreamAsync(fileStream, cancellationToken); 

        Debug.WriteLine($"Status {upload.Status}."); 

        upload.ContinueWith(task => 
        { 
         Debug.WriteLine($"Status {task.Status}."); 
         Debug.WriteLine("Upload is over successfully."); 
        }, TaskContinuationOptions.OnlyOnRanToCompletion); 

        upload.ContinueWith(task => 
        { 
         Debug.WriteLine($"Status {task.Status}."); 

         if (task.Exception != null) 
         { 
          Debug.WriteLine("Task could not be completed." + task.Exception.InnerException); 
         } 
        }, TaskContinuationOptions.OnlyOnFaulted); 

        upload.Wait(cancellationToken); 
       } 
      } 
      else 
      { 
       var blockIds = new List<string>(); 
       var index = 1; 
       long startPosition = 0; 
       long bytesUploaded = 0; 

       do 
       { 
        var bytesToRead = Math.Min(blockSize, bytesToUpload); 
        var blobContents = new byte[bytesToRead]; 

        using (var fileStream = new FileStream(fileData.LocalFileName, FileMode.Open)) 
        { 
         fileStream.Position = startPosition; 
         fileStream.Read(blobContents, 0, (int)bytesToRead); 
        } 

        var manualResetEvent = new ManualResetEvent(false); 
        var blockId = Convert.ToBase64String(Encoding.UTF8.GetBytes(index.ToString("d6"))); 
        Debug.WriteLine($"Now uploading block # {index.ToString("d6")}"); 
        blockIds.Add(blockId); 
        var upload = blob.PutBlockAsync(blockId, new MemoryStream(blobContents), null); 

        upload.ContinueWith(task => 
        { 
         bytesUploaded += bytesToRead; 
         bytesToUpload -= bytesToRead; 
         startPosition += bytesToRead; 
         index++; 
         var percentComplete = (double)bytesUploaded/fileSize; 
         Debug.WriteLine($"Percent complete: {percentComplete.ToString("P")}"); 
         manualResetEvent.Set(); 
        }); 

        manualResetEvent.WaitOne(); 
       } while (bytesToUpload > 0); 

       Debug.WriteLine("Now committing block list."); 
       var putBlockList = blob.PutBlockListAsync(blockIds); 

       putBlockList.ContinueWith(task => 
       { 
        Debug.WriteLine("Blob uploaded completely."); 
       }); 

       putBlockList.Wait(); 
      } 

      File.Delete(fileData.LocalFileName); 
      return base.ExecutePostProcessingAsync(); 
     } 
    } 
} 

ho anche attivato la modalità streaming come this post sul blog suggerisce. Questo approccio funziona alla grande, dal momento che il file viene caricato correttamente nell'archivio di Azure. Quindi, quando effettuo una chiamata a questo servizio utilizzando lo XMLHttpRequest (e sottoscrivendo l'evento progress), l'indicatore si sposta rapidamente al 100%. Se un file da 5 MB richiede circa 1 minuto per il caricamento, il mio indicatore si sposta alla fine in appena 1 secondo. Quindi probabilmente il problema risiede nel modo in cui il server informa il cliente dell'avanzamento del caricamento. Qualche idea su questo? Grazie.

================================ Update 1 ============ =======================

Questo è il codice JavaScript che uso per chiamare il servizio

function uploadFile(file, index, uploadCompleted) { 
    var authData = localStorageService.get("authorizationData"); 
    var xhr = new XMLHttpRequest(); 

    xhr.upload.addEventListener("progress", function (event) { 
     fileUploadPercent = Math.floor((event.loaded/event.total) * 100); 
     console.log(fileUploadPercent + " %"); 
    }); 

    xhr.onreadystatechange = function (event) { 
     if (event.target.readyState === event.target.DONE) { 

      if (event.target.status !== 200) { 
      } else { 
       var parsedResponse = JSON.parse(event.target.response); 
       uploadCompleted(parsedResponse); 
      } 

     } 
    }; 

    xhr.open("post", uploadFileServiceUrl, true); 
    xhr.setRequestHeader("Authorization", "Bearer " + authData.token); 

    var data = new FormData(); 
    data.append("file-" + index, file); 

    xhr.send(data); 
} 
+0

Giorgios, come ti iscrivi all'evento progress? –

risposta

6

tuo indicatore di avanzamento potrebbe essere muoversi rapidamente veloce, potrebbe essere causa di

public async Task<HttpResponseMessage> UploadFile() 

ho incontrato prima, durante la creazione di un'API di tipo asincrono, im non sono nemmeno sicuro se può essere atteso, sarà solo di cour Se hai appena terminato la tua chiamata api sullo sfondo, ragiona subito il tuo indicatore di avanzamento, a causa del metodo asincrono (fuoco e dimentica). l'API ti darà immediatamente una risposta, ma in realtà finirà sullo sfondo del server (se non è attesa).

Vi preghiamo gentilmente di provare a fare solo

public HttpResponseMessage UploadFile() 

e anche provare questi

var result = Request.Content.ReadAsMultipartAsync(streamProvider).Result; 
var upload = blob.UploadFromStreamAsync(fileStream, cancellationToken).Result; 

O

var upload = await blob.UploadFromStreamAsync(fileStream, cancellationToken); 

spero che aiuta.

+0

Grazie per la risposta. Il tuo suggerimento ha un punto logico, ma sfortunatamente il risultato è lo stesso. Ho anche provato a far funzionare tutte le mie chiamate in modo sincrono, ma l'avanzamento del caricamento non è mai corretto rispetto al progresso effettivo. Ho anche aggiunto nel post la chiamata JavaScript che uso per chiamare il mio servizio di upload. Grazie. –

+0

Non dovresti chiamare '.Result' sulla risposta. Ti imbatterai in deadlock. Si prega di attendere sempre il codice, invece. – Cody

2

Un altro modo per ottenere ciò che si desidera (non capisco come funziona l'evento di avanzamento di XMLHttpRequest) sta utilizzando lo ProgressMessageHandler per ottenere l'avanzamento del caricamento nella richiesta.Quindi, per notificare al cliente, è possibile utilizzare un po 'di cache per memorizzare l'avanzamento e dal client richiedere lo stato corrente nell'altro endpoint, o utilizzare SignalR per inviare il progresso dal server al client

Qualcosa come :

//WebApiConfigRegister 
var progress = new ProgressMessageHandler(); 
progress.HttpSendProgress += HttpSendProgress; 
config.MessageHandlers.Add(progress); 
//End WebApiConfig Register 

    private static void HttpSendProgress(object sender, HttpProgressEventArgs e) 
    { 
     var request = sender as HttpRequestMessage; 
     //todo: check if request is not null 
     //Get an Id from the client or something like this to identify the request 
     var id = request.RequestUri.Query[0]; 
     var perc = e.ProgressPercentage; 
     var b = e.TotalBytes; 
     var bt = e.BytesTransferred; 
     Cache.InsertOrUpdate(id, perc); 
    } 

È possibile controllare più documentazione on this MSDN blog post (Scorrere verso il basso per "progresso Notifiche" sezione)

Inoltre, si potrebbe calcolare il progresso in base ai blocchi di dati, memorizzare i progressi in una cache, e notificare allo stesso modo di sopra. Something like this solution

+0

L'uso di SignalR era un pensiero che avevo anche io, ma volevo evitarlo solo per scuotere i progressi dei rapporti. Ma sembra essere l'unico modo che ho. Grazie. –

Problemi correlati