ASP.NET MVC 4 Async Controller Callback



Answers

This article seems to describe what you want:

ASP.NET MVC 3: Async jQuery progress indicator for long running tasks

Controller:

public class HomeController : Controller
{
    private static IDictionary<Guid, int> tasks = new Dictionary<Guid, int>();

    public ActionResult Index()
    {
        return View();
    }

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

View:

<script type="text/javascript">

function updateMonitor(taskId, status) {
  $("#" + taskId).html("Task [" + taskId + "]: " + status);
}

$(function () {
  $("#start").click(function (e) {
   e.preventDefault();
   $.post("Home/Start", {}, function (taskId) {

     // Init monitors
     $("#monitors").append($("<p id='" + taskId + "'/>"));
     updateMonitor(taskId, "Started");

     // Periodically update monitors
     var intervalId = setInterval(function () {
       $.post("Home/Progress", { id: taskId }, function (progress) {
         if (progress >= 100) {
           updateMonitor(taskId, "Completed");
         clearInterval(intervalId);
         } else {
           updateMonitor(taskId, progress + "%");
         }
        });
      }, 100);
    });
  });
});
</script> 
<div id="monitors"></div>
Question

I am just using the new Async Controller features in MVC 4 as described here http://www.asp.net/mvc/tutorials/mvc-4/using-asynchronous-methods-in-aspnet-mvc-4

If I have an action that may take 10-20 seconds to run I would like to provide some kind of status bar to notify the user of progress. Do the Async features have anything to help this out?

EDIT: I will take a stab at how I will try and do it and see if there are any better ways

public async Task<ActionResult> GizmosAsync()
{
    return View("Gizmos", await GetGizmosAsync());
}

private void GetGizmosAsync()
{
    for(int i=0; i<10; i++) 
    {
        lock(_locker) 
        {
           _statusMessage = String.Format("{0} of 10", i);
        }  
        DoSomethingLongRunning();
    }
}

public ActionResult Status()
{
   return Json(new { Status = _statusMessage });
}

static readonly object _locker = new object();

static string _statusMessage = "";

....

<script>

setTimeout(displayStatus, 1000);

function displayStatus() {
  $.post("/controller/status", function(data) {
    alert(data.Status);
  });
}

</script>



Async Controllers (MVC), long running process with “stops”

I was trying to solve something similar (reporting real-time progress from a long running server operation back to the client) recently and it turned out SignalR is a perfect fit for this situation.

Basically it is a library wrapping long-polling and Web Sockets, using (transparently) whatever is available on server and client.

I only have good experience with it so far.




Do not use "threads" in an web app. Let the server handle this by using "async" calls. Otherwise you would have to setup a threadpool and que the slow request.




This article seems to describe what you want, simple and w/o SignalR:

ASP.NET MVC 3: Async jQuery progress indicator for long running tasks

Controller:

public class HomeController : Controller { private static IDictionary tasks = new Dictionary();

 public ActionResult Index()
 {
   return View();
 }

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

}

View:

<script type="text/javascript">

function updateMonitor(taskId, status) {
  $("#" + taskId).html("Task [" + taskId + "]: " + status);
}

$(function () {
  $("#start").click(function (e) {
   e.preventDefault();
   $.post("Home/Start", {}, function (taskId) {

     // Init monitors
     $("#monitors").append($("<p id='" + taskId + "'/>"));
     updateMonitor(taskId, "Started");

     // Periodically update monitors
     var intervalId = setInterval(function () {
       $.post("Home/Progress", { id: taskId }, function (progress) {
         if (progress >= 100) {
           updateMonitor(taskId, "Completed");
         clearInterval(intervalId);
         } else {
           updateMonitor(taskId, progress + "%");
         }
       });
     }, 100);
   });
 });

});




For jQuery I use

jQuery.ajaxSetup({
  beforeSend: function() {
     $('#loader').show();
  },
  complete: function(){
     $('#loader').hide();
  },
  success: function() {}
});



Links



Tags