Wednesday, September 7, 2011

Creating MOSS Custom Timer Job

As always, I would take this opportunity to define what we are intending to explain in this article. It is SharePoint Timer job and to add a surprise factor, it would be custom developed. To be very honest I have often observed SharePoint Timer jobs becomes a head ache, since debugging is very cumbersome. See my other article Creating Custom SharePoint Timer Job, I have described in details all the steps of developing timer jobs. In this article I will be very crisp in developing a timer job.

 

 

The timer job would need only two classes (i) The timer job and (ii) Feature Receiver class

 

Need to create a class that will inherit from the SPJobdefintion class, i.e. from "Microsoft.SharePoint.Administration.SPJobdefinition".  Add the reference of the namespace using Microsoft.SharePoint.Administration; the class would look like public class MySPTimerJobActivation : SPJobDefinition

 

Constructor is very important in SP Timer Jobs. In this code the constructor would be called from the feature receiver class while instantiating the MySPTimerJobActivation. There are two overloaded constructors apart from the default contractor available, in my case the one I have used is sufficient.

#region Overloaded Contructors

public MySPTimerJobActivation(): base()

{

}

public MySPTimerJobActivation(string jobName, SPWebApplication webApp, SPServer server, SPJobLockType joblock)

            : base(jobName, webApp, server, joblock)

{

   this.Title = jobName;

}

#endregion

 

Implement the desired code in Execute() method. Whenever the timer job would be activated, this Execute() method would be called. Another important thing worth mentioning is from within the Timer Jobs class we will not be able to get the reference to the site, since the context is different. Thus we need to put the site url in the properties bag of the timer job class while activating the feature in feature receiver class and use it here

public override void Execute(Guid targetInstanceId)

{

string strUrl = this.Properties["SiteURL"].ToString();

SPSite oSiteColl = new SPSite(strUrl);

////Desired Code to execute here

}

 

The Timer Job Class would look something like this.

public class MySPTimerJobActivation : SPJobDefinition

{

  #region Overloaded Contructors

  public MySPTimerJobActivation()

  : base()

  {

  }

 

  public MySPTimerJobActivation(string jobName, SPWebApplication webApp, SPServer server, SPJobLockType joblock)

  : base(jobName, webApp, server, joblock)

  {

    this.Title = jobName;

  }

  #endregion

 

  #region Execute Method

  public override void Execute(Guid targetInstanceId)

  {

    string strUrl = this.Properties["SiteURL"].ToString();

    string strListName = "TimerList";

    try

    {

      using (SPSite oSiteColl = new SPSite(strUrl))

      {

        using (SPWeb oWeb = oSiteColl.OpenWeb())

          {

            ////Desired Code to execute here

            ////Get the list item

       SPListItemCollection oItemColl = oWeb.Lists[strListName].Items;

            ////Add new item

            SPListItem oListItem = oItemColl.Add();

            ////Logging the data

            oListItem["Title"] = "From MySP Timer Job";

            oListItem["DateTimeFired"] = DateTime.Now;

            oListItem.Update();

          }

        }

      }

      catch (Exception ex)

      {

        PortalLog.LogString("Exception occured in Notification Update Module – {0} – {1}", ex.Message, ex.StackTrace);

      }

    }

   #endregion

}

 

The feature receiver class would be the next stop to look into

The Feature Receiver Class would be similar to any other receiver class inheriting from SPFeatureReceiver. There are few very important and interesting things to remember at this point.

i)              Pass the parameter in Constructor – “SPServer” as null (the constructor would expect this parameter)

ii)             SPJobLockType.Job – Options are ContentDatabase,Job,None

Consider the different available Job Locks:

·         SPJobLockType.ContentDatabase - Locks the content database. A timer job runs one time for each content database that is associated with the Web Application; therefore your job will run as many times for each content database associated with the Web Application that exists

·         SPJobLockType.Job - Locks the timer job. This prevents multiple instances of the job from running on a single server (Recommended).

·         SPJobLockType.None - No locks

iii)            oTimerJob.Properties["SiteURL"]=SiteCol.Url; as mentioned earlier we would need this site url in the Execute Method of Timer Job class

iv)            Good practice to delete any previous instance of the job

////Deleting the Job on deactivation of the feature

    foreach (SPJobDefinition objMyJob in jobCol)

    {

      if (objMyJob.Name == My_Timer_Job)

      jobToDelete = objMyJob;

    }

    if (jobToDelete != null)

    jobToDelete.Delete();

Do not delete the job within the enumeration rather get the job and delete it outside the enumeration.

v)             Timer Job Running Intervals

If you want to run it at 11:05 PM every day, use SPDailySchedule class to configure:

SPDailySchedule tempSchedule = new SPDailySchedule();

tempSchedule.BeginHour = 23;

tempSchedule.BeginMinute=5;

tempSchedule.BeginSecond = 0;

tempSchedule.EndSecond = 15;

tempSchedule.EndMinute = 5;

tempSchedule.EndHour = 23;

oTimerJob.Schedule = tempSchedule;               

oTimerJob.Update();

 

At the end the class would look like below. As said earlier this class will inherit from the Microsoft.SharePoint.SPFeatureReceiver class and implement the FeatureActivated & FeatureDeactivated event handlers:

public class MySPTimerJobActivationFeatureReceiver : SPFeatureReceiver

{

public const string My_Timer_Job = "Notification Timer Job";

#region Feature Activated

public override void FeatureActivated(SPFeatureReceiverProperties properties)

{

 try

  {

    ////Get the Site collection

    SPSite SiteCol = (SPSite)properties.Feature.Parent;

    SPWebApplication webApp = SiteCol.WebApplication;

    SPJobDefinitionCollection jobCol = webApp.JobDefinitions;

    ////Deleting older vrsion of Job if exists               

    SPJobDefinition jobToDelete = null;

    ////Deleting the Job on deactivation of the feature

    foreach (SPJobDefinition objMyJob in jobCol)

    {

      if (objMyJob.Name == My_Timer_Job)

      jobToDelete = objMyJob;

    }

    if (jobToDelete != null)

    jobToDelete.Delete();

  ////Install the Job Definition

  MySPTimerJobActivation oTimerJob = new      MySPTimerJobActivation(My_Timer_Job, webApp, null, SPJobLockType..Job);

 ////Set the Site Url.. will be used later from the Timer Job Class

  oTimerJob.Properties["SiteURL"] = SiteCol.Url;

  ////Run the Scheduler every 2 minutes

  SPMinuteSchedule minSchedule = new SPMinuteSchedule();

  minSchedule.BeginSecond = 0;

  minSchedule.EndSecond = 30;

  minSchedule.Interval = 2;

  oTimerJob.Schedule = minSchedule;

  oTimerJob.Update();

  }

  catch (Exception ex)

  {

  Microsoft.Office.Server.Diagnostics.PortalLog.LogString("Exception  occured in fetch query module– {0} – {1}", ex.Message, ex.StackTrace);

  }

}

#endregion

 

#region Feature Deactivating

public override void FeatureDeactivating(SPFeatureReceiverProperties properties)

{

            try

            {

                SPSite SiteCol = (SPSite)properties.Feature.Parent;

                SPWebApplication webApp = SiteCol.WebApplication;

                SPJobDefinitionCollection jobCol = webApp.JobDefinitions;

                ////Deleting older vrsion of Job if exists               

                SPJobDefinition jobToDelete = null;

                ////Deleting the Job on deactivation of the feature

                foreach (SPJobDefinition objMyJob in jobCol)

                {

                    if (objMyJob.Name == My_Timer_Job)

                        jobToDelete = objMyJob;

                }

                if (jobToDelete != null)

                    jobToDelete.Delete();

            }

            catch (Exception ex)

            {

                Microsoft.Office.Server.Diagnostics.PortalLog.LogString("Exception occured in fetch query module– {0} – {1}", ex.Message, ex.StackTrace);

            }

}

#endregion

 

Now the Timer Job is ready and we have to install it on the farm and deploy to our web application. The "recommended way" for doing this would be to create a Feature Receiver and implement the FeatureActivated event. In this event, we can instantiate the job, set the job schedule and update the Job. Below is the code snippet of the Feature.xml

<?xml version="1.0" encoding="utf-8"?>

<Feature  Id="E4922BE5-5F1E-448b-9BD9-4CA4B1C652CD"

          Title="MySPTimerJob"

          Description="This feature will create a MySp Timer Job"

          Version="1.0.0.0"

          Hidden="FALSE"

          Scope="Site"

          ImageUrl ="DECISION.GIF"

          DefaultResourceFile="core"

          ReceiverAssembly="MySPTimerJob, Version=1.0.0.0, Culture=neutral, PublicKeyToken=86244771e654f5a1"

         ReceiverClass="MySPTimerJob.MySPTimerJobActivationFeatureReceiver"

          xmlns="http://schemas.microsoft.com/sharepoint/">

</Feature>

 

1.    How to deploy it in Test/Production Environment?

Create the solution package of the timer job and deploy in production environment by automated stsadm.exe scripts.

ScriptDeployTimerJob.bat

@echo Off

echo **************************** Deployment Starts ******************************

@Set STSADM="C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\BIN\stsadm.exe"

echo -----------------------------------------------------------------------------

%STSADM% -o addsolution -filename MySPTimerJobr.wsp

echo -----------------------------------------------------------------------------

%STSADM% -o deploysolution -name MySPTimerJobr.wsp -allowgacdeployment -immediate

Rem %STSADM% -o execadmsvcjobs

echo -----------------------------------------------------------------------------

echo **************************** Deployment Starts ******************************

 

Thanks for reading. If you have some other explanation – please post a comment… I’ll be happy to hear.

...HaPpY CoDiNg

Partha (Aurum)

 

References:

http://www.codeguru.com/cpp/misc/misc/microsoftofficeoutlook/print.php/c14133

 

No comments:

Post a Comment