タイマージョブを作成する機会があったので、メモがてら記録しておきます。

はじめる

  1. Visual Studio 2010 で、” 空のSharePointプロジェクト” から始めます。
    • デバッグの設定は、サンドボックスではなく、”ファーム” にします。
    • 特定のサイトに対するジョブを作成する場合は、デバッグの設定でそのサイトを指定します。
  2. Features の下にフィーチャを追加します。
    • タイトル、説明、スコープを設定します。スコープについては、後述。
    • そのフィーチャを右クリックして、イベントレシーバを追加します。イベントレシーバについては、後述。
  3. パッケージの名前、タイトル、および説明を必要に応じて設定します。
    • パッケージにフィーチャを追加します。(というか、既定で追加されています)
  4. プロジェクトにクラスを追加します。このクラスがジョブの本体になります。後述。

フィーチャのスコープ

フィーチャのスコープは “Farm” または “Site” にしますが、この設定によって、ジョブの Execute() メソッドの中での this.Parent の内容や、FeatureActivated( SPFeatureReceiverProperties properties )properties 引数の内容が変わってきます。要するに、コンテキストが変わります。したがって、ジョブの処理内容が特定のサイト コレクションに関係することであれば Site を、ファーム全体に対する処理 (サイトコレクションをまたぐような処理) であれば Farm を選びます。

イベント レシーバ

アクティブ時

フィーチャがアクティブになったときに、次のようにジョブを登録するようにします。

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." );
    }
}

非アクティブ時

フィーチャが非アクティブになったときに、次のようにジョブを削除するようにします。

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

追加したクラスは、SPJobDefinitionクラスを継承するようにします。

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 ) {
	}
}

この、Execute メソッドに処理を記述します。

その他

配置

一般的な SharePoint プロジェクト同様、Visual Studioの [配置] コマンドでソリューションを配置できますが、配置後にタイマーを再起動する必要があります。

net stop SPTimerV4
net start SPTimerV4

デバッグ

タイマージョブに限りませんが、デバッグのためにトレースログを出すように設定しておくと便利です。

次のようなクラスを追加し、コード中で diag.WriteTrace( 1, cat, TraceSeverity.Verbose, "{0} == {1}", a, b ); といったようにして書き出します。

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

トレースログをきちんと確認するようにします。SPSite や SPWeb などをきちんと Dispose していないと、”An SPRequest object was not disposed before the end of this thread.” といったようなメッセージが記録されます。ある意味、便利です。