2015-09-21 7 views
9

Die Aufgabe, die ich ausführen möchte, besteht darin, einen Web-API-Dienst zu erstellen, um eine Datei in den Azure-Speicher hochzuladen. Gleichzeitig möchte ich einen Fortschrittsanzeiger haben, der den tatsächlichen Upload-Fortschritt widerspiegelt. Nach einigen Recherchen und Studium i zwei wichtige Dinge herausgefunden:Web API - Fortschritt beim Hochladen in Azure-Speicher abrufen

Erste ist, dass ich die Datei manuell in Stücke geteilt haben, und laden Sie asynchron die PutBlockAsync Methode von Microsoft.WindowsAzure.Storage.dll verwenden.

Zweitens ist, dass ich die Datei in meinem Web-API-Dienst im gestreamten Modus und nicht im gepufferten Modus erhalten muss.

Also ich bis jetzt haben die folgende Umsetzung:

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

ich auch gestreamte Modus als this Blog-Eintrag aktiviert schlägt vor. Dieser Ansatz funktioniert hervorragend, wenn die Datei erfolgreich in den Azure-Speicher hochgeladen wird. Dann, wenn ich diesen Dienst unter Verwendung von XMLHttpRequest anrufe (und das Fortschrittsereignis abonniere), sehe ich, dass der Indikator sehr schnell zu 100% wechselt. Wenn eine 5MB-Datei etwa 1 Minute zum Hochladen benötigt, bewegt sich meine Anzeige in nur 1 Sekunde zum Ende. Das Problem liegt wahrscheinlich in der Art und Weise, wie der Server den Client über den Upload-Fortschritt informiert. Irgendwelche Gedanken dazu? Vielen Dank.

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

, dass der JavaScript-Code ist ich den Dienst

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, wie abonnierst du das Fortschrittsereignis? –

Antwort

6

Ihre Statusanzeige zu verwenden Anruf könnte sein, schnell schnell, bewegen könnte sein, weil die

public async Task<HttpResponseMessage> UploadFile() 

mir diese vorher begegnet ist, wenn ein api asynchrone Art zu schaffen, im nicht einmal sicher, ob es erwartet werden kann, wird es nur von cour Beenden Sie einfach Ihren API-Anruf im Hintergrund, damit Ihre Fortschrittsanzeige aufgrund der asynchronen Methode (Feuer und Vergessen) sofort beendet wird. Die API wird Ihnen sofort eine Antwort geben, wird aber auf dem Server-Hintergrund enden (wenn nicht erwartet).

freundlich bitte versuchen Sie es nur

public HttpResponseMessage UploadFile() 

und versuchen, auch diese hier

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

ODER

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

hoffe, es hilft zu machen.

+0

Vielen Dank für Ihre Antwort. Ihr Vorschlag hat einen logischen Punkt, aber leider ist das Ergebnis dasselbe. Ich habe sogar versucht, alle meine Anrufe synchron laufen zu lassen, aber der Upload-Fortschritt ist nie richtig im Vergleich zum tatsächlichen Fortschritt. Ich habe auch in der Post den JavaScript-Aufruf hinzugefügt, den ich verwende, um meinen Upload-Service aufzurufen. Vielen Dank. –

+0

Sie sollten ".Result" nicht auf die Antwort aufrufen. Sie werden in Deadlocks geraten. Bitte warten Sie immer auf den Code. – Cody

2

Andere Möglichkeit, accomplish, was Sie wollen (ich verstehe nicht, wie die XMLHttpRequest Fortschrittsereignis funktioniert) verwendet , um den Upload-Fortschritt in der Anfrage zu erhalten.Um den Client zu benachrichtigen, können Sie dann einen Cache verwenden, um den Fortschritt zu speichern, und von der Clientanforderung den aktuellen Status an einem anderen Endpunkt abrufen oder SignalR verwenden, um den Fortschritt vom Server an den Client zu senden :

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

Sie mehr Dokumentation on this MSDN blog post (Blättern Sie nach unten zu „Progress Notifications“ Abschnitt)

prüfen Sie auch, können Sie den Fortschritt auf der Grundlage der Datenblöcke berechnen könnte, speichern Sie die Fortschritte in einem Cache, und benachrichtigen in der gleichen Weise wie oben. Something like this solution

+0

Die Verwendung von SignalR war ein Gedanke, den ich auch hatte, aber ich wollte es vermeiden, nur um den Fortschritt der Berichterstattung zu schütteln. Aber es scheint der einzige Weg zu sein, den ich habe. Vielen Dank. –

Verwandte Themen