Modifying an alert e-mail for discussion lists
This post is about discussion lists and customizing their alerts, maybe it’s not the most shocking topic in the world but it took me some time to get it all working and there are not a lost of posts around, so hence this one.. ;)
I was at a customer last week week who had some interesting questions, one of the questions was “Is it possible to modify the alerts that are sent from a discussion list, since the link is to the item itself, rather than the discussion it’s part of?”
See the above picture? I’m talking about the “View When will SP1 be released” link, by default, this points to Lists/<ListName>/DispForm.aspx?ID=<ID> . But, the discussion list is a bit different from other lists in SharePoint. For starters the list uses two content types, Discussion and Message. When you start a new discussion, you are actually creating a folder. Then each reply to that discussion is being stored in that folder. If you look closely at the URL when displaying a discussion, there is no dispform.aspx being used to render the page but instead there is a Threaded.aspx or Flat.aspx (depending on the view that is choosen). Next, when you look even more close, it accepts a parameter called RootFolder that is a server relative URL that looks like this “RootFolder=/sites/<siteCollection>/Lists/<ListName>/<DiscussionName> ” or as in my example it shows “ /sites/alert/Lists/Team Discussion/When will SP1 be released”. In order to prove that every discussion is a folder, I came up with the following piece of code:
SPList list = web.Lists.TryGetList("Team Discussion");
foreach (SPListItem dicussion in list.Folders)
{
Literal literal = new Literal();
literal.Text =
"<BR>"+
"Discussion : "+ dicussion.Name.ToString() + "<BR>" +
"Replies : " + dicussion.Folder.ItemCount.ToString();
this.Controls.Add(literal);
}
Which results in
And here is the actual list (to prove my point ;)
Back to the question, to show the discussion instead of the reply, we have to change the URL from Dispform.aspx?<ListItemId> to RootFolder?<Server-relativeUrl-of-discussion>.. seems pretty easy right? So, let’s get to it!
This was actually for me the first time I’ve ever looked at into modifying alerts. I knew this was possible by editing the alerttemplates.xml file but like I said, this was my first time with customizing alerts. So I went on searching for some articles and I found this blogpost from a few years ago from the Developer Documentation team called Customizing Alert Notifications and Alert Templates in Windows SharePoint Services 3.0 and while reading through, the last paragraph called my attention since it said “In my next post I'll talk about how to use the IAlertNotifyHandler interface to customize alerts.” That seemed like something was something was possible using the Object Model (yes!) instead of modifying the XML..
Essentially that approach is about three steps
Create a custom handler that intercepts the email.
public class SPAlertNotification : IAlertNotifyHandler
{
public bool OnNotification(SPAlertHandlerParams ahp)
{
//Makes life easier when debugging
#if (DEBUG)
Debugger.Break();
#endif
string rootFolder = string.Empty;
string viewName = "Flat.aspx"; // or "Threaded.aspx"
using (SPSite site = new SPSite(ahp.siteId))
{
using (SPWeb web = site.OpenWeb(ahp.webId))
{
//In case of add (1) or a change (2) we modify the body of the message
if (ahp.eventData[0].eventType == 1 || ahp.eventData[0].eventType == 2)
{
SPList list = web.Lists[ahp.a.ListID];
SPListItem item = list.GetItemByIdSelectedFields(ahp.eventData[0].itemId, "ContentType", "Url");
//Each discussion is a folder in the discussion list, the RootItem being of ContentType Dicussion
if (item.ContentTypeId.Parent.Equals(SPBuiltInContentTypeId.Discussion))
rootFolder = item.Folder.ServerRelativeUrl;
//Each reply in a discussion is a listItem in the folder, the ContentType of a reply is Message
if (item.ContentTypeId.Parent.Equals(SPBuiltInContentTypeId.Message))
rootFolder = web.GetFolder(item.Url).ParentFolder.ServerRelativeUrl;
//Instead of returning the DispForm.aspx, we are returning the url of the view (Flat.aspx)
//which requires the rootfolder of the discussion.
ahp.body = ahp.body.Replace("DispForm.aspx?ID=" + item.ID.ToString(),
viewName +"?RootFolder=" + rootFolder);
}
//Send the email the alert
SPUtility.SendEmail(web, true, false, ahp.headers["to"].ToString(), ahp.headers["subject"], ahp.body);
}
}
return false;
}
}
Create our own AlertTemplates.xml file, (creating a copy of the existing one and using the copy is the easiest)
- Per type of list, you can add a reference to your custom handler. In this case, we are only interested in modifying the email that is sent for discussion lists. Let’s see how an alerttemplate looks like
<AlertTemplate Type="List" Name="SPAlertTemplateType.DiscussionBoard">
<EventTypes IsVisible="True"/>
<Format>…. </Format>
<Properties>
<!--This is our custom handler being defined here –>
<NotificationHandlerAssembly>SP.DiscussionAlert, Version=1.0.0.0, Culture=neutral, PublicKeyToken=dc834ccbbe356fc6</NotificationHandlerAssembly>
<NotificationHandlerClassName>SP.DiscussionAlert.SPAlertNotification</NotificationHandlerClassName>
<NotificationHandlerProperties></NotificationHandlerProperties>
</Properties>
<Filters IsVisible="True"> ….</Filters>
</AlertTemplate>
- Per type of list, you can add a reference to your custom handler. In this case, we are only interested in modifying the email that is sent for discussion lists. Let’s see how an alerttemplate looks like
Register the customized alerttemplate file to the WebApplication of your choice, now this can be done using STSADM but that’s old fashioned ;) So why not register it using the OM and let the IT Pro just click on “Activate” when the solution is deployed eh? Much more fancy imo.. therefore let’s create a WebApplication-scoped FeatureReceiver like this:
/// <summary>
/// Adds the modified alert templates file to the webapplication
/// </summary>
/// <param name="properties"></param>
public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
SPWebApplication webApplication =
(SPWebApplication) properties.Feature.Parent;
SPAlertTemplateCollection alertTemplateCollection =
new SPAlertTemplateCollection(
(SPWebService)(webApplication.Parent));
alertTemplateCollection.InitAlertTemplatesFromFile(
SPUtility.GetGenericSetupPath("TEMPLATE\\XML") +
"\\CustomizedAlertTemplates.xml");
}
/// <summary>
/// Puts the original alert templates file back to the webapplication
/// </summary>
/// <param name="properties"></param>
public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
{
SPWebApplication webApplication =
(SPWebApplication)properties.Feature.Parent;
SPAlertTemplateCollection alertTemplateCollection =
new SPAlertTemplateCollection(
(SPWebService)(webApplication.Parent));
alertTemplateCollection.InitAlertTemplatesFromFile(
SPUtility.GetGenericSetupPath("TEMPLATE\\XML")
}
So there you have it.. I hope the inner workings with discussion list is a bit more clear now. I still haven’t covered the difference between the Threaded and Flat view and how SharePoint is doing that but maybe that’s another post..
Let me know if you find the alert modification useful :)