Хобрук: Ваш путь к мастерству в программировании

Веб-API — получение информации о ходе загрузки в хранилище Azure

Задача, которую я хочу выполнить, — создать службу веб-API для загрузки файла в хранилище Azure. В то же время я хотел бы иметь индикатор прогресса, который отражает фактический прогресс загрузки. После некоторых исследований и изучения я обнаружил две важные вещи:

Во-первых, мне нужно разбить файл вручную на куски и загрузить их асинхронно, используя Метод PutBlockAsync из Microsoft.WindowsAzure.Storage.dll.

Во-вторых, я должен получить файл в моей службе веб-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();
        }
    }
}

Я также включил режим потоковой передачи как -a-web-api-service.aspx" rel="nofollow">этот пост предлагает. Этот подход отлично работает с точки зрения того, что файл успешно загружен в хранилище Azure. Затем, когда я звоню в эту службу, используя XMLHttpRequest (и подписавшись на событие прогресса) я вижу, что индикатор очень быстро приближается к 100%. Если для загрузки файла размером 5 МБ требуется около 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);
}

  • Георгиос, как вы подписываетесь на событие прогресса? 28.09.2015

Ответы:


1

ваш индикатор прогресса может быстро двигаться быстро, может быть из-за

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

Надеюсь, поможет.

29.09.2015
  • Спасибо за ваш ответ. Ваше предложение имеет логическую основу, но, к сожалению, результат тот же. Я даже пытался заставить все мои вызовы работать синхронно, но ход загрузки никогда не был правильным по сравнению с фактическим ходом. Я также добавил в сообщение вызов JavaScript, который я использую для вызова моей службы загрузки. Спасибо. 03.10.2015
  • Вы не должны вызывать .Result в ответ. Вы попадете в тупики. Вместо этого всегда ждите код. 11.03.2017

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

    Вы можете проверить дополнительную документацию в этой записи блога MSDN (прокрутите вниз до раздела "Уведомления о ходе выполнения")

    Кроме того, вы можете рассчитать прогресс на основе фрагментов данных, сохранить прогресс в кеше и уведомить так же, как описано выше. Что-то вроде этого решения

    01.10.2015
  • У меня тоже была мысль использовать SignalR, но я хотел избежать этого только для того, чтобы встряхнуть сообщение о прогрессе. Но, похоже, это единственный способ, который у меня есть. Спасибо. 03.10.2015
  • Новые материалы

    Dall-E 2: недавние исследования показывают недостатки в искусстве, созданном искусственным интеллектом
    DALL-E 2 — это всеобщее внимание в индустрии искусственного интеллекта. Люди в списке ожидания пытаются заполучить продукт. Что это означает для развития креативной индустрии? О применении ИИ в..

    «Очень простой» эволюционный подход к обучению с подкреплением
    В прошлом семестре я посетил лекцию по обучению с подкреплением (RL) в моем университете. Честно говоря, я присоединился к нему официально, но я редко ходил на лекции, потому что в целом я нахожу..

    Освоение информационного поиска: создание интеллектуальных поисковых систем (глава 1)
    Глава 1. Поиск по ключевым словам: основы информационного поиска Справочная глава: «Оценка моделей поиска информации: подробное руководство по показателям производительности » Глава 1: «Поиск..

    Фишинг — Упаковано и зашифровано
    Будучи старшим ИТ-специалистом в небольшой фирме, я могу делать много разных вещей. Одна из этих вещей: специалист по кибербезопасности. Мне нравится это делать, потому что в настоящее время я..

    ВЫ РЕГРЕСС ЭТО?
    Чтобы понять, когда использовать регрессионный анализ, мы должны сначала понять, что именно он делает. Вот простой ответ, который появляется, когда вы используете Google: Регрессионный..

    Не зря же это называют интеллектом
    Стек — C#, Oracle Опыт — 4 года Работа — Разведывательный корпус Мне пора служить Может быть, я немного приукрашиваю себя, но там, где я живу, есть обязательная военная служба на 3..

    LeetCode Проблема 41. Первый пропущенный положительный результат
    LeetCode Проблема 41. Первый пропущенный положительный результат Учитывая несортированный массив целых чисел, найдите наименьшее пропущенное положительное целое число. Пример 1: Input:..