Question

I have a custom timer job that imports data from an external source into a specific list on a specific Web in my SharePoint 2010 installation. That is the job is only relevant to one list on one web.

I'm having a devil of a time getting a reference to that Web (and thus the list) inside my SPJobDefinition class.

I could hardcode the path of the site and get it with a new SPSite("someurl") command, but I'd prefer to get it from the context of the job somehow so it will work seamlessly across my dev, test and production environments.

I tried passing the site GUID in the SPEventreceiver that schedules the job, but whenever my SPJobDefinition object is created it seems to be using the constructor with no parameters instead of the one I used in the SPEventReciever.FeatureActivated handler.

So, my question is this:
How can I get a reference to the SPWeb object (or alternatively an SPSite object) from within the SPJobDefinition.Execute() method?

Was it helpful?

Solution

I believe you have created Web Application Scoped timer job. If not, you may want to do that. See reference (Step 2, bullet no 7)

And then as described in Step 2, bullet item 10 in link above, you can write code like this:

public override void Execute(Guid targetInstanceId)
{
  // Execute the timer job logic.
  SPWebApplication webApp = this.Parent as SPWebApplication;
  SPList taskList = webApp.Sites[0].RootWeb.Lists["Tasks"];     
}

UPDATE: In addition to that, in your FeatureActivated event, you may want to check whether the site collection (or site) in which the feature is being activate has the list that timer job will act on. If yes, then only create an instance of web application scoped timer job.

OTHER TIPS

Have a constructor on your job which takes in a SPWeb or string url, and then store the web url and list url and whatever other properties you want as a persisted property on the job.

I recommend creating a web-scoped feature to install the timer job, and create it with a name that has the web ID tacked on (for uniqueness sake in case you want the job on multiple webs). The feature activated event would have the web and all its properties right there to be able to pass into the job's constructor.

See my blog post for more information on exactly this: http://spmonkeypoint.wordpress.com/2011/11/14/custom-sharepoint-2010-timer-job

Here is the code listing from that post:

using System;
using System.Runtime.InteropServices;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;

namespace MonkeyPoints.TimerJobs
{
    [Guid("6DD003C3-2861-4F63-B974-D2653E713A74")]
    public class CustomTimerJob : SPJobDefinition
    {
        [Persisted]
        public string WebUrl;

        [Persisted]
        public string ListUrl;

        public CustomTimerJob() : base() { }

        public CustomTimerJob(SPWeb web, string listUrl) : this(JobName(web), web, listUrl) { }

        public CustomTimerJob(string jobName, SPWeb web, string listUrl)
            : base(jobName, web.Site.WebApplication, null, SPJobLockType.Job)
        {
            this.WebUrl = web.Url;
            this.ListUrl = listUrl;
        }

        protected static string JobName(SPWeb web)
        {
            return "CustomTimerJob_" + web.ID;
        }

        protected override bool HasAdditionalUpdateAccess()
        {
            return true;
        }

        public override void Execute(Guid targetInstanceId)
        {
            try
            {
                using (var site = new SPSite(WebUrl))
                using (var web = site.OpenWeb())
                {
                    // process whatever you need to on this list
                }
            }
            catch (Exception ex)
            {
                SPDiagnosticsService.Local.WriteTrace(0, new SPDiagnosticsCategory(this.Name, TraceSeverity.Unexpected, EventSeverity.Error), TraceSeverity.Unexpected, ex.Message, ex.StackTrace);
            }
        }

        public static void Install(SPWeb web, string listUrl, SPSchedule schedule)
        {
            using (SPSite site = new SPSite(web.Site.ID, web.Site.SystemAccount.UserToken))
            using (SPWeb eweb = site.OpenWeb(web.ID))
            {
                site.AllowUnsafeUpdates = true;
                eweb.AllowUnsafeUpdates = true;
                Uninstall(eweb);
                var syncJob = new CustomTimerJob(eweb, listUrl);
                syncJob.Schedule = schedule;
                syncJob.Update();
            }
        }

        public static void Uninstall(SPWeb web)
        {
            var jobName = JobName(web);
            foreach (SPJobDefinition job in web.Site.WebApplication.JobDefinitions)
            {
                if (job.Name == jobName)
                    job.Delete();
            }
        }

        public static void RunNow(string webUrl, string listUrl)
        {
            using (SPSite site = new SPSite(webUrl))
            using (SPWeb web = site.OpenWeb())
            {
                var job = new CustomTimerJob(web, listUrl);
                job.Execute(Guid.Empty);
            }
        }
    }
}

You could create a page for configure a setting against the web application, and then use the web app settings to control the SPWeb used.

To do this, you would deploy an application page into SharePoint. You'd make that available in Central Admin (probably using a WebApplicationSelector control to choose which web app to set the value for).

(The reason you'd do this in central admin is that updating the web application's properties requires write access to the configuration database - and you might only have this in central admin, depending on your configuration)

Then in 'Save' postback for you new for you'd have something like:

protected WebApplicationSelector _selector = ...

public void SaveIt() {
  SPWebAppliction webApp = _selector.CurrentItem;
  webApp.Properties["MyWeb"] = "SomeValue";
  webApp.Update();
}

Then in the Timer Job you should be able to access the web application's properties and read the value you need.

In your timerjob code you could iterate throught all site collections in the context web app and check for the existence of a custom property bag item on the RootWeb (you will need to add this yourself) that holds the (server relative) url to an SPWeb in that site collection.

Licensed under: CC-BY-SA with attribution
Not affiliated with sharepoint.stackexchange
scroll top