15

After reading the documentation on AsyncControllers in ASP.NET MVC 2, I am wondering what's the best way to implement an ajax progress bar in this scenario. It seems a bit odd that the tutorial does not cover this at all.

I guess implementing an AJAX progress bar involves requires additional action method that returns the status of the current task. However, I am not sure about the best way to exchange information on the status of the task between worker threads and that action method.

My best idea so far was to put information on the curent progress into the Session dictionary along with a unique id, and share that id with the client so that it can poll the status. But perhaps there is a much easier way that I did not notice.

What's the best way to do this?

Thanks,

Adrian

Adrian Grigore
  • 33,034
  • 36
  • 130
  • 210
  • This is kind of like asking why you don't get a progress bar for a normal request. Should answer itself when you think about it that way. – nick Jan 20 '11 at 22:58
  • 2
    @nick: Not really. Normal requests are not long-running. Nobody needs a progress bar for something that takes a couple of seconds at most. However, if you are using an async controller, you expect the request to take a long time. And that's when you need a progress bar. Am I the only one that finds this obvious? – Adrian Grigore Jan 21 '11 at 16:27
  • With an Async controller it means a task goes off to somewhere else - so you make it Async and can allow the rest of the code to continue exuction - no requirement for any of that to be a long-running task. Just means you can improve performance. with a web request - any benefit will be noticed by the client. Are you the only one? Does that say something? What are you expecting? The server to keep sending multiple responses so you get a progress bar - that will negate your performance benefits and add really stupid behaviour. – nick Jan 22 '11 at 23:34

1 Answers1

17

Very interesting question! Actually it seems that it is not a task for AsyncController. Async controllers are designed for long-running single-HTTP-query operations at server-side. When you are using async action, this could only help you to release ASP.Net worker thread during some long-running operation(s) and allow it to serve other requests while operation is performed. But from client-side point of view it doesn't matter, is this async controller or not. For client this is just single HTTP request.

You need to redesign this using some long-running queries service in your application. Here is example of controller, that could serve such workflow:

public class LongOperationsController : Controller
{
    public ActionResult StartOperation(OperationData data)
    { 
        Guid operationId = Guid.NewGuid(); // unique identifier for your operation
        OperationsService.DoStartOperation(operationId, data); // service starts to perform operation using separate thread
        return new JsonResult(operationId); // operation id should be sent to client to allow progress monitoring
    }

    public ActionResult GetOperationStatus(Guid operationId) 
    {
        var status = OperationsService.GetStatus(operationId); // this method returns some object, that describes status of operation (e.g. progress, current task etc.)
        return new JsonResult(status); // returning it to client
    }

    public ActionResult GetOperationResult(Guid operationId)
    {
        var result = OperationsService.GetOperationResult(operationId); // this should throw exception if operation is not yet completed
        return new JsonResult(result);
    }

    public ActionResult ClearOperation(Guid operationId)
    {
        OperationsService.ClearOperationResult(operationId); // we should delete operation result if it was handled by client
        return true;
    }
}

And here are client-side code, that could interact with this controller:

var operationId;
function startOperation(data) {
    $.post('/LongOperations/StartOperation', data, function(response) {
        operationId = response; // store operationId
        startOperationMonitoring(); // start
    }, 'json');
}

function startOperationMonitoring() {
    // todo : periodically call updateOperationStatus() to check status at server-side
}

function updateOperationStatus() {
    // todo : get result of GetOperationStatus action from controller 
    // todo : if status is 'running', update progress bar with value from server, if 'completed' - stop operation monitoring and call finishOperation()
}

function finishOperation() {
    // todo : get result of GetOperationResult action from controller and update UI
    // todo : call ClearOperation action from controller to free resources
}

This is very basic concept, there are some missed items here, but I hope you will get the main idea. Also it's up to you how to design components of this system, for example:

  • use singleton for OperationsService, or not;
  • where and how long operation result should be stored (DB? Cache? Session?);
  • is it really required to manually release resources and what to do when client stopped to monitor operation (user closed browser) etc.

Best luck!

Tieson T.
  • 20,774
  • 6
  • 77
  • 92
Victor Haydin
  • 3,518
  • 2
  • 26
  • 41
  • Hi mace, Thanks for your detailed answer. It's exactly the approach I came up with too and which I described in the third paragraph of my OP. This works fine, but it seems like a too tedious implementation that should be part of almost every async controller (namely a progress bar). I would have thought (or at least hoped) that ASP.NET MVC is better than this and provides some kind of progress indicator for async controllers out of the box. – Adrian Grigore Jan 18 '11 at 23:27
  • 1
    I am wondering, that you are not completely understand what async controllers are designed for. Its not about async requests handling, its about running long operations on server. Unfortunately HTTP is synchronous protocol and wasn't designed for such purposes from the beginning. – Victor Haydin Jan 18 '11 at 23:56
  • Also, in my opinion its not a best idea to keep one request in open state and periodically poll server about progress using parallel requests. – Victor Haydin Jan 19 '11 at 00:00
  • Snazzy solution - I'll give you that :) – nick Jan 20 '11 at 23:00
  • Victor: I absolutely do understand what they are for. I never said an async controller is primarily made for showing a progress bar. What I am saying is that if you use an async controller, you probably also need a progress bar. It seems so obvious, that I would have expected ASP.NET MVC to provide some help for implementing a progress bar out of the box. Again, I do understand how to work around this missing feature (the solution was in my OP!), but I was hoping there was a better approach. – Adrian Grigore Jan 21 '11 at 16:26
  • I like this solution a lot, but I am not clear on the details of how OperationsService.GetStatus(operationId) would work. I am picturing a static class with a GUID/Thread dictionary, which I would use to find the correct thread and ask for its status, but how? – KennyZ Jun 14 '12 at 16:44
  • An alternative solution might involve SignalR to report real-time progress, rather than polling the server periodically but is probably overkill for this scenario. – kamranicus Jun 11 '13 at 18:12