Showing the progress of long running controller actions

Sometimes, a controller action trigger a long running background process. For example, the user clicks a link in the page. Generate a word document in the background. Show the properties of the document in the subsequent page. Generation of word documents take anywhere between 3 seconds to 30 seconds. During this time, the user needs some feedback about the progress of the operation. This post shows how we can provide progress information to the user.

Consider an application with two pages. Index.cshtml and Generate.cshtml. The index page has a Generate link. When the user clicks the link, the Generate page is shown.

<body>
    <h1>Home</h1>
    <h2 id="progressText"></h2>
    <div>
        @Html.ActionLink("Generate", "Generate", null, 
            new { id = "genLink" })
    </div>
</body>

The Generate action is a long running operation that happens in the background. To execute long running operations from a controller, we derive the controller from AsyncController.

public class HomeController : AsyncController
{
    //
    // GET: /Home/

    private BackgroundWorker worker = default(BackgroundWorker);
    private static int progress = default(int);

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

    public void GenerateAsync()
    {
        worker = new BackgroundWorker();
        worker.WorkerReportsProgress = true;
        worker.DoWork += worker_DoWork;
        worker.ProgressChanged += worker_ProgressChanged;
        AsyncManager.OutstandingOperations.Increment();
        worker.RunWorkerAsync();
    }

    void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        progress = e.ProgressPercentage;
    }

    void worker_DoWork(object sender, DoWorkEventArgs e)
    {
        for (int i = 0; i < 100; i++)
        {
            Thread.Sleep(300);
            worker.ReportProgress(i+1);
        }

        AsyncManager.OutstandingOperations.Decrement();
    }

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

    public ActionResult GetProgress()
    {
        return this.Json(progress, JsonRequestBehavior.AllowGet);
    }
}

The three action methods in the above controller are Index, GenerateAsync and GetProgress. GenerateAsync action triggers the background operation. The background operation loops 100 times and sleeps for 300 ms in each iteration. At the end of each iteration, it reports percentage completed. The GetProgress action gets the completion status which is stored as a static variable. (Alternatively, store the variable in cache).

Add javascript to the Index page. It calls GetProgress action method at regular intervals and displays the progress.

$(document).ready(
    function () {
        $("#genLink").click(
            function (e) {
                setInterval(
                    function () {
                        $.get("/Home/GetProgress",
                            function (data) {
                                $("#progressText").text(data);
                        });
                    }, 1000);
            });
    });

Instead of displaying text, use a circular progress plugin to show the progress.

Related Posts

Leave a Reply

Your email address will not be published.