2015-09-21 26 views
9

我想要完成的任務是創建Web API服務,以便將文件上載到Azure存儲。同時,我想有一個反映實際上傳進度的進度指示器。經過一番調查和研究,我發現了兩個重要的事情:Web API - 上傳到Azure存儲時取得進展

首先是,我必須手動分割文件分割成塊,並將其上傳異步使用PutBlockAsync方法從Microsoft.WindowsAzure.Storage.dll

其次,是我必須在我的Web API服務中以流模式而不是緩衝模式接收文件。

所以,到現在爲止,我有以下實現:

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

我還啓用了流模式this博客文章提示。這種方法效果很好,因爲該文件已成功上載到Azure存儲。然後,當我使用XMLHttpRequest(並訂閱進度事件)撥打此服務的電話時,我看到該指示器非常迅速地移至100%。如果一個5MB文件需要大約1分鐘才能上傳,我的指標只需1秒即可完成。所以問題可能在於服務器通知客戶端上傳進度的方式。對此有何想法?謝謝。

================================更新1 ============ =======================

這是我用它來調用服務的JavaScript代碼

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,你如何訂閱進度事件? –

回答

6

進度指標可能是疾速快,可能是因爲

public async Task<HttpResponseMessage> UploadFile() 

我以前也遇到過這種,創建異步類型的API時,即時通訊甚至不知道這是否可以被期待已久的,它只會考爾的您只需在背景上完成您的API調用,由於異步方法(火和遺忘),您的進度指示器立即完成。該API會立即給你一個迴應,但實際上會在服務器背景上完成(如果沒有等待)。

請您嘗試使剛剛

public HttpResponseMessage UploadFile() 

,並嘗試這些的

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

OR

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

希望它幫助。

+0

謝謝你的回答。你的建議有一個邏輯點,但不幸的是結果是一樣的。我甚至試圖讓所有的通話都同步工作,但是與實際進度相比,上傳過程永遠不會正確。我還在帖子中添加了我用來調用我的上傳服務的JavaScript調用。謝謝。 –

+0

你不應該在響應中調用'.Result'。你會遇到死鎖。請始終等待代碼。 – Cody

2

其他方式來實現你想要的東西(我不明白XMLHttpRequest的進度事件是如何工作的)使用ProgressMessageHandler來獲取請求中的上傳進度。然後,爲了通知客戶端,可以使用一些緩存來存儲進度,並從客戶端請求其他端點的當前狀態,或者使用SignalR從服務器向客戶端發送進度

類似:

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

查看更詳細的文檔on this MSDN blog post(向下滾動到「進度通知」一節)

此外,你可以計算出基於數據塊的進步,存儲在緩存中的進展情況,並通知以與上述相同的方式。 Something like this solution

+0

使用SignalR是我也有過的想法,但我想避免它只是爲了報告進展的動搖。但它似乎是我擁有的唯一途徑。謝謝。 –

相關問題