Take the 2-minute tour ×
SharePoint Stack Exchange is a question and answer site for SharePoint enthusiasts. It's 100% free, no registration required.

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?

share|improve this question

4 Answers 4

up vote 2 down vote accepted

I believe you have created Web Application Scoped timer job. If not, you may want to do that. See reference: http://msdn.microsoft.com/en-us/library/ff798313.aspx (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.

share|improve this answer
    
This assumes he needs the first sitecollection in the web app and the root web of that site collection. I think the question is more related to how you can have the timer job point to a specific site in a specific site collection, and how to define this configuration. –  Jaap Vossers Jun 20 '11 at 12:59
1  
I think the user does not know how to get reference to SPSite and/or SPWeb and perhaps that's the reason trying to use "new" operator and concerned about hardcoding the path for various environment. So the code above is not a final solution but to give him an idea that he can get SPSite without using new operator or hardcoding the URL. –  Ashish Patel Jun 20 '11 at 15:07
1  
Actually this post WAS kind of helpful and led me to a workable answer. I did want to avoid the new operator because the URL would change in production (I guess I could have used a config entry), but mainly I just wanted a way to get a reference to an object that I could use to get the only site collection in the current web app, so this worked fine. –  JohnFx Jun 21 '11 at 20:01

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);
            }
        }
    }
}
share|improve this answer
    
Thanks! But why do we need Guid attribute on this class? –  Сергій Oct 9 '12 at 6:34
2  
The timer job is serialized/deserialized from the database as XML, and the base class SPAutoSerializingObject which automates this deserialization relies on the Guid to determine which specific type to deserialize to. Because of this, the Guid attribute is extremely important to not forget. –  Dennis George Oct 9 '12 at 10:30

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.

share|improve this answer
    
this answer is slightly incomplete.. how would the timer job know which web application to access? –  Dennis George Oct 9 '12 at 10:22
    
also, what if you need multiple instances of the timer job, each with different values for the same setting? –  Dennis George Oct 9 '12 at 10:23
    
You'd need to add and use a constructor that uses the web app. One of the constructors for the SPJobDefinition is like that: msdn.microsoft.com/en-us/library/ms427704.aspx –  Andy Burns Oct 9 '12 at 10:47

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.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.