2015-09-21 12 views
9

Tác vụ tôi muốn hoàn thành là tạo dịch vụ API Web để tải tệp lên bộ nhớ Azure. Đồng thời, tôi muốn có chỉ báo tiến trình phản ánh tiến trình tải lên thực tế. Sau khi một số nghiên cứu và học tập tôi phát hiện ra hai điều quan trọng:API Web - Nhận tiến trình khi tải lên bộ nhớ Azure

Đầu tiên là tôi phải chia file bằng tay vào khối, và tải chúng lên không đồng bộ sử dụng phương pháp PutBlockAsync từ Microsoft.WindowsAzure.Storage.dll.

Thứ hai, là tôi phải nhận tệp trong dịch vụ API Web của tôi ở chế độ Đã phát trực tuyến và không ở chế độ Đệm.

Vì vậy, cho đến bây giờ tôi có việc thực hiện sau đây:

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

Tôi cũng bật chế độ Truyền như this bài viết trên blog gợi ý. Cách tiếp cận này hoạt động tuyệt vời, trong điều kiện tập tin được tải lên thành công để lưu trữ Azure. Sau đó, khi tôi thực hiện cuộc gọi đến dịch vụ này sử dụng số XMLHttpRequest (và đăng ký sự kiện tiến độ), tôi thấy chỉ báo chuyển sang 100% rất nhanh. Nếu một tệp 5MB cần khoảng 1 phút để tải lên, chỉ báo của tôi sẽ di chuyển đến cuối chỉ sau 1 giây. Vì vậy, có thể sự cố nằm trong cách máy chủ thông báo cho khách hàng về tiến trình tải lên. Bất kỳ suy nghĩ về điều này? Cảm ơn bạn.

================================ Cập nhật 1 ============ =======================

Đó là mã JavaScript tôi sử dụng để gọi dịch vụ

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, bạn đăng ký với sự kiện tiến trình như thế nào? –

Trả lời

6

chỉ số tiến bộ của bạn có thể là di chuyển nhanh chóng, có thể là do

public async Task<HttpResponseMessage> UploadFile() 

tôi đã gặp phải điều này trước đây, khi tạo api của loại không đồng bộ, tôi không chắc liệu nó có thể được chờ đợi hay không chỉ cần kết thúc cuộc gọi api của bạn trên nền, lý do chỉ báo tiến trình của bạn ngay lập tức kết thúc, vì phương pháp async (lửa và quên). api sẽ ngay lập tức cung cấp cho bạn một phản hồi, nhưng thực sự sẽ kết thúc trên nền máy chủ (nếu không được chờ đợi).

xin vui lòng thử làm cho nó chỉ

public HttpResponseMessage UploadFile() 

và cũng cố gắng những cái

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

HOẶC

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

hy vọng nó giúp.

+0

Cảm ơn bạn đã trả lời. Đề xuất của bạn có một điểm hợp lý nhưng tiếc là kết quả là như nhau. Tôi thậm chí đã cố gắng làm cho tất cả các cuộc gọi của tôi hoạt động đồng bộ nhưng tiến trình tải lên không bao giờ chính xác so với tiến độ thực tế. Tôi cũng đã thêm vào trong cuộc gọi JavaScript mà tôi sử dụng để gọi dịch vụ tải lên của mình. Cảm ơn bạn. –

+0

Bạn không nên gọi '.Result' trên phản hồi. Bạn sẽ chạy vào deadlocks. Thay vào đó, hãy luôn chờ mã. – Cody

2

Cách khác để hoàn thành những gì bạn muốn (Tôi không hiểu sự kiện tiến độ của XMLHttpRequest hoạt động như thế nào) đang sử dụng ProgressMessageHandler để nhận tiến trình tải lên trong yêu cầu.Sau đó, để thông báo cho khách hàng, bạn có thể sử dụng một số bộ nhớ cache để lưu trữ tiến trình và từ yêu cầu máy khách trạng thái hiện tại ở điểm cuối khác hoặc sử dụng SignalR để gửi tiến trình từ máy chủ đến máy khách

:

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

bạn có thể kiểm tra nhiều tài liệu on this MSDN blog post (Cuộn xuống "Thông báo Progress" phần)

Ngoài ra, bạn có thể tính toán tiến độ dựa trên các khối dữ liệu, lưu trữ các tiến bộ trong một bộ nhớ cache, và thông báo theo cách tương tự như trên. Something like this solution

+0

Sử dụng SignalR là một ý nghĩ mà tôi cũng có, nhưng tôi muốn tránh nó chỉ vì sự rung chuyển của tiến trình báo cáo. Nhưng nó có vẻ là cách duy nhất tôi có. Cảm ơn bạn. –

Các vấn đề liên quan