When working on a SharePoint project recently I was faced with complexities related to development of SharePoint timer jobs, or more precisely – ones related to ensuring their proper quality. Indeed the timer jobs are not trivial when it gets to their debugging. Below is my approach to dealing with the issue:
1. First things first – you need to install your timer job, likely using a custom feature receiver. I am quoting Andrew Connell again, he has an excellent post describing the details.
2. You want to be able to start your timer job explicitly during testing (SharePoint does not let you do this other than through the API). There are two obstacles here:
- firstly you need the harness code to invoke the API,
- secondly you need to impersonate the identity of the Timer Service when invoking the API.
I have chosen to “abuse” the Unit Testing infrastructure given to me by Visual Studio to host my harness logic. You can either do the same or write a console application for this purpose. My unit test thus looked as follows:
[TestClass]
public class TimerJobsTest
{
private Impersonator impersonator;
private SPSite site;
public TimerJobsTest()
{
impersonator = new Impersonator();
}
[TestInitialize]
public void Initialize()
{
using (SecureString securePassword = new SecureString())
{
// Omitted setting secure password and
// retrieval of other variables for brevity.
securePassword.MakeReadOnly();
impersonator.ImpersonateUser(
adminUserName,
adminDomain,
securePassword);
}
site = new SPSite(siteUrl);
}
[TestMethod]
public void TestJob()
{
foreach (SPJobDefinition job in site.WebApplication.JobDefinitions)
{
if (job.Title.Equals(
"My Job Title",
StringComparison.OrdinalIgnoreCase))
{
job.Execute(site.ContentDatabase.Id);
break;
}
}
}
[TestCleanup]
public void CleanUp()
{
site.Dispose();
impersonator.StopImpersonating();
impersonator.Dispose();
}
}As you see I was using an Impersonator class, which allowed me to control the user identity when invoking my job. This is a custom class I wrote for this and similar situations. You can get its source code here. It is far from being a rocket science (and it shouldn’t be) as it is using the old common Win32 impersonation API. Yet one thing about it is interesting: using SecureString type, something I was referring to in my earlier post. If you examine the code of Impersonator.cs, you could see how the managed SecureString is marshaled to the unmanaged code when the Win32 API is called. By the way if you are ever using WPF PasswordBox control, it is capable of passing on a SecureString containing password as of .NET Framework 3.5 SP1; then my impersonator becomes really handy.True, you don’t need to be fancy when testing your own code. The Impersonator.cs is meant to be a reusable secure facility for impersonation so it adds some overhead. Feel free to change it if you do not intend to use it in production environment.
3. Once this is done, you set breakpoints, attach to the WSS Timer Service process and step through your job’s code as (I hope) you normally do.
Andrew Connell has really covered this topic well. Here is his post that I've found after publishing mine - he suggests a very simple, even though not so much versatile way to debug the jobs.
ReplyDelete