Vimalan Jaya Ganesh Vimalan Jaya Ganesh - 3 months ago 14
TypeScript Question

Sending intermittent response from ASP.NET Web API method to jquery ajax

I am working on a task where I need to send intermittent status response from Web API method back to the jquery ajax call and display the progress in UI.

enter image description here

Initially, the jquery ajax will call the web Api method passing some parameters and the web Api method starts to perform long running actions. After each action is complete, I want to send a percentage (manually, some number) back to the calling jquery ajax method and show the progress in UI,

What I have tried so far,

HTML:

<div class="row">
<div class="col-xs-12">
<div class="col-xs-3">
<input type="file" id="FileInput" />
</div>
<div class="col-xs-1">
<button type="button" class="btn btn-default btn-xs" id="UploadFileBtn">Upload</button>
</div>
</div>
</div>


Typescript:

instance.importFile.on('change', function () {
instance.selectedFile = this.files[0];
// This code is only for demo ... (usage of FileAPI)
console.log("name : " + instance.selectedFile.name);
console.log("size : " + instance.selectedFile.size);
console.log("type : " + instance.selectedFile.type);
console.log("date : " + instance.selectedFile.lastModifiedDate);
});

$('#UploadFileBtn').on('click', function () {
var formData = new FormData();
formData.append('file', instance.selectedFile);

$.when(FileUploadService.ProcessData(formData)).done(function () {
}).fail(function () {
}).progress(function () {
console.log("progressing...");
});
});


Web API:

public class FileUploadController : ApiController
{
[HttpPost]
public HttpResponseMessage Upload()
{

if (HttpContext.Current.Request.Files.Count > 0)
{
var postedFile = HttpContext.Current.Request.Files[0];

var fileNameParts = postedFile.FileName.Split('\\');
var fileName = fileNameParts[fileNameParts.Length - 1];

fileName = string.Format("{0}_{1}", DateTime.Now.Ticks.ToString(), fileName);
string filePath = Path.Combine("c:\\temp", fileName);
postedFile.SaveAs(filePath);
}

var response = new HttpResponseMessage(HttpStatusCode.OK);
response.Content = new StringContent("UPLOADED");

System.Threading.Thread.Sleep(5000);

return response;
}
}


Question

Now, I have the web api method but, I am not sure how to send the intermittent responses back to UI. I am looking for simple solutions. Any suggestion / example is appreciated.

Answer

I followed the comments from Jason and able to do polling using a separate ajax call and get the intermittent response.

In the code below, I am trying to upload a file (using jquery.form plugin) to server and perform a long running tasks at the controller level. In order to show progress to the users, the polling concept is used to check the status of the long running tasks.

Here is the code,

ASP.NET HTML:

<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" runat="server">
    <div class="row">
        <div class="col-xs-12">
            <div class="alert alert-warning">This page uses jquery.form plugin to upload</div>
        </div>
    </div>
</asp:Content>
<asp:Content ID="Content3" ContentPlaceHolderID="NoFormContentPlaceHolder" runat="server">

    <div class="row">
        <div class="col-xs-12">
            <form id="ImportFileForm" method="post" action="api/FileUpload/Upload" enctype="multipart/form-data">
                <div class="col-xs-3">
                    <input type="file" id="ImportFile" name="ImportFile" accept="*.xlsx" class="col-xs-12 file-selector" />
                </div>
                <div class="col-xs-1">
                    <button class="btn btn-danger btn-xs" id="ImportFileBtn" type="submit" title="Import"><span class="glyphicon glyphicon-import"></span>Import</button>
                </div>   
            </form>
        </div>
    </div>
    <div class="row row-margin">
        <div class="col-xs-12">
            <div class="col-xs-3">
                <div id="UploadStatusMessage"></div>
                <div class="progress">
                    <div class="progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%">
                        0%
                    </div>
                </div>
            </div>
        </div>
        <div class="col-xs-12">
            <div class="alert alert-default" id="PollingStatusMessage"></div>
        </div>
    </div>
</asp:Content>
<asp:Content ID="Content1" ContentPlaceHolderID="head" runat="server">
     <%:Scripts.Render("~/Scripts/jQueryForm") %>
</asp:Content>

File Upload Controller CS

 public class FileUploadController : ApiController
    {
        [HttpPost] //this is demo, lets assume that user is uploading a file and in the same method, some long running operation is happening.
        public HttpResponseMessage Upload()
        {
            var requestId = HttpContext.Current.Request.Form["RequestTracker"];

            if (HttpContext.Current.Request.Files.Count > 0)
            {
                var postedFile = HttpContext.Current.Request.Files[0];

                var fileNameParts = postedFile.FileName.Split('\\');
                var fileName = fileNameParts[fileNameParts.Length - 1];

                fileName = string.Format("{0}_{1}", DateTime.Now.Ticks.ToString(), fileName);
                string filePath = Path.Combine("c:\\temp", fileName);
                postedFile.SaveAs(filePath);
            }

            //Note: usually, this will be some other operation (database insert/update, data validation etc)
            //Inorder to show the exact status / progress, the backend should store some indication which can be retrieved using another ajax call by polling

            ObjectCache cache = MemoryCache.Default;
            cache.Remove(requestId);//remove any previous for demo purpose

            CacheItemPolicy policy = new CacheItemPolicy();
            policy.AbsoluteExpiration = DateTime.Now.AddMinutes(10);

            List<int> taskStatus = new List<int>();

            //long running task 1

            for (int i = 0; i < 100; i++)
            {
                System.Threading.Thread.Sleep(100);
                taskStatus.Add(i);                
            }

            cache.Set(requestId, taskStatus, policy);

            //long running task 2

            for (int i = 0; i < 100; i++)
            {
                System.Threading.Thread.Sleep(100);
                taskStatus.Add(i);
            }

            cache.Set(requestId, taskStatus, policy);

            //long running task 3

            for (int i = 0; i < 100; i++)
            {
                System.Threading.Thread.Sleep(100);
                taskStatus.Add(i);
            }

            cache.Set(requestId, taskStatus, policy);

            //long running task 4

            for (int i = 0; i < 100; i++)
            {
                System.Threading.Thread.Sleep(100);
                taskStatus.Add(i);
            }

            cache.Set(requestId, taskStatus, policy);


            return Request.CreateResponse(HttpStatusCode.OK, "All Operations Completed");
        }
    }

Polling Controller CS

public class PollingController : ApiController
    {
        [HttpPost]
        public HttpResponseMessage GetStatus(UploadStatusRequest request)
        {
            ObjectCache cache = MemoryCache.Default;
            var fileUploadStatus  = cache.Get(request.RequestId) as List<int>;

            var count = 0;

            if (fileUploadStatus != null)
            {
                count = fileUploadStatus.Count;
            }

            return Request.CreateResponse(HttpStatusCode.OK, "Processed data : " + count + ". Please wait...");
        }
    }

TypeScript file:

namespace jQueryForm {

    export class FileUploadComponent {

        progressBar: JQuery = $('.progress-bar');
        importFile: JQuery = $('#ImportFile');
        uploadStatusMessage: JQuery = $('#UploadStatusMessage');
        pollingStatusMessage: JQuery = $('#PollingStatusMessage');
        pollingInstance: any = null;

        public RegisterEvents() {

            var instance: FileUploadComponent = this;

            instance.importFile.on('change', function () {
                instance.ResetProgressBar();
            });

            var requestStatusTracker = null;

            $('#ImportFileForm').ajaxForm({
                beforeSubmit: function (arr, $form, options) {

                    requestStatusTracker = new Date().getMilliseconds();

                    arr.push({ name: 'RequestTracker', value: requestStatusTracker });

                    return true;
                },
                beforeSend: function (xhr, options) {               

                    instance.ResetProgressBar();
                    instance.uploadStatusMessage.text('Uploading...'); 
                },                
                //Note: The uploadProgress method displays the status of file transfer progress to web api method. Once the file is completely transferred to Web API method, 
                //      the percentage will become 100. But, there could be other operations once the file is reached Web API and those are not counted.
                uploadProgress: function (event, position, total, percentComplete) {
                    var percentVal = percentComplete + '%';

                    instance.progressBar.css("width", percentVal).attr("aria-valuenow", percentComplete).text(percentVal);

                    if (percentComplete == 100) {

                        var uploadStatusRequest = new Entities.UploadStatusRequest();
                        uploadStatusRequest.RequestId = requestStatusTracker;

                        instance.Poll(uploadStatusRequest);
                        instance.uploadStatusMessage.text('File Upload Complete.');
                    }
                },
                success: function (data) {
                    //instance.ResetProgressBar();

                    //Note: all operations completed in the web api method and the success response is received from the controller
                    clearTimeout(instance.pollingInstance);
                    instance.pollingStatusMessage.html(data);

                },
                error: function (xhr) {

                },
                complete: function (xhr) {//controller has completed all the action

                }
            });
        }

        private ResetProgressBar() {
            this.progressBar.css("width", '0%').attr("aria-valuenow", 0).text('0%');
            this.uploadStatusMessage.empty();
        }

        private Poll(uploadStatusRequest) {

            var instance: FileUploadComponent = this;

            instance.pollingInstance = setTimeout(function () {
                $.ajax({
                    url: "api/Polling/GetStatus", data: uploadStatusRequest, dataType: "json", type: 'POST', success: function (data) {
                        //Update your status
                        instance.pollingStatusMessage.html(data);

                        //Setup the next poll recursively
                        instance.Poll(uploadStatusRequest);

                    }, error: function (xhr) {
                        instance.pollingStatusMessage.html(xhr.responseText);
                    }
                });
            }, 2000);
        }
    }
}
$(function () {

    var fileUploadComponent = new jQueryForm.FileUploadComponent();

    fileUploadComponent.RegisterEvents();
});

Output:

The progress is displayed to the user as shown below,

enter image description here