Search code examples
javascriptc#asp.netazurexmlhttprequest

XHR progress bar for file upload for request AND response


I want to show the upload progress of a file through to the time the request completes from the server.

Every single example out there just shows how to do a progress bar for sending the file from browser to server and not from browser to server back to browser.

The code I have below only shows the progress of the client uploading the file to the server and not the response. Basically as soon as the request is hit on the server the progress event on the client completes which isn't what I want.

How do I do this?

xhr:

export default (url, opts = {}, uploadEvents) =>
  new Promise((res, rej) => {
    const xhr = new XMLHttpRequest();

    xhr.open(opts.method || 'get', url, true);

    Object.keys(opts.headers || {}).forEach((key) => {
      xhr.setRequestHeader(key, opts.headers[key]);
    });

    xhr.onload = e => res(e.target.responseText);
    xhr.onerror = rej;

    if (xhr.upload) {
      Object.keys(uploadEvents).forEach((key) => {
        xhr.upload.addEventListener(key, uploadEvents[key]);
      });
    }

    xhr.send(opts.body);
  });

Request:

fetchProgress('/upload/upload', {
  method: 'post',
  body: formData,
}, {
  progress: (e) => {
    if (e.lengthComputable) {
      const progressPercent = parseInt((e.loaded / e.total) * 100, 10);

      progressPercents[i] = {
        value: progressPercent,
        id,
      };

      dispatch({
        type: 'ADD_UPLOAD_PROGRESS',
        progressPercents,
      });
    }
  },
});

Server code:

[HttpPost]
public async Task<IActionResult> Upload(IFormFile file)
{
    var processAudio = await _fileStorage.TempStoreMp3Data(file);
    var audioBlob = _cloudStorage.GetBlob(CloudStorageType.Audio, processAudio.AudioName);

    await audioBlob.UploadFromPathAsync(processAudio.AudioPath, ProcessAudio.AudioContentType);

    return Ok();
}

Solution

  • Basically as soon as the request is hit on the server the progress event on the client completes which isn't what I want.

    To get the progress based on server side operation, I suggest you create another action which could return the progress on server side. On client side, you need to invoke this action to get the progress. Code below is for your reference.

    On server side, we need to define a Dictionary to store the progresses and we need to use a uploadId to connect the two actions. When invoke these 2 actions, we need to use the same uploadId from client side.

    //use this dictionary to store the progress for upload
    private static Dictionary<string, int> uploadProgresses = new Dictionary<string, int>();
    
    public IActionResult GetUploadProgress(string uploadId)
    {
        int progress = 0;
        uploadProgresses.TryGetValue(uploadId, out progress);
        return Content(progress.ToString());
    }
    
    
    [HttpPost]
    public async Task<IActionResult> Upload(IFormFile file, string uploadId)
    {
        uploadProgresses.Add(uploadId, 0);
        var processAudio = await _fileStorage.TempStoreMp3Data(file);
        uploadProgresses[uploadId] = 20;
        var audioBlob = _cloudStorage.GetBlob(CloudStorageType.Audio, processAudio.AudioName);
        uploadProgresses[uploadId] = 40;
        await audioBlob.UploadFromPathAsync(processAudio.AudioPath, ProcessAudio.AudioContentType);
        uploadProgresses[uploadId] = 100;
        return Ok();
    }
    

    Client side code sample.

    var myVar = window.setInterval(function () { getUploadProgress(); }, 1000);
    
    var uniqualId = uuidv4();
    
    document.getElementById("uuid").innerHTML = uniqualId;
    
    function uuidv4() {
        return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
            var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
            return v.toString(16);
        });
    }
    
    function getUploadProgress() {
        var xhttp = new XMLHttpRequest();
        xhttp.onreadystatechange = function () {
            if (this.readyState == 4 && this.status == 200) {
                //update your progress bar
                document.getElementById("progress").innerHTML = xhttp.responseText;
                if (xhttp.responseText == '100') {
                    myStopFunction();
                }
            }
            else if (this.readyState == 4 && this.status != 200)
            { 
                myStopFunction();
            }
        };
        xhttp.open("GET", "/Home/GetUploadProgress?uploadId=" + uniqualId, true);
        xhttp.send();
    }
    
    function myStopFunction() {
        window.clearInterval(myVar);
    } 
    

    Update 7/10/2017

    Should we be using Session to store the upload progress instead of a static dictionary?

    No, we can't use Session to store the upload progress. According the lifecycle of ASP.NET application, the state(Session) will be updated after the handler(your Action) be executed. It means the Session will not be updated until your Action executed completed.

    enter image description here