Summary: See how to retrieve the site collection URL at run time when using a timer job for deployment.

In real word scenarios, a common requirement while using timer jobs is to deploy the job in such a way that it runs on only one machine in the farm and retrieves the site collection URL at run time. In this type of situation, it is not recommended that you use a hardcoded URL to compare to the requested site collection URL in the content database or that you use an index to get the URL. To deploy the same solution on different production servers, you would need to modify the deployment code for each individual server which is tedious at best and prone to errors.

There are various ways to pass the URL, or any other values, to a timer job. For example, you can use configuration files, external files, property bags such as owstimer.exe.config, XML files, or the property bag for a SharePoint 2010 object. A property bag is simply a container for a series of name/value pairs that correspond to the properties of a feature or object. However, using external files or modifying configuration files present their own issues such as security concerns and is not a recommended best practice. Instead, working with the property bag is straight forward. Just add a new entry to the property bag of the SharePoint object and read it back out when needed.

In this post, you see how to create a timer job lock by using the SPJobLockType enumeration and then fetch the site collection URL at run time by using a property bag.

Creating the Custom Timer Job and Adding a Lock

The first step in creating a custom timer job is to create a class that inherits from the SPJobDefinition class. The SPJobDefinition class contains three overridden constructors. The constructor that has no parameters is reserved for internal use. The other two constructors set the timer job name, the parent Web application or service (such as the search indexer), the server, and the job lock type. The lock type helps prevent multiple jobs from running at the same time.

There are three different types of locking, as defined by the SPJobLockType enumeration:

  • SPJobLockType.ContentDatabase: Locks the content database. A timer job runs one time for each content database that is associated with the Web application.
  • SPJobLockType.Job: Locks the timer job so that it runs on only one machine in the farm.
  • SPJobLockType.None: Specifies no locks. The timer job runs on every machine on which the parent service is provisioned.

The following constructor code uses the SPJobLockType.Job lock to lock the timer job so that it runs on only one machine in the farm.

using System;

using System.Net;

using Microsoft.SharePoint;

using Microsoft.SharePoint.Administration;

 

namespace MSDN.SharePoint.Samples {

  public class GetSiteCollectionUrlJob : SPJobDefinition {

 

    public  GetSiteCollectionUrlJob () : base() { }

 

    public  GetSiteCollectionUrlJob (SPWebApplication webApp)

      : base(Globals.JobName, webApp, null, SPJobLockType.Job) {

      this.Title = Globals.JobName;

    }

 

    public override void Execute(Guid targetInstanceId) {

            // Put your job's code here.

    }

 

  }

}

 

The only other requirement is that the class GetSiteCollectionUrlJob override the Execute virtual method defined in the SPJobDefinition class. This method is called when the job is executed and contains the timer job functionality. It receives a single parameter: the ID of the content database that is being processed by the job. This is where you need the site collection URL on which timer job is deployed at run time. In the next section, you see how to set that value at feature activation.

Creating the Feature for the Timer Job Deployment

Now that you have created the job, you must add a SharePoint Feature to enable administrators to use the functionality in SharePoint.

To add a feature to your project

1. Right-click the Features folder and choose Add Feature from the context menu. The feature description screen is displayed as shown in Figure 1.

Figure 1. Add the feature properties

Figure01

2. Type a user-friendly title and description.

3. Because this feature registers the job with one of the site collection in the farm, set the scope to Site.

4. Click the Save button on the toolbar.

Now that you have created and configured the feature, you must add an event receiver to the feature so that you can add the code to register the job and set the property bag value for the site collection URL. An event receiver is a class that runs code when certain events occur in SharePoint. In this instance, you set the property bag value when job is activated on a site collection.

To add the event receiver

1. Right-click the feature you created previously and then choose Add Event Receiver from the context menu.

2. Next, uncomment the FeatureActivated and FeatureDeactivating methods.

3. Now, add code in the FeatureActivated method to register the timer job with SharePoint. This is also where you add the site collection URL to the property bag of the timer job instance of the GetSiteCollectionUrlJob class as shown in the following code.

/// <summary>

/// Occurs after a Feature is activated.

/// </summary>

/// <param name="properties">An <see cref="T:Microsoft.SharePoint.SPFeatureReceiverProperties"/> object that represents the properties of the event.</param>

public override void FeatureActivated(SPFeatureReceiverProperties properties)

{

   if (properties != null)

   {

      SPSite site = properties.Feature.Parent as SPSite;

      // make sure the job isn't already registered

      foreach (SPJobDefinition job in site.WebApplication.JobDefinitions)

         {

            if (string.Equals(job.Name, “GetSiteCollectionUrlJob”))

               {

                  job.Delete();

               }

         }

      //// install the job

      GetSiteCollectionUrlJob siteCollectionUrlJob = new GetSiteCollectionUrlJob (“GetSiteCollectionUrlJob”, site.WebApplication);

      //// Below you set the property bag with site collection URL on which your timer job is registered , that you can use in execute method

     siteCollectionUrlJob.Properties.Add("SiteCollectionUrl", site.Url);

     SPMinuteSchedule schedule = new SPMinuteSchedule();

     schedule.BeginSecond = 0;

     schedule.EndSecond = 59;

     schedule.Interval = 5;

     siteCollectionUrlJob.Schedule = schedule;

     siteCollectionUrlJob.Update();

   }

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

4. In the FeatureDeactivatiing method, you write the code to unregister the job as shown in the following code.

/// <summary>

        /// Occurs when a Feature is deactivated.

        /// </summary>

        /// <param name="properties">An <see cref="T:Microsoft.SharePoint.SPFeatureReceiverProperties"/> object that represents the properties of the event.</param>

        public override void FeatureDeactivating(SPFeatureReceiverProperties properties)

        {

            if (properties != null)

            {

                SPSite site = properties.Feature.Parent as SPSite;

 

                // delete the job

                foreach (SPJobDefinition job in site.WebApplication.JobDefinitions)

                {

                    if (string.Equals(job.Name, “GetSiteCollectionUrlJob”))

                    {

                        job.Delete();

                    }

                }

            }

       }

 

 

 

 

 

 

 

 

 

 

Using the Property Bag Value in the Execute Method

Now that the value has been set in the property bag of the timer job class, you can access it in the Execute method as seen in the following code. This eliminates the need for comparing URLs to identify the site collection in the content database or by using indexes.

using System;

using System.Net;

using Microsoft.SharePoint;

using Microsoft.SharePoint.Administration;

 

namespace MSDN.SharePoint.Samples {

  public class GetSiteCollectionUrlJob : SPJobDefinition {

 

    public  GetSiteCollectionUrlJob () : base() { }

 

    public  GetSiteCollectionUrlJob (SPWebApplication webApp)

      : base(Globals.JobName, webApp, null, SPJobLockType.Job) {

      this.Title = Globals.JobName;

    }

 

    public override void Execute(Guid targetInstanceId) {

            string siteCollectionUrl = string.Empty;

           

            // Below you are fetching the same property bag value you set in feature activation

 

             if (!string.IsNullOrEmpty(this.Properties["SiteCollectionUrl"].ToString()))

                {

                   siteCollectionUrl = this.Properties["SiteCollectionUrl"].ToString();

                }

          

            try

            {

                using (SPSite site = new SPSite(pwaUrl))

                {

                    using (SPWeb web = site.OpenWeb())

                    {

                        // Here you can write code to work with specific site content.

                    }

                }

            }

            catch (Exception ex)

            {

               

            }

 

      }

 

  }

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

By using the process describes here, you can get the site collection URL at runtime without dependency on any content database. This also enables you to deploy the package in different environments without making any changes in code or indexes.

Conclusion

This post described a technique that enables a timer job to run on only one server in the farm and to retrieve the site collection URL at runtime without any manual manipulations. This ensures that the timer job package ready to be deployed to different environments with any changes in code.

Additional Resources

Creating Timer Jobs in SharePoint 2010 That Target Specific SharePoint Services (Wrox)