Introduction
Its been about 15 months since my last post, the main reason for the delay would be my 14 month old son :-).
Ive been working primarily with event and feature receivers in MOSS 2007 for the last few weeks and this post will describe some of the issues that I encountered and their solutions (or workarounds) if they exist mainly for my own benefit, as my memory doesnt seem to be what it used to, but I wouldnt be unhappy if it helps someone else out.
VseWSS Project Template Grumps
These are generally fairly good. I use them as much as possible as it cuts down on the amount of bolierplate code you need to type and on the amount of faffing getting the right GUIDs in the feature.xml, element manifest, class attributes, etc.. However theyre not perfect by any means, and for me its becasue of the following reasons:
- You get item and list event receivers but not an e-mail receiver. Now I dont suppose theyre all that common but the option to add one in after the project has been created would be nice, we can at least do that with feature receivers albeit with a small amount of faffing (see next point).
- To me the point of adding a feature receiver to a list definition project would be to have its code run when you activate/deactivate the list or just as likely an instance of the list, but when you add one you need to manually add the ReceiverClass and ReceiverAssembly attributes to the feature.xml of the feature you want the code to run against (including getting the public key token of the assembly).
- In the WSP View, after youve made some changes to the project the reference to the Receiver class file goes AWOL and never comes back. You can obviously still get to it in the Solution Explorer but its just a bit annoying, particularly when it used to be there.
- In the list definiton schema.xml file you add your custom fields, set the ShowInNewForm and ShowInEditForm attributes to TRUE, package and deploy the solution and when you create a new item from the list your field doesnt display. The way round this is remove the element from the schema (as the built-in Item content type doesnt have your custom fields). This one isnt that big a deal but I still spent about half an hour on Google finding out why the fields were not displaying.
E-mail Event Receiver and the Windows SharePoint Services Timer Service
After implementing the e-mail event receiver, packaging and deploying you notice theres a problem with the execution, so you deactivate and uninstall the feature, retract and delete the solution, modify the code, re-package and re-deploy. Make the receiver fire again and the same problem exists. The reason for this is that the receiver code is executed by the OWSTIMER process (the Windows SharePoint Services Timer Service) and it doesnt use the shiny new DLL you just deployed to the GAC, it creates it own local copy and wont use the updated assembly until the service is restarted, so at the command line:
net stop "Windows SharePoint Services Timer"
net start "Windows SharePoint Services Timer"
and you should see that the the receiver uses the updated DLL.
Enable Incoming E-mail on Document Library on Feature Activation
Ive saved the worst for last, this one caused me no end of pain. I have a list definition project that sets up a library based on a document library, the project has the list definition, list instance, item and list event receivers. I subsequently added a feature receiver for the purpose of enabling incoming e-mail on the library and setting the e-mail alias. Simple enough looking code to do this (if you need it you can find it here). However when I deployed the solution (using STSADM) I noticed an error message in the command window: "Error in the application". Wow Microsoft, youve really excelled yourself with the verbosity of that one! Looking at the log file there was an exception:
{xxxx.xxxx.SharePoint.Lists.xxxxxxxxxxxx.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.FeatureActivated} : Exception Message : Error in the application., Exception Stack Trace : at Microsoft.SharePoint.SPList.UpdateDirectoryManagementService(String oldAlias, String newAlias) at Microsoft.SharePoint.SPList.Update(Boolean bFromMigration) at Microsoft.SharePoint.SPList.Update()…
UpdateDirectoryManagementService? So it failing when trying to set the e-mail alias. OK so a bit of Googling and Ive found this thread, which says that unless you run the code using an Application Pool account it plain wont work. So we need to ask clients to log in using an Application Pool account to be able to install the feature. I think not…
Enter the RunAsSystem method.
If you use the SharePoint object model a lot youll be familiar with the RunWithElevatedPrivileges method, the RunAsSystem uses the same principal but in my opinion in a cleaner way. Its been about 4 or 5 months since I originally started this admittedly short post so I have forgotten where I found this class, but here it is, and if I find out from whence it came I'll get round to editing this post. Anyway, to use this create a method that taks an SPSite object as a parameter that you want to run using an elevated account and call it thusly: site.RunAsSystem(MyMethod);
public static class SPSiteExtensions
{
public static SPUserToken GetSystemToken(this SPSite site)
{
SPUserToken token = null;
bool tempCADE = site.CatchAccessDeniedException;
try
{
site.CatchAccessDeniedException = false;
token = site.SystemAccount.UserToken;
}
catch (UnauthorizedAccessException)
{
SPSecurity.RunWithElevatedPrivileges(() =>
{
using (SPSite elevSite = new SPSite(site.ID))
token = elevSite.SystemAccount.UserToken;
});
}
finally
{
site.CatchAccessDeniedException = tempCADE;
}
return token;
}
public static void RunAsSystem(this SPSite site, Action<SPSite> action)
{
using (SPSite elevSite = new SPSite(site.ID, site.GetSystemToken()))
action(elevSite);
}
public static T SelectAsSystem<T>(this SPSite site, Func<SPSite, T> selector)
{
using (SPSite elevSite = new SPSite(site.ID, site.GetSystemToken()))
return selector(elevSite);
}
public static void RunAsSystem(this SPSite site, Guid webId, Action<SPWeb> action)
{
site.RunAsSystem(s => action(s.OpenWeb(webId)));
}
public static void RunAsSystem(this SPSite site, string url, Action<SPWeb> action)
{
site.RunAsSystem(s => action(s.OpenWeb(url)));
}
public static void RunAsSystem(this SPWeb web, Action<SPWeb> action)
{
web.Site.RunAsSystem(web.ID, action);
}
public static T SelectAsSystem<T>(this SPSite site, Guid webId, Func<SPWeb, T> selector)
{
return site.SelectAsSystem(s => selector(s.OpenWeb(webId)));
}
public static T SelectAsSystem<T>(this SPSite site, string url, Func<SPWeb, T> selector)
{
return site.SelectAsSystem(s => selector(s.OpenWeb(url)));
}
public static T SelectAsSystem<T>(this SPWeb web, Func<SPWeb, T> selector)
{
return web.Site.SelectAsSystem(web.ID, selector);
}
}