Sunday, August 28, 2011

Custom Alerts in SharePoint - Templates or Code?

My client needed a customized alert created each time a new issue was added to a standard Issues list. The only difference from out-of-the-box alert functionality was that customized alert Email message needed to have an Issue ID in its subject. I haven’t customized alerts before so I did some research, which showed that:

  • you can customize HTML of an alert through alert templates and use placeholders to insert values in an Email message.
  • there is an API letting you to tap into alert message processing pipeline and customize Email message before it is sent out.

There is a lot of information online on the subject, yet I still had difficulties with this seemingly easy problem. The following resources were most useful to me: MSDN (http://msdn.microsoft.com/en-us/library/bb802949.aspx), Albert Meerscheidt’s post about alerts in WSS 3.0 and Yaroslav Pentsarskyy’s post about customizing alerts in SharePoint 2010.

For me empowered with this information the task came down to writing a correct alert template, or so I thought. Take a look at this fragment of an out-of-the-box immediate alert template defining Email message subject text (immediate alerts are sent right away while digest alerts are sent later as a summary):

<Subject>
<GetVar Name="AlertTitle" />
<HTML><![CDATA[ - ]]></HTML>
<GetVar Name="ItemName" />
</Subject>


The interesting part here is placeholders “AlertTitle” and “ItemName” and the way they are used. I have a field named “ID” (it is a part of a standard Issues list), and a naive approach of writing <GetVar Name=”ID” /> didn’t get me anywhere. Same result with adding a custom text column named “ATextColumn” and then doing <GetVar Name=”ATextColumn” />. Well, the <GetVar> element is a part of CAML Vew Schema and yields a value of a local or a global variable set in current page context, but what are these placeholders and how are they set? At this point I have realized that my effort estimates were a little too optimistic. Then I bumped into a help article about alerts in WSS 2.0. Among other things it had a list of “tags” that could be included in alert templates. Here it is:

TagDescription
SiteUrlThe full URL to the site.
SiteNameThe name of the site.
SiteLanguageThe locale ID (LCID) for the language used in the site.
For example, 1033 for U.S. English.
AlertFrequencyImmediate (0), Daily (1), or Weekly (2).
ListUrlThe full URL to the list.
ListNameThe name of the list.
ItemUrlThe full URL to the item.
ItemNameThe name of the item.
EventTypeItemAdded (1), Item Modified (2), Item Deleted (4), DiscussionAdded (16),
Discussion Modified (32), Discussion Deleted (64), Discussion Closed (128),
Discussion Activated (256).
ModifiedByThe name of the user who modified an item.
TimeLastModifiedThe time the item was last modified.
MySubsUrlThe full URL to the My Alerts on this Site page in Site Settings.


Some of these tags are used in the out-of-the-box templates. I tried the rest of them and all have worked. So it appears as we are limited to using these 12 tags only, and that it is an old functionality which survived WSS 2.0, WSS 3.0 and SharePoint Foundation 2010 releases. If someone knows more about it please post a comment to validate, disprove or complete this statement.

Another thing that comes out of reflecting over template XML is usage of <GetVar Name=”OldValue#{Field}” /> or <GetVar Name=”NewValue#{Field}” /> or <GetVar Name=”DisplayName#{Field}” />. These elements are descendents of <Fields> element for immediate alerts, and of <RowFields> element for digest alerts. If you inspect generated alert body HTML then you would notice that fields (or columns) are iterated over and their values are inserted in the body except when a field is listed inside of <ImmediateNotificationExcludedFields> or <DigestNotificationExcludedFields> elements. So then <Fields> element establishes a loop, and the {Field} must be a contextual variable inside this loop. With the above syntax you can get display name, old or new values for each field into the Email body and exclude fields you don’t want to be listed.

Great, but how do I get the ID into my Email’s subject? I don’t want to list a bunch of fields in my subject, just the ID, so I don’t want to use <Fields> element there. The API did the trick. I have created a class implementing IAlertNotifyHandler interface and used regular expressions to replace a placeholder with a value:

public class MessageCustomizer : IAlertNotifyHandler

{
public bool OnNotification(SPAlertHandlerParams parameters)
{
string webUrl = parameters.siteUrl + parameters.webUrl;

using (SPSite site = new SPSite(webUrl))
using(SPWeb web = site.OpenWeb())
{
string to = parameters.headers["To"];
string subjectTemplate = parameters.headers["Subject"];
string itemId = parameters.eventData[0].itemId.ToString();

//
// Below we are replacing a placeholder we have
// created in our alert template with the actual value.
//

string subject = Regex.Replace(
subjectTemplate,
"#ID#",
itemId,
RegexOptions.IgnoreCase);
bool result = SPUtility.SendEmail(
web,
false,
false,
to,
subject,
parameters.body);
return result;
}
}
}

We still need a customized alert template – firstly to insert our own custom placeholder (in my example #ID#) and secondly to register the MessageCustomizer class so its OnNotification() method would get called. Here is updated fragment defining Email’s subject:

<Subject>
<HTML><![CDATA[Issue ID #ID#: ]]></HTML>
<GetVar Name="AlertTitle" />
<HTML><![CDATA[ - ]]></HTML>
<GetVar Name="ItemName" />
</Subject>

Registration of MessageCustomizer class and its assembly is done inside of <NotificationHandlerClassName> and <NotificationHandlerAssembly> elements of the template:

<NotificationHandlerAssembly>AlertCustomization, Version=1.0.0.0, Culture=neutral, PublicKeyToken=bda7bcef852778f0</NotificationHandlerAssembly>
<NotificationHandlerClassName>AlertCustomization.MessageCustomizer</NotificationHandlerClassName>

We can now wire up the templates and the handler. Handler’s assembly needs to go to the GAC, then you copy and rename alerttemplates.xml file sitting in 14\TEMPLATE\XML folder, add your template (or again copy an existing one and change it), then you register this file with SharePoint running stsadm –o updatealerttemplates command. I didn’t find PowerShell cmdlets equivalent to this command. Lastly you need to assign your template to a list using SPList.AlertTemplate property. You can write a PowerShell script or use a feature receiver. The latter approach is demonstrated in Yaroslav Pentsarskyy’s post mentioned earlier.

So we are arriving at a standard “It depends…” answer to the question of whether to customize alerts via templates or via code. Regardless of which approach works for you for any such customization that is not an ad hoc fix or a proof of concept you are looking into creating a package including deployment script, a SharePoint solution and possibly a feature with feature receiver. The functionality is almost identical between WSS 3.0 and SharePoint Foundation 2010 with the latter adding SMS support. Also with Visual Studio 2010 it is much easier to package things, yet you are probably still looking into a few hours of work to get it done right.