1

I have a MVC4 application with

  • A View who use bootstrap progress bar ( It's a partial view ) and Kendo UI for upload a file like this :

    @using MyApplication.Web.Resources.Views.Contact;
    <div class="row">
        <div class="span6">
    
             <form class="form-horizontal well" method="post" action="@Url.Action("ImportContact","ContactAsync")">
                  @Html.ValidationSummary(true)
    
                  <script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
                  <script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
    
    
                  <fieldset>
                      <legend>Contact</legend>
    
                      <p>
                          Some text...
                      </p>
    
                      @(Html.Kendo().Upload()
                      .Name("files"))
    
    
                       <div class="progress progress-striped">
                             <div class="bar" style="width: 0%;"></div>
                       </div>
    
                       <div class="form-actions"> 
                           <button type="submit" class="btn btn-primary" onclick="importMessage()">@Contact.ValidationButton</button>
                           <button type="submit" class="btn btn-secondary" onclick="window.location.href='@Url.Action("Index")';return false;">@Contact.CancelButton</button>
                       </div> 
               </fieldset>
            </form>
        </div>
    </div>   
    
  • And a Async Controller like this :

    public class ContactAsyncController : AsyncController
    {
        private BackgroundWorker worker = new BackgroundWorker();
    
        public ContactAsyncController(IContactService cs)
        {
            _contactService = cs;
        }
    
        //
        // POST: /Contact/ImportContactAsync
        [HttpPost]
        public void ImportContactAsync(IEnumerable<HttpPostedFileBase> files)
        {
            AsyncManager.OutstandingOperations.Increment();
    
            worker.WorkerReportsProgress = true;
            worker.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);
            worker.DoWork += (o, e) => ImportContact(files, e);
            worker.RunWorkerCompleted += (o, e) =>
                {
                    AsyncManager.OutstandingOperations.Decrement();
                };
    
            worker.RunWorkerAsync();
        }
    
        private void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            Debug.WriteLine(e.ProgressPercentage.ToString() + "%");
        }
    
        private void ImportContact(IEnumerable<HttpPostedFileBase> files, DoWorkEventArgs e)
        {
            try
            {
                if (files != null)
                {
                    string path = "";
                    string extension = "";
    
                    foreach (HttpPostedFileBase file in files)
                    {
                        if (file != null)
                        {
                            // New changes - first save the file on the server
                            file.SaveAs(Path.Combine(Server.MapPath("~/Temp/Import"), file.FileName));
    
                            // Now create a path to the file 
                            path = Path.Combine(Server.MapPath("~/Temp/Import"), file.FileName);
    
                            extension = Path.GetExtension(file.FileName);
                        }
                        else
                        {
                            // ...
                        }
                    }
    
                    if (extension == ".pst") // PST
                    {
                        ImportContactPst(path);
                    }
                    else
                    {
                        // ...
                    }
                }
            }
            catch
            {
                ViewBag.Error = myApplication.Web.Resources.Views.Contact.Contact.ErrorMessage;
            }
        }
    
        private void ImportContactPst(string path)
        {
             // Some code ...
    
             Session["test"] = // My percentage
        }
    
        public ActionResult ImportContactCompleted()
        {
            return RedirectToAction("Index","Contact");
        }
    }
    

And I want to make progress my progress bar. So for this, I thought to do a script in my view like this :

<script>
    $(document).ready(function () {
        var progresspump = setInterval(function () {
            /* query the completion percentage from the server */
            $.ajax({
                type: "GET",
                url: "@Url.Action("GetProgressBar")",
                dataType: "json",
                cache: false,
                success: function (data) {
                    var percentComplete = parseInt(data.PercentComplete);
                    if (percentComplete == null || percentComplete == 100) {
                        $(".progress").removeClass("active"); // we're done!
                        $(".progress .bar").css("width", "100%");
                    } else { // update the progress bar
                        $(".progress").addClass("active");
                        $(".progress .bar").css("width", percentComplete + "%");
                    }
                },
                error: function (xhr, textStatus, thrownError) {
                    console.log(xhr.statusText);
                    console.log(xhr.responseText);
                    console.log(xhr.status);
                    console.log(thrownError);
                }
            });
        }, 3000);
    });
</script>

Where GetProgressBar give me the perrcentage that I want, but it doesn't work because this method wait that the asynchronous method ( ImportContact ) finishes to do his job ... There are an update of the progress bar only at the end of the upload method.

public ActionResult GetProgressBar()
{
    try
    {
        if (this.Session["test"] != null)
        {
            return Json(new { PercentComplete = this.Session["test"] }, JsonRequestBehavior.AllowGet);
        }
        else
            return Json(0, JsonRequestBehavior.AllowGet);
    }
    catch
    {
        ViewBag.Error = myApplication.Web.Resources.Views.Contact.Contact.ErrorMessage;
        return View();
    }

}

So, as you can see ( at the beginning ), I have implemented the progressChanged event but I don't know how to use it to update my progress bar in my View... Can you help me ?

Thank you all for reading and trying to understand this, if you want more informations please tell me.

Frédéric Guégan
  • 109
  • 1
  • 2
  • 7
  • 1
    As I can see you pulling the requests? You can use an Realtime js that keeps connection per session that way you don't need to create pooling. Use signalR and other socketed frameworks. – hackp0int Jul 24 '13 at 08:57

1 Answers1

0

I am not sure it can work that way. Between client and server events do not work as they work in a Form Application for example. What I would do is to have javascript periodically check the backend for the state of progress made and update the visual bar.

You could do that with a timer and check every couple of seconds like

setInterval(function(){/*do the ajax call here*/},2000);

I did some digging and it turns out that using a backgroundworker might not be the best option as it is not well suited for MVC. I found out a solution that works which uses a secondary thread. Please have a look here:

http://blog.janjonas.net/2012-01-02/asp_net-mvc_3-async-jquery-progress-indicator-long-running-tasks

For the shake of completeness the whole part is below regarding the controller. The javascript is pretty self explaining.

    public ActionResult Start()
  {
    var taskId = Guid.NewGuid();
    tasks.Add(taskId, 0);

    Task.Factory.StartNew(() =>
    {
      for (var i = 0; i <= 100; i++)
      {
        tasks[taskId] = i; // update task progress
        Thread.Sleep(50); // simulate long running operation
      }
      tasks.Remove(taskId);
    });

    return Json(taskId);
   }

   public ActionResult Progress(Guid id)
   {
     return Json(tasks.Keys.Contains(id) ? tasks[id] : 100);
   }
   }
idipous
  • 2,868
  • 3
  • 30
  • 45
  • It's already what I tried to do but the ajax call wait for the other method – Frédéric Guégan Jul 22 '13 at 12:51
  • Do not try to do is as an event rather as an action (method) in the controller and call that action with the setinterval – idipous Jul 22 '13 at 14:13
  • Sorry I was not clear, it's what I tried to do but the method with the setinterval wait for the other method ( upload ) – Frédéric Guégan Jul 23 '13 at 07:30
  • From you code it doesn't seem that while you are uploading the file you are doing anything with the bar. The bar comes into play after you have uploaded the file and you have started the contact import from the pst file. Correct? If I am correct then you have to show the code in importPST and update the session at intervals while you are importing the contacts in that function. – idipous Jul 23 '13 at 08:19
  • I have updated this too, in importPST, I have some loop so I update here Session["test"] for the GetProgressBar method. But it's very long so I can't show the code – Frédéric Guégan Jul 23 '13 at 08:28
  • Ok here is a dump question.... how long does it take for the import to happen. Less than 3 seconds? If yes then this might be why you only see 100% or 0 since you are only checking for the import of contacts. Please use the debug and step through the method to check the value of session in both getprogressbar and importpst methods. – idipous Jul 23 '13 at 08:35
  • It takes around 1 to 4 minutes ( depending on the file ). The value of session in importpst is well updated but I can't check it in getprogressbar because it's blocked at the beginning of the method and I don't know why – Frédéric Guégan Jul 23 '13 at 08:56
  • hmmm.. I am not sure I get it. If you set a break point on line `if (this.Session["test"] != null)` do you ever visit hit it? You should since the ajax request hits that method every three sec. – idipous Jul 23 '13 at 09:04
  • No I never visit it, that's the problem. It looks like the ajax request wait for my other method. – Frédéric Guégan Jul 23 '13 at 09:07
  • No this is not the problem. The problem is with the javascript then. Try using firebug to see if you hit the right method or you get an error (in the net tab of firebug). Also try to change the url: "@Url.Action("GetProgressBar")", to url: '@Url.Action("GetProgressBar", "ContactAsync")', – idipous Jul 23 '13 at 09:14
  • Since I don't upload my file, the ajax request is ok but when I push the upload button, nothing append in the net tab of firebug, I can see in the console of firebug that there are some requests of ProgressBar but they are waiting – Frédéric Guégan Jul 23 '13 at 09:20