I had a chance to create a custom time job on SharePoint 2010. This is a kind of a note for myself.

Getting Started

  1. Create Empty SharePoint Project on Visual Studio 2010.
    • Set for “Farm” on Debug Setting, not “Sandbox”.
    • If your timer job is only for a specific site collection, specify the site URL on Debug Setting.
  2. Add a new feature under Features node.
    • Specify Title, Description, and Scope. About Scope is described later on the article.
    • Right click on the feature, then add an event receiver. About the event receiver is described later.
  3. Specify Name, Title, and Description for the package.
    • Add the feature to the package. (It is already added by default…)
  4. Add a new class to the project. The class will be a body of the timer job. The description is below on the article.

Feature Scope

Scope of the feature should be set to Farm or Site. It depends on what you want to do by the timer job. This setting affects to a value of ?this.Parentin Execute() method of the job, or properties argument of FeatureActivated( SPFeatureReceiverProperties properties ) method. In other words, the context will be changed. So if you want the timer job to do something for a specific site collection, set it as Site. Or if your timer job will wander around any site collections, set it as Farm.

Event Receiver

On Activated

This is a sample event receiver code for “on activated” of the feature.

public override void FeatureActivated( SPFeatureReceiverProperties properties ) {
    SPWebService service = properties.Feature.Parent as SPWebService;
    SPFarm farm = service.Farm;
    SPService timer = null;
    timer = getSPTimer( farm );
    if( timer != null ) {
        foreach( SPJobDefinition job in timer.JobDefinitions ) {
            if( job.Name == List_JOB_NAME ) {
                job.Delete();   //  Delete all living jobs.
            }
        }
    } else {
        throw new Exception("Could not get SPTimerV4 Service.");
    }

    // install the job
    Job one = null;
    try {
        one = new Job( List_JOB_NAME, timer, null, SPJobLockType.Job );
    } catch {
        throw new Exception( "Installing the job failed." );
    }
    try {
        SPMinuteSchedule schedule = new SPMinuteSchedule();
        schedule.BeginSecond = 0;
        schedule.EndSecond = 59;
#if DEBUG
        schedule.Interval = 1;
#else
        schedule.Interval = 15;
#endif
        one.Schedule = schedule;
        one.Update();
    } catch {
        throw new Exception( "Scheduling the job failed." );
    }
}

On Deactivated

This is a sample event receiver code for “on deactivated” of the feature.

public override void FeatureDeactivating( SPFeatureReceiverProperties properties ) {
    SPWebService service = properties.Feature.Parent as SPWebService;
    SPFarm farm = service.Farm;
    SPService timer = null;
    timer = getSPTimer( farm );
    if( timer != null ) {
        foreach( SPJobDefinition job in timer.JobDefinitions ) {
            if( job.Name == List_JOB_NAME ) {
                job.Delete();
            }
        }
    } else {
        throw new Exception( "Could not get SPTimerV4 Service." );
    }
}

SPJobDefinition

Inherit SPJobDefinition class on the added new class.

public class Job : SPJobDefinition {
	private const String CoreName = "My Time Job Title";

	public Job()
	    : base() {
	}

	public Job( string jobName, SPService service, SPServer server, SPJobLockType targetType )
	    : base( jobName, service, server, targetType ) {
	    this.Title = CoreName;
	}

	public Job( string jobName, SPWebApplication webApplication )
	    : base( jobName, webApplication, null, SPJobLockType.ContentDatabase ) {
	    this.Title = CoreName;
	}

	public override string Description {
	    get {
	        return "This time job is doing something on entire site collections.";
	    }
	}

	public override string DisplayName {
	    get {
	        return CoreName;
	    }
	}

	public override void Execute( Guid contentDbId ) {
	}
}

That Execute method is a body of your timer job. Write what you need to do inside of the method.

Misc.

Deploy

You can use Deploy command on Visual Studio as well as typical SharePoint projects. However you should restart SharePoint Timer service after deploying.

net stop SPTimerV4
net start SPTimerV4

Debug

This is not only for time job coding. It is very useful if you set trace log for debugging.

Add a class like following codes, then write as diag.WriteTrace( 1, cat, TraceSeverity.Verbose, "{0} == {1}", a, b ); in your code.

class Diagnostics : SPDiagnosticsServiceBase {
	public enum CategoryId {
	    None = 0,
	    Deployment = 100,
	    Provisioning = 200,
	    CustomAction = 300,
	    Rendering = 400,
	    WebPart = 500
	}

	private static string DiagnosticsAreaName = "MyTimerJob";

	public Diagnostics()
	    : base() {
	}
	public Diagnostics( String name, SPFarm parent )
	    : base( name, parent ) {
	}

	protected override IEnumerable ProvideAreas() {
	    List categories = new List();
	    foreach( string catName in Enum.GetNames( typeof( CategoryId ) ) ) {
	        uint catId = (uint)(int)Enum.Parse( typeof( CategoryId ), catName );
	        categories.Add( new SPDiagnosticsCategory( catName, TraceSeverity.Verbose, EventSeverity.Error, 0, catId ) );
	    }

	    yield return new SPDiagnosticsArea( DiagnosticsAreaName, categories );
	}

	public static Diagnostics Local {
	    get {
	        return SPDiagnosticsServiceBase.GetLocal();
	    }
	}

	public SPDiagnosticsCategory this[CategoryId id] {
	    get {
	        return Areas[DiagnosticsAreaName].Categories[id.ToString()];
	    }
	}
}

Dispose

We should remind again disposing SPSite or SPWeb objects. You can find a line of “An SPRequest object was not disposed before the end of this thread” in trace logs if you forget the dispose. It is bit useful.