Long Running Tasks And Sitecore Sidekick

In a previous blog post – I started tinkering with the lovely Sitecore Sidekick. Looks like the module is now in the market place – yay!

So I thought I’d do a blog post to show how you can start a long running Sitecore job and report back on the screen with its progress.

We’re just going to create a dummy job which takes ages to do nothing:

    public class LongRunningJob
    {
        public void RunJob(CustomJobStatus status)
        {
            status.Status = Status.InProgress;
            for (int i = 0; i < 50; i++)
            {
                status.Processed++;
                Thread.Sleep(1000);
            }
            status.Status = Status.Successful;
        }
    }

This also uses a custom job status:

    public class CustomJobStatus
    {
        public int Processed { get; set; }
        public Status Status { get; set; }
        public List<string> Messages { get; set; }
    }

    public enum Status
    {
        New = 0,
        InProgress = 1,
        Successful = 2,
        Failed = 3
    }

To run it – we need some mark-up in the .scs file:

<div ng-controller="mycontroller as vm">
  <a class="btn" ng-click="vm.runJob()">Run Job</a>
<div ng-show="inProgress">
    Processed: {{processed}}</div>
<div ng-show="success">
    Job completed successfully.</div>
<div ng-show="fail">
    Job failed:

    {{messages}}</div>
<pre ng-if="vm.error" class="scserror">{{vm.error}}</pre>
<pre ng-if="vm.data" class="scsdata">{{vm.data}}</pre>
</div>

And then you need a controller:

(function () {
	'use strict';

	angular
        .module('app')
        .controller('mycontroller', mycontroller);

	mycontroller.$inject = ['$scope', '$http', '$timeout'];

	function mycontroller($scope, $http, $timeout) {
		$('#uframe').load(function () {
			this.height =
			this.contentWindow.document.body.offsetHeight + 'px';
		});

		var loadTime = 100, //Load the data every second
          errorCount = 0, //Counter for the server errors
          loadPromise; //Pointer to the promise created by the Angular $timout service

	    function getData() {
	        $http.get('/scs/checkjob.scsvc')
	            .then(function(res) {
	                if (res.data.ErrorMessage) {
	                    $scope.error = true;
	                    $scope.errorMessage = res.data.ErrorMessage
	                } else {
	                    $scope.processed = res.data.Processed;

	                    errorCount = 0;

	                    if (res.data.Status !== 2 && res.data.Status !== 3) {
	                        nextLoad();
	                    } else if (res.data.Status === 2) {
	                        $scope.success = true;
	                    } else if (res.data.Status === 3) {
	                        $scope.fail = true;
	                        $scope.messages = res.data.Messages;
	                    }
	                }
	            })
	            .catch(function(res) {
	                $scope.data = 'Server error';
	                nextLoad(++errorCount * 2 * loadTime);
	            });
	    }

	    var vm = this;

		vm.runJob = function () {
		    $http.get('/scs/runjob.scsvc')
		    $scope.inProgress = true
            getData()
		}

		function cancelNextLoad() {
		    $timeout.cancel(loadPromise);
		};

		function nextLoad(mill) {
		    mill = mill || loadTime;

		    //Always make sure the last timeout is cleared before starting a new one
		    cancelNextLoad();
		    $timeout(getData, mill);
		};

	    //Always clear the timeout when the view is destroyed, otherwise it will keep polling and leak memory
		$scope.$on('$destroy', function () {
		    cancelNextLoad();
		});
	}
})();

And finally, to stitch it all together – here are the methods in our Handler cs file:

        public override void ProcessRequest(HttpContextBase context)
        {
            var file = GetFile(context);
            if (file == "runjob.scsvc")
                this.ReturnJson(context, this.RunJob(context));
            if (file == "checkjob.scsvc")
                this.ReturnJson(context, this.CheckJob(context));
            else
                ProcessResourceRequest(context);
        }

        private object RunJob(HttpContextBase context)
        {
            var status = new CustomJobStatus();

            Job job = JobManager.Start(new JobOptions("MyLongRunningJob", "SidekickJobs", "master",
                                                      new LongRunningJob(), "RunJob", new object[] {  status } )
            {
                ContextUser = Context.User,
                AfterLife = TimeSpan.FromMilliseconds(Settings.Publishing.PublishDialogPollingInterval * 50),
                Priority = Settings.Publishing.ThreadPriority
            });
            job.Options.CustomData = status;

            return status;
        }

        private object CheckJob(HttpContextBase context)
        {
            Job job = JobManager.GetJob("MyLongRunningJob");
            CustomJobStatus status = null;
            if (job != null)
            {
                status =  job.Options.CustomData as CustomJobStatus;
            }

            if (status == null)
            {
                status = new CustomJobStatus { Status = Status.New };
            }
            return status;
        }

Our job updates the job status, the Javascript code polls the service periodically and gets the status, putting the current count on the screen.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s