Long Running Process Options
Recently I was asked by another developer about what options we had in Sitecore for long running processes. It wasn’t until I had to list them out all at once did I realise just how many options there are, all with their appropriate uses.
Sitecore contains a very visual and engaging UI. And when it goes away to do something for a long time, it will provide feedback to the user so they know what’s happening. We can leverage the mechanisms Sitecore have built into the product for our own customisations and modules.
The Sitecore Job
Let’s start with the basic building block of long running processes, the Sitecore Job. A Sitecore job is just a background thread. The benefit of using a thread is it’s non-blocking, so the method will return without the process completing, giving you opportunity to do other work including updating the user on what’s going on with their process.
There are a number of ways to create a job, but the easiest is to use an overload of the JobManager.Start
method.
public class JobExample
{
public void StartJob()
{
var jobOptions = new Sitecore.Jobs.JobOptions(
"JobName",
"JobCategory",
Sitecore.Context.Site.Name,
this,
"ProcessMethod",
new object[]{"hi"});
Sitecore.Jobs.JobManager.Start(jobOptions);
}
public void ProcessMethod(string message)
{
System.IO.File.WriteAllText(@"c:\temp\process.txt", message);
}
}
The call to JobManager.Start
is non-blocking, so the StartJob
method above will return immediately while the JobManager
runs the ProcessMethod
method on a background thread.
A Long Running Process
For the following examples I’ll need something to simulate a long running process.
using System;
using System.Threading;
namespace LongRunningProcesses
{
public class LongProcess
{
public void Execute(int iterations)
{
for (var i = 0; i < iterations; i++)
{
Thread.Sleep(200);
if (Sitecore.Context.Job != null)
{
Sitecore.Context.Job.Status.Processed = i;
Sitecore.Context.Job.Status.Messages.Add("Processed item " + i);
}
}
}
}
}
Note how I’m updating the status on the context job to provide feedback about what’s going on to anyone who might be listening. If the current code is being executed inside a job then Sitecore.Context.Job
will be populated with the job.
Progress Box
Armed with the basics, let’s now have a look at options for long running processes that need to provide feedback to the user. These kinds of operations are normally started by the user in the UI. The simplest of these would be the ProgressBox
which abstracts away all the setting up of the job and even the UI stuff.
This option is good when you have a user initiated operation which would take more than about 2 seconds to complete.
To show the ProgressBox
in action I’ll create a new command which I can invoke from the content editor. Using the UI feedback features of Sitecore requires a ClientPage
context such as executing a command.
To setup my command I’ll create the command handler class in my project.
public class MyCommand : Command
{
public override void Execute(CommandContext context)
{
}
}
I’ll then register this command handler through a config include (App_Config\Include\MyCommands.config
).
<?xml version="1.0"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
<sitecore>
<commands>
<command name="proc:mycommand" type="LongRunningProcesses.MyCommand,LongRunningProcesses"/>
</commands>
</sitecore>
</configuration>
Now jump into the core database inside the Sitecore desktop, open the content editor and navigate to /sitecore/content/Applications/Content Editor/Ribbons/Chunks
then create a new chunk and command or create a new command under an existing chunk. Fill in the details for your new command and make sure the Click
field contains the name of the command to execute when the button is clicked (proc:mycommand
).
Now to fill in the call to the progress box inside the command’s Execute method.
public override void Execute(CommandContext context)
{
Sitecore.Shell.Applications.Dialogs.ProgressBoxes.ProgressBox.Execute(
"MyJob",
"Long Process",
new Sitecore.Shell.Applications.Dialogs.ProgressBoxes
.ProgressBoxMethod(StartProcess),
new object[] { 50 });
}
public void StartProcess(params object[] parameters)
{
var proc = new LongProcess();
proc.Execute((int)parameters[0]);
}
And when we now click the new button we get a nice dialog showing the progress of the operation.
Client Callbacks
Although it can be handy having the UI taken care of for you sometimes it feels a bit clunky to open a new window to show progress, especially if you’re operating inside a custom application as opposed to running inside the content editor. You might also need a bit more control over the user feedback process.
For this example I’ll need to create a new application in Sitecore to show the client callback. Before creating the application I’ll create a new XML layout (Sheer UI layout) which will be the UI of my application. To do this go into the core database then open the developer center then the file menu, new, select layouts category then select XML layout, then complete the wizard.
Here’s the source for my simple XML layout.
<control xmlns:def="Definition"
xmlns="http://schemas.sitecore.net/Visual-Studio-Intellisense">
<ProcessApp>
<FormPage>
<CodeBeside
Type="LongRunningProcesses.ProcessAppForm,LongRunningProcesses"/>
<Border Class="scBackground" Height="100%" Padding="10" Margin="10">
<Edit ID="HandleId" Hidden="True"/>
<GridPanel CellPadding="10">
<Border>
<Edit ID="Iterations"/>
<Space Width="20"/>
<Button Header="Process" Click="ProcessStart"/>
<Space Width="20"/>
<Literal ID="Message"/>
</Border>
<Border ID="Progress" Visible="False">
<ThemedImage Src="Images/Progress.gif" Width="94" Height="17"/>
</Border>
</GridPanel>
</Border>
</FormPage>
</ProcessApp>
</control>
Now create a new application definition by creating a new item based on the application template under /sitecore/content/Applications
and assign the XML layout from above to the presentation of the application item.
Don’t try and run the application just yet, we also need to create the code-beside.
using System.Threading;
using Sitecore;
using Sitecore.Jobs;
using Sitecore.Web.UI.HtmlControls;
using Sitecore.Web.UI.Sheer;
namespace LongRunningProcesses
{
public class ProcessAppForm : ApplicationForm
{
protected Border Progress;
protected Literal Message;
protected Edit HandleId;
protected Edit Iterations;
protected void ProcessStart()
{
Progress.Visible = true;
Message.Text = "Starting...";
var count = 0;
int.TryParse(Iterations.Value, out count);
var job = JobManager.Start(new JobOptions(
"MyJob",
"MyCategory",
Context.Site.Name,
this,
"Process",
new object[]{count}));
HandleId.Value = job.Handle.ToString();
}
protected void Process(int iterations)
{
var proc = new LongProcess();
proc.Execute(iterations);
}
}
}
Note how above I’m using a job to execute the process. That way the ProcessStart
method returns immediately. In the Process method I’m also updating the status of the context job. I’m also storing the job handle in a hidden field so I can easily retrieve the job on a callback. Now to add the listeners.
We need to instruct the client to call back to the server periodically to get the status of the process. To do this we use the SheerResponse.Timer
method to cause the client to callback to a method after a number of milliseconds. Simply add this call at the end of the ProcessStart
method after the job has started.
SheerResponse.Timer("CheckStatus", 500);
Now we need to create that CheckStatus
method to update the UI.
protected void CheckStatus()
{
var handle = Handle.Parse(HandleId.Value);
var job = JobManager.GetJob(handle);
Message.Text = "Processed " + job.Status.Processed.ToString();
if (!job.IsDone)
SheerResponse.Timer("CheckStatus", 500);
else
Progress.Visible = false;
}
In the above method if the job hasn’t been marked as done we instruct the client to callback again in another 500 milliseconds. This is how we get the UI to continue to update.
Keep in mind that for the SheerResponse
class to work you need to be inside a Sitecore ajax call.
Agents and Scheduled Tasks
The next two options are not necessarily specific long running process options, but they are worth a mention. Sitecore contains a mechanism by which you can have a task run periodically. Sometimes these tasks will take a while to complete and would be considered long running, but not always. These kinds of tasks can perform a variety of tasks including cleaning up content, updating stats, refreshing caches or importing data from an external feed. Anything that needs to run on a schedule.
The first option to have your periodic task is to create an agent. Agents are defined in configuration under the sitecore/scheduling
element.
Let’s create a class to be our agent. An agent is any old class, it doesn’t have to inherit any specific base class.
using System;
namespace LongRunningProcesses
{
public class Agent
{
public int Iterations
{
get;
set;
}
public void Run()
{
var proc = new LongProcess();
proc.Execute(Iterations);
}
}
}
Now to register the agent in configuration. I’ll do that through a config include as I did above.
<?xml version="1.0"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
<sitecore>
<scheduling>
<agent type="LongRunningProcesses.Agent,LongRunningProcesses"
method="Run" interval="00:01:00">
<Iterations>50</Iterations>
</agent>
</scheduling>
</sitecore>
</configuration>
Now the Run
method of my class will be called by Sitecore every minute (depending on other agent settings and schedule resolution).
We could also do the same thing using a scheduled task. How does a scheduled task differ from an agent? A scheduled task is defined inside a Sitecore database instead of in configuration, but other than that they are the same. In fact, the scheduled tasks are run by the database agents which are present in a default Sitecore install.
Really Long Running Processes
All the above options for long running processes are OK running up to a couple of minutes. You must keep in mind that Sitecore runs inside IIS and so the execution lifetime of your code is controlled by IIS and not you. IIS may terminate your application at any time for whatever reason. Due to this I would recommend using an external Windows application and services hosted in Sitecore for any tasks which would run for more than a few minutes. I’ve recently blogged about how to host a WCF service inside Sitecore.
An external Windows application, whether that’s WinForms or a console app, has it’s lifetime controlled by you. If it’s an interactive process where the user initiates it and requires feedback you can use a WinForms app or a console application. If you require your task to run on a schedule then you can use the Windows Task Scheduler to call a console application. I’ve used this approach in the past to import data from files periodically which were uploaded to the system through sFTP.
Conclusion
As you can see you have a few options for long running processes. For the UI options it’s up to you to determine which fits into your application and the user’s workflow. If you have short lived (few minutes) periodic tasks then you could use an agent or scheduled task. Anything that runs for more than a few minutes should use an external application to provide the orchestration and call services hosted by Sitecore.
[...] [/blog/2011/03/17/long-running-process-options/](/blog/2011/03/17/long-running-process-options/) [...]