Long Running Process Options

10

March 17, 2011 by Alistair Deneys

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.

ProgressBox

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.

ClientCallback

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.

Advertisements

10 thoughts on “Long Running Process Options

  1. Jigs says:

    good one Al, this is what I was looking for

  2. […] Alistair Deneys blog about long running operations include information on how to create a progress box in the Sitecore Shell. https://adeneys.wordpress.com/2011/03/17/long-running-process-options/ […]

  3. […] template, the Sitecore API provides functionality to do just that. This could end up being a very long running process so it’s a good idea to ask the user whether or not to perform this […]

  4. […] is processing. To make it more usable and non-blocking we can leverage techniques from my article Long Running Process Options. The technique I’ll implement for this scenario is using the Progress […]

  5. Joe says:

    I use your code for run a long process which creates a file the user has to download at the end. I use the Sitecore.Shell.Applications.Dialogs.ProgressBoxes.ProgressBoxMethod(StartProcess) to create the file. At the end of the job the download has to be started automatically. I tried for this some methods but I always get the same error.

    I used:
    Sitecore.Context.ClientPage.ClientResponse.Download(_fi.FullName);
    SheerResponse.Download(_fi.FullName);
    Files.Download(_fi.FullName);

    I always get the error:
    System.Reflection.TargetInvocationException: Ein Aufrufziel hat einen Ausnahmefehler verursacht. —> System.NullReferenceException: Der Objektverweis wurde nicht auf eine Objektinstanz festgelegt. bei Sitecore.Web.UI.Sheer.ClientPage..ctor() bei Sitecore.Shell.Framework.Files.Download(String filename) bei Dornbracht.Backend.Commands.ExportSalesPartner.StartProcess(Object[] parameters) in…

    Do you have a solution for this?

    • Alistair Deneys says:

      Hi Joe,
      Passing the details of the exception through Google Translate I see it’s a general null reference exception for the ClientPage object. This sounds like you might not have a client request in the scope of the call, so you’ll not have any luck with Sheer API calls. From inside the Execute() method, try starting a new client pipeline and doing the work in the callback. This way you’ll have a client request in scope and your Sheer API calls should work. I show how to start a client pipeline in another article: https://adeneys.wordpress.com/2009/09/22/copy-and-paste-items-server-to-server/

  6. […] processors to carry out each distinct task, and call the pipeline using a Sitecore Job. This is perfect for long running tasks and since it runs as a background tasks it is non-UI […]

  7. […] “Long Running Process Options” by Alistair Deneys […]

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

Categories

The views expressed on this blog are solely my own and do not necessarily reflect the views of my employer.
%d bloggers like this: