Vor kurzem wurde ich mit einem Sharepoint-Problem eines Kunden konfrontiert, fr das ich nur einen selbstdefinierten TimerJob als sinnvolle L”sungsm”glichkeit sah. Da ich mich aber bisher noch nicht mit SharePoint TimerJobs auseinandergesetzt habe, war dies ein willkommene Gelegenheit, dies nachzuholen. Zwar findet man im Internet einige interessante Posts ber SharePoint TimerJobs, aber ich wollte mir zun„chst selbst die Basics erarbeiten.
Und genau ber diese Basics m”chte ich hier berichten:
Ein SharePoint TimerJob besteht aus einer Klasse, die von der Basisklasse SPJobDefinition abgeleitet ist. In der Ableitung wird im Wesentlichen die Methode Execute() mit der gewnschten Funktionalit„t berschrieben und der eigentliche TimerJob ist bereits fertig. Zus„tzlich kann man auch noch die Konstruktoren berschreiben, um z.B. dem eigenen TimerJob einen aussagekr„ftigen Titel zu geben.
Ein TimerJob kann dann z.B. folgendermaáen aussehen:
using System;
using Microsoft.SharePoint.Administration;
namespace TimerJobNotifyExpiredPassword
{
public class NotifyExpiredPasswordTimerJob : SPJobDefinition
{
public NotifyExpiredPasswordTimerJob()
: base()
{
}
public NotifyExpiredPasswordTimerJob(string strJobName, SPService oService, SPServer oServer, SPJobLockType oTargetType)
: base(strJobName, oService, oServer, oTargetType)
{
}
public NotifyExpiredPasswordTimerJob(string strJobName, SPWebApplication oWebApp)
: base(strJobName, oWebApp, null, SPJobLockType.ContentDatabase)
{
this.Title = "NotifyExpiredPasswordTimerJob";
}
public override void Execute(Guid oContentDBId)
{
// Hier den auszufhrenden Timer-Code einfgen
}
}
}
An der grn markierten Stelle wrde man den eigenen Code einfgen, der vom SharePoint TimerDienst zyklisch aufgerufen werden soll. OK, nun haben wir bereits die Klasse, die vom SharePoint TimerDienst aufgerufen werden k”nnte – wenn es uns nun noch gelingen wrde, die obige Klasse als SharePoint TimerJob auf einem SharePoint-Server zu installieren. Dies ist leider nur mit ein wenig zus„tzlicher Programmierarbeit m”glich. Das Verfahren ist „hnlich, wie es z.B. auch bei einem benutzerdefiniertem EventHandler angewendet werden kann. Wir werden unseren TimerJob ber ein SharePoint Feature installieren und uns an den Event h„ngen, der beim Installieren bzw. beim Deinstallieren eines Features ausgel”st wird. Beim Installieren fgen wir unsere neue TimerJob-Klasse als neuen TimerJob der entsprechenden SharePoint-Liste hinzu, beim Deinstallieren entfernen wir unsere Klasse aus der Liste der SharePoint-TimerJobs. Im Folgenden m”chte ich zeigen, wie man das genau macht:
Zuerst ben”tigen wir eine zweite Klasse, die aber diesmal von der Basisklasse SPFeatureReceiver abgeleitet wird. Wir erstellen uns also einen eigenen EventReceiver und berschreiben hier z.B. die Methoden FeatureActivated() und FeatureDeactivating(). Beim Aktivieren unseres Features wird unsere Methode FeatureActivated() aufgerufen und hier fgen wir nun den Code ein, um unseren TimerJob zu installieren. Beim Deaktivieren unseres Features wird dann die Methode FeatureDeactivating() aufgerufen und wir fgen hier den Code ein, um unseren TimerJob wieder zu deinstallieren.
Dies kann dann z.B. folgendermaáen aussehen:
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;
namespace TimerJobNotifyExpiredPassword
{
class FeatureReceiver : SPFeatureReceiver
{
public override void FeatureActivated(SPFeatureReceiverProperties oProperties)
{
SPSite oSite = oProperties.Feature.Parent as SPSite;
// Falls „ltere Instanz vorhanden, diese zuvor l”schen
foreach (SPJobDefinition oJob in oSite.WebApplication.JobDefinitions)
{
if (oJob.Name == "TimerJobNotifyExpiredPassword")
{
oJob.Delete();
}
}
// Timer-Job installieren
TimerJobNotifyExpiredPassword.NotifyExpiredPasswordTimerJob oTimerJob = new TimerJobNotifyExpiredPassword.NotifyExpiredPasswordTimerJob("TimerJobNotifyExpiredPassword", oSite.WebApplication);
SPMinuteSchedule oSchedule = new SPMinuteSchedule();
oSchedule.BeginSecond = 0;
oSchedule.EndSecond = 59;
oSchedule.Interval = 1; // <- diesen Job jede Minute starten
oTimerJob.Schedule = oSchedule;
oTimerJob.Update();
}
public override void FeatureDeactivating(SPFeatureReceiverProperties oProperties)
{
SPSite oSite = oProperties.Feature.Parent as SPSite;
// Job suchen und l”schen
foreach (SPJobDefinition oJob in oSite.WebApplication.JobDefinitions)
{
if (oJob.Name == "TimerJobNotifyExpiredPassword")
{
oJob.Delete();
}
}
}
public override void FeatureInstalled(SPFeatureReceiverProperties oProperties)
{
}
public override void FeatureUninstalling(SPFeatureReceiverProperties oProperties)
{
}
}
}
Das Ganze – also unsere beiden Klassen habe ich in einem einfachen VisualStudio 2008 Projekt fr eine Klassenbibliothek verpackt und bersetzt. Um dies jetzt als SharePoint Feature installieren zu k”nnen, ben”tigen wir nur noch eine einzige Datei. Diese Datei tr„gt den Namen feature.xml und enth„lt eine XML-Beschreibung unsers Features. Fr mein einfaches Beispiel reicht diese feature.xml v”llig aus:
<?xml version="1.0" encoding="utf-8" ?>
<Feature xmlns="http://schemas.microsoft.com/sharepoint/"
Id="04496CA0-316D-11DD-BD11-0800200C9A66"
Title="TimerJobNotifyExpiredPassword"
Description="TimerJobNotifyExpiredPassword"
Version="1.0.0.0"
Scope="Site"
ReceiverAssembly="TimerJobNotifyExpiredPassword, Version=1.0.0.0, Culture=neutral, PublicKeyToken=639ff2c7cd9696b7"
ReceiverClass="TimerJobNotifyExpiredPassword.FeatureReceiver">
</Feature>
So einfach diese Datei auch aussieht, es gibt hier einiges, was man beachten muss:
-
unser Feature muss ber eine eindeutige GUID identifizierbar sein. Die GUID in der Zeile "Id=" muss deswegen ausgetauscht werden
-
gleiches gilt fr die Zeilen Title und Description. Hier gibt man dem Feature einen Namen und eine Beschreibung
-
ausgetauscht bzw. angepasst werden mssen auch die letzten beiden Zeilen, die die Assembly und die Klasse angeben, die als Receiver fr die oben beschreibenen Feature-Events verwendet werden soll. šbrigens: den PublicKeyToken bekommt man recht einfach mit dem .NET Reflector von Lutz Roeder heraus.
-
wichtig ist auch die Zeile "Scope=". Hier gibt man den Gltigkeitsbereich des Features an. Wichtig zu beachten: der hier eingetragene Scope muss mit dem Scope bereinstimmen, den wir beim Installieren bzw. Deinstallieren unseres TimerJobs verwendet haben!
So – das waren alle Dateien, die man unbedingt ben”tigt, um einen eigenen TimerJob auf einem SharePoint-Server zu installieren. Stellt sich die Frage: wie geht es nun weiter?
Zuerst bersetzten wir unsere beiden Klassen. Sofern beide in einem Projekt zusammengefasst wurden, sollte dabei eine DLL erstellt werden. Diese DLL kopieren wird in den Global Assembly Cache. Als n„chstes erzeugen wird im sogenannten 12-Hive unter ../TEMPLATE/FEATUES ein neues Verzeichnis mit dem Namen, dem wir dem Feature in der Datei feature.xml gegeben haben und kopieren die Datei feature.xml dort hin. Jetzt sind wir bereit, unser Feature zu installieren.
Dies geschieht mit folgendem Auruf:
stsadm -o installfeature -name TimerJobNotifyExpiredPassword
Nun mssen wir das Feature nur noch Aktivieren, damit unser EventReceiver aufgerufen wird und unseren TimerJob installieren kann:
stsadm -o activatefeature -name TimerJobNotifyExpiredPassword -url http://mossbasis
Die URL muss natrlich durch die URL des jeweiligen Ziels ersetzt werden.
Beide Aufrufe kann man auch in einer kleinen Batch-Datei zusammenfassen. Fr meine Testinstallationen verwende ich gern diese Batch-Datei:
REM TO BE SAFE FIRST DEACTIVATE and UNINSTALL FEATURE
stsadm -o deactivatefeature -name TimerJobNotifyExpiredPassword -url http://mossbasis
stsadm -o uninstallfeature -name TimerJobNotifyExpiredPassword -force
PAUSE
REM NOW INSTALL FEATURE
stsadm -o installfeature -name TimerJobNotifyExpiredPassword
PAUSE
REM FINALLY ACTIVATE FEATURE
stsadm -o activatefeature -name TimerJobNotifyExpiredPassword -url http://mossbasis
Der beispielhafte TimerJob fhrt zwar keine Aktion aus, dennoch k”nnen wir mit dem Debugger schnell berprfen, ob alles wie gewnscht funktioniert. Dazu setzten wir einen Breakpoint auf die Methode Execute() und verbinden uns mit dem Prozess OWSTIMER.EXE. Es kann notwendig sein, einen Haken neben "Prozesse aller Benutzer anzeigen" im Fenster "An den Prozess anh„ngen" des Debuggers zu setzen. Nach maximal einer Minute sollte der Debugger beim Aufruf der Methode Execute() anhalten – sofern am obigen Aktivierungsintervall (SPMinuteSchedule) nichts ge„ndert wurde!
Auf „hnliche Weise kann man brigens auch Testen, ob unser Feature-EventReceiver funktioniert. Einfach je einen Breakpoint auf die berschriebenen Methoden setzen und diesmal an den Prozess W3WP.EXE anh„ngen. M”glicherweise gibt es den Prozess in mehreren Instanzen. Sofern man die richtige Instanz nicht kennt, kann man sich auch einfach -zu Testzwecken- an alle Instanzen h„ngen.
Unser Feature l„át sich natrlich auch ber die Zentraladministration aktivieren bzw. deaktiviren – in der Websitesammlungsverwaltung klickt man auf Websiteauflistungsfeatures. In der Liste Websitesammlungs-Features sollte sich unser TimerJob-Feature finden.
Ich habe hier das grunds„tzliche Vorgehen beim Erstellen und Installieren eines benutzerdefinierten SharePoint TimerJobs beschrieben. Auf diese Weise funktioniert es zwar, aber fr das Deployment auf ein produktives System ist diese Vorgehensweise natrlich nicht geeignet. Hierfr bietet es sich an, aus unserem SharePoint-Feature eine SharePoint-Solution zu machen