Reputation: 277
So I am trying to add an async progress bar on a really slow and long query that inserts a bunch of rows to a database. My implementation is based off this example: http://blog.janjonas.net/2012-01-02/asp_net-mvc_3-async-jquery-progress-indicator-long-running-tasks
Here is the javascript code for the progress bar
function updateMonitor(taskId, status) {
$("#monitors").html("Task [" + taskId + "]: " + status);
}
//other code
if (doSend == true) {
$.post("/SendBatch/HandleBatchRequest", {
//other code
},
function (taskId) {
// Init monitors
//breakpoint here only stops it once the "/SendBatch/HandleBatchRequest" is done. PROBLEM.
$("#monitors").append($("<p id='" + taskId + "'/>"));
updateMonitor(taskId, "Started");
// Periodically update monitors
var intervalId = setInterval(function () {
$.post("SendBatch/Progress", { id: taskId }, function (progress) {
if (progress >= 100) {
updateMonitor(taskId, "Completed");
clearInterval(intervalId);
} else {
updateMonitor(taskId, progress + "%");
}
});
}, 100);
}
,"html");
Then there is the DIV within the display part of the website
<div id="monitors"></div>
Here is how the controller looks
public SendBatchController
//some code
private static IDictionary<Guid, int> tasks = new Dictionary<Guid, int>();
public ActionResult HandleBatchRequest(
//some code
)
{
var taskId = Guid.NewGuid();
tasks.Add(taskId, 0);
var batchId = Guid.NewGuid().ToString("N");
var costd = cost.ToDecimal();
IEnumerable<BatchListModel> customers;
try
{
customers = new CustomerService(_customerRepository.Session).GetCustomers(
//some code
);
}
catch (Exception err)
{
return Json(err.Message);
}
if (doSend)
{
var sent = 0;
foreach (var c in customers)
{
try
{
var usr = _customerRepository.LoadByID(c.ID);
var message = new ComLog
{
//insertions to log
};
_comLogRepository.Save(message);
sent++;
//progress bar part inside here that is important comes here:
tasks[taskId] = sent;
}
catch (Exception e)
{
Log.WriteLine("ERR:" + e);
}
tasks.Remove(taskId);
}
return Json(taskId);
}
return Json(customers.Count() + " customers");
}
public ActionResult Progress(Guid id)
{
return Json(tasks.Keys.Contains(id) ? tasks[id] : 100);
}
This does not work. The process works in the background. The DIV does show up after the query is done with a "Complete" message. It does not show during the query. I am not sure how async stuff works this works so there might be some logic error here. For instance, does it even know how many customers there is in total? Another key part of the code is the "tasks[taskId] = sent;" wich I am not sure is done correctly.
Upvotes: 0
Views: 1000
Reputation: 4009
Why not just add a loading gif to your website (you can download them here - http://www.ajaxload.info/) Then posistion this loading gif in a div on your page - whereever your are wanting it to display.
Something like the below
<div><img class="hidden " id="LoadingGif" src='../Content/Styles/images/loading.gif' height="100" /></div>
Then in your js file simplay remove class hidden on the LoadingGif id and possibly add class hidden to your monitors div.
hidden class should be like below in your site.css
.hidden {visibility:hidden; display:none;}
Upvotes: 0
Reputation: 16595
On the javascript side, the function that begins the progress check won't fire until after the successful completion of HandleBatchRequest
request. The third parameter of $.post()
is what is fired after a successful request.
You'll either need to have your C# controller return the task ID right away, and ferry off the database insert into another thread/background worker (more on this below). Or have Javascript randomly generate the taskId and pass into the C# controller.
From there, as Brad mentioned, you don't want to do setInterval with a ajax request inside of it. Since AJAX is asyncronous, you could be waiting a while before the server returns a response. To prevent multiple requests from happening at once and causing undo stress on the backend server, you'll want to do something like:
var taskId = 0;
$.post('/SendBatch/HandleBatchRequest', {}, function(r) {
taskId = r; // set task id from
$("#monitors").append($("<p id='" + taskId + "'/>"));
CheckProgress();
}, 'html');
function CheckProgress() {
$.post("SendBatch/Progress", { id: taskId }, function (progress) {
if (progress >= 100) {
updateMonitor(taskId, "Completed");
} else {
updateMonitor(taskId, progress + "%");
setTimeout(function() {
CheckProgress()
}, 1000);
}
}
On the C# side of things, The controller is instantiated on every web request, so when you go check the progress, tasks
will be empty. You'll need to off load the actual records into some sort of background worker/thread that does the actual leg work of inserting the records from the controller. If you're doing this pretty often, you may want to look into some sort of background service installed on the server itself that continuously runs to optimize things and give you the best flexibility going forward.
Upvotes: 1
Reputation: 7898
I would highly recommend against using ajax inside of a setInterval function. Instead, use a setTimeout in the success/complete callback function of the $.post(). This will guarantee that the requests are generated sequentially.
If you fire off ajax requests every 100ms, they will quickly fall out of sync and undoubtedly cause issues.
Upvotes: 0