Thursday, November 1, 2012

Service based custom timer job in SharePoint 2010

Hi Friends,

There are many articles you will find on internet about Timer Jobs in SharePoint 2010 which is based on web application but not on service. That is the reason i am sharing this article.

Timer job based on web application having one major problem. i.e- Job will not work if that application not worked or crashed or say front end on which web application hosted is down.

Here i will not tell whole story of Timer Job, you will get better information here

A Complete Guide to Writing Timer Jobs in SharePoint 2010

To create timer job based on service or say service application, we will have to do few things, this are below
  • Create custom service and add to the local farm
  • Create service instance based on above custom service and add this to all the servers in the farm
  • Create timer job
  • Associate timer job to service


Steps in detail

1: Open Visual Studio 2010 and create farm based SharePoint Solution (use blank SharePoint Project Template), add class file named CustomTimerJobService.cs, code is below


using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SharePoint.Administration;

namespace CustomTimerJobService
{
    [System.Runtime.InteropServices.Guid("6D53ECA1-E5E4-47CF-961F-28D80C8C5B98")]
    public class CustomTimerJobService : SPService
    {
        public const string serviceName = "Custom Timer Job Service";

        //private static CustomTimerJobService local;

        public CustomTimerJobService() { }

        public CustomTimerJobService(SPFarm farm) : base("CustomTimerJobService", farm)
        { }

        //public static CustomTimerJobService Local
        //{
        //    get
        //    {
        //        if (CustomTimerJobService.local == null)
        //        {
        //            CustomTimerJobService.local = SPFarm.Local.Services.GetValue<CustomTimerJobService>("CustomTimerJobService");
        //        }

        //        return CustomTimerJobService.local;
        //    }
        //}

        public override void Provision()
        {
            base.Provision();
        }

        public override string TypeName
        {
            get
            {
                return serviceName;
            }
        }

        public override string DisplayName
        {
            get
            {
                return serviceName;
            }
        }       
    }   
}


2: Add class file named CustomTimerJobServiceInstance.cs, code is below

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SharePoint.Administration;

namespace CustomTimerJobService
{
    public class CustomTimerJobServiceInstance : SPServiceInstance
    {
        public const string serviceDescription = "Custom Service for service based timer jobs";

        public CustomTimerJobServiceInstance() : base()
        { }

        public CustomTimerJobServiceInstance(string name, SPServer server, CustomTimerJobService service) : base(name, server, service)
        { }

        public override string Description
        {
            get
            {
                return serviceDescription;
            }
        }
    }
}


3: Add feature of scope Farm to the solution and then add feature receiver.

Here we will add service to the local farm, then instantiate above service on all the servers in the farm.

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

namespace CustomTimerJobService.Features.AddTimerJobService
{
    [Guid("8daf90a1-df8a-4646-a606-0646498a6242")]
    public class AddTimerJobServiceEventReceiver : SPFeatureReceiver
    {
        public const string serviceName = "Custom Timer Job Service";

        public override void FeatureActivated(SPFeatureReceiverProperties properties)
        {
            //1: Add service in local farm
            //2: Add service instance of this service on every server in the farm

            //Create service definition instance
            CustomTimerJobService timerJobService = new CustomTimerJobService(SPFarm.Local);

            //Get all services from local farm and add our custom service in farm
            SPServiceCollection services = SPFarm.Local.Services;
            services.Add(timerJobService);
            SPFarm.Local.Update();

            //Get all servers in local farm
            SPServerCollection servers = SPFarm.Local.Servers;

            //Add service instance on all the servers
            foreach (SPServer server in servers)
            {
                //Create new service instance based on our custom service and add in server
                CustomTimerJobServiceInstance timerJobServiceInstance = new CustomTimerJobServiceInstance(serviceName, server, timerJobService);
                server.ServiceInstances.Add(timerJobServiceInstance);
                server.Update();
            }
        }

        public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
        {
            //1: Remove service instance of this service on every server in the farm
            //2: Remove service in local farm

            //Get all servers in local farm
            SPServerCollection servers = SPFarm.Local.Servers;

            foreach (SPServer server in servers)
            {
                //Remove instance on every server
                CustomTimerJobServiceInstance timerJobServiceInstance = server.ServiceInstances.GetValue<CustomTimerJobServiceInstance>();
                    
                if (timerJobServiceInstance != null && timerJobServiceInstance.DisplayName.ToString().Equals(serviceName, StringComparison.OrdinalIgnoreCase))
                {
                    server.ServiceInstances.Remove(timerJobServiceInstance.Id);
                    server.Update();                       
                }
            }

            //Remove service from the farm
            CustomTimerJobService timerJobService = SPFarm.Local.Services.GetValue<CustomTimerJobService>();

            if (timerJobService != null)
            {
                timerJobService.Delete();
                SPFarm.Local.Update();                   
            }           
        }
    }
}


Next step is to create timer job and associate it to service


Steps in detail

1: Open Visual Studio 2010 and create farm based SharePoint Solution (use blank SharePoint Project Template), add class file named CustomTimerJob.cs.cs, code is below


using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;

namespace CustomTimerJobDemo
{
    public class CustomTimerJob : SPFirstAvailableServiceJobDefinition
    {
        //If you are taking configuration values from the site where you are going
        //to activate this timer job feature then make persisted property
        //By doing this we can use it further
        [Persisted]
        public string configSiteURL = null;

        public CustomTimerJob()
            : base()
        { }

        public CustomTimerJob(string jobName, SPService service, SPSite site)
            : base(jobName, service)
        {
            this.Title = jobName;
            this.configSiteURL = site.Url;
        }


        public override void Execute(SPJobState jobState)
        {
            if (jobState.ShouldStop != true)
            {
                this.UpdateProgress(10);

                //To Do: Do some activity here

                this.UpdateProgress(100);
            }
        }
    }
}


2: Add feature of scope Site to the solution and then add feature receiver.

Here we will associate timer job to service


using System;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Security;
using Microsoft.SharePoint.Administration;
using Microsoft.SharePoint.Deployment;

namespace CustomTimerJobDemo.Features.CustomTimerJobDemo
{
    [Guid("a26f0c48-6464-4438-bbd0-35fac5218a0b")]
    public class CustomTimerJobDemoEventReceiver : SPFeatureReceiver
    {
        public const string jobName = "Custom Timer Job for demo";

        public override void FeatureActivated(SPFeatureReceiverProperties properties)
        {
            SPSite currentSite = properties.Feature.Parent as SPSite;

            //Check current context whether it is a normal feature activation or through any custom deployment job
            //Timer job should not be created if feature activation occurs through any custom deployment job
            if (!SPImportContext.Current.IsRunning)
            {
                SPService timerJobService = GetCustomTimerJobService();

                if (timerJobService != null)
                {
                    // Remove job if it already associated with the service
                    foreach (SPJobDefinition job in timerJobService.JobDefinitions)
                    {
                        if (job.Name.Equals(jobName, StringComparison.OrdinalIgnoreCase))
                        {
                            //Deleting existing job
                            job.Delete();
                        }
                    }

                    //Create new job here
                    CustomTimerJob timerJob = new CustomTimerJob(jobName, timerJobService, currentSite);

                    SPDailySchedule dailySchedule = new SPDailySchedule();
                    dailySchedule.BeginMinute = 0;
                    dailySchedule.EndMinute = 59;
                    dailySchedule.BeginHour = 0;
                    dailySchedule.EndHour = 12;

                    timerJob.Schedule = dailySchedule;
                    timerJob.Update();
                }
            }                                     
        }

        public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
        {
            //Get custom timer service
            SPService timerJobService = GetCustomTimerJobService();

            if (timerJobService != null)
            {
                // Remove job
                foreach (SPJobDefinition job in timerJobService.JobDefinitions)
                {
                    if (job.Name.Equals(jobName, StringComparison.OrdinalIgnoreCase))
                    {
                        //Deleting job
                        job.Delete();
                    }
                }
            }
           
        }

        private static SPService GetCustomTimerJobService()
        {
            SPService timerJobService = null;           
            string serviceName = "Custom Timer Job Service";
                           
            //Get all services from the farm
            SPServiceCollection services = SPFarm.Local.Services;

            foreach (SPService service in services)
            {
                if (service.DisplayName.Equals(serviceName, StringComparison.OrdinalIgnoreCase))
                {
                    timerJobService = service;
                    break;
                }
            }

            if (timerJobService == null)
            {
                throw new Exception("Service not available. Please check service on servers.");
            }           
           
            return timerJobService;
        }
    }
}


This is done now, you can go to central admin and run timer job or it will run based on schedule you configured for it.

Bye Good Day....!