Sharepoint 2010 / customization of search alerts
I want to talk about my own successful experience in customizing search alerts in Sharepoint 2010 using IAlertNotifyHandler.
At first, the task seemed trivial. It seems like everything is simple. In the MSDN documentation there is an article describing the process itself, there are examples on other resources. But, as it turned out, customization of all types of alerts except search ones is described everywhere . Since I haven’t found a solution all over the Internet, I’m presenting my own (maybe not the most optimal, but working).
There is an SQL database, search through which Sharepoint is implemented using Business Connectivity Services (BCS) and Enterprise Search. The output of the search results to the user Sharepoint is customized using the appropriate XSLT transformations. Thus, the user, when searching, gets the result approximately in this form (the base is working - hereinafter the names and all that are crossed out).
Using Sharepoint, users can assign alerts for each search request that trigger when changes are made to the database. When triggered, the user receives an email similar to the following.
That is, the standard alert does not take into account XSLT transformations and gives the user unreadable information. Therefore, the task arises of converting email text into readable one when forming, preferably the same as when searching.
First, I will briefly describe a standard solution that is not suitable for search alerts , but parts of which (paragraphs 2, 5-11) will then be needed to launch my version.
1. Create an AlertHandler dll with class Class1 that implements the IAlertNotifyHandler interface, in the OnNotification method of which we can get the current alert text and modify it accordingly.
2. Place the dll in the GAC
3. Make a copy of the alertTemplates.xml file which is located: C: \ Program Files \ Common Files \ Microsoft Shared \ Web Server Extensions \ 14 \ TEMPLATE \ XML. In this case, always modify the copy of the file and not the original (this is important, so that later you could return to the standard handler and not mess up).
4. Name the new file (copy alertTemplates.xml) CustomAlertTemplates and save it. Modify the file as follows. Find the properties block and add the following lines to this block:
Now the whole block will look something like this:
Note: PublicKeyToken can be viewed in the dll properties by finding it in C: \ windows \ assembly.
5. Run the command from C: \ Program Files \ Common Files \ Microsoft Shared \ web server extensions \ 14 \ BIN: stsadm -o updatealerttemplates -filename "C: \ Program Files \ Common Files \ Microsoft Shared \ Web Server Extensions \ 14 \ TEMPLATE \ XML \ customalerttemplates.xml "-url 6. Run the command: stsadm -o setproperty -pn job-immediate-alerts -pv" every 1 minutes "this is for testing only. Return back after testing.
7. Verify that SharePoint is configured to send outgoing emails.
8. Make sure the alert is turned on for example a document library if you are testing on a document library.
9. Run the command: iisreset
10. Run the command: services.msc
11. Restart Windows SharePoint Services Timer.
Everything is wonderful, everything works, but not for the case of search alerts. For search alerts in the OnNotification method (SPAlertHandlerParams ahp), the values of the ahp.eventData and ahp.body fields that actually are needed for customization do not come.
In response to my questions on the social.technet.microsoft.com forums , I received the following information.
First, you can custom the SharePoint alert using IAlertNotifyHandler as following:
blogs.msdn.com/b/sharepointdeveloperdocs/archive/2007/12/14/how-to-customizing-alert-emails-using-ialertnotificationhandler.aspx
Second, there is no search alert email template for you to modify.
All the Alert template: msdn.microsoft.com/en-us/library/bb802738.aspx
That is, it turns out that there is no solution?! .. But since I was not used to giving up without a fight, I continued to collect bit by bit information from the Internet. And this is what came of it.
1. Reading the forums showed that for processing search alerts, first of all, you need to add a new type of AlertTemplate - “OSS.Search”. Add to the CustomAlertTemplates file.
It’s important to note that in order to return back to the standard handler, the previously saved standard file is no longer enough, therefore we copy the standard alertTemplates.xml to a file, for example, in alertTemplates_forRestore.xml and add the following section
We carefully store the alertTemplates_forRestore.xml file - rollback to the standard handler is impossible without it.
2. Having puzzled my brain, I thought that, when using the standard solution, some part of the information still comes to us, then we can start from it. And I found maybe not the most elegant, but a working solution.
I won’t give all the code (it’s long) - only the main points.
Where ErgebnisKenntnis, LetzterKontakt, Kandidatenmail are the fields of the SQL database (shown as an example). To be able to search through them, it is important that these fields are registered in the Metadata property of the Sharepoint 2010 server.
Now we can create the HTML body of the myBody message using any fields in the SQL, Sharepoint database and at the end we do not forget to send it to the user.
Now, after completing paragraphs 2, 5-11 of the standard solution, users will receive search alerts, for example, in this form.
Everything, the problem is solved. I did not find other ways on the Internet, so I will be glad if my solution helps someone.
At first, the task seemed trivial. It seems like everything is simple. In the MSDN documentation there is an article describing the process itself, there are examples on other resources. But, as it turned out, customization of all types of alerts except search ones is described everywhere . Since I haven’t found a solution all over the Internet, I’m presenting my own (maybe not the most optimal, but working).
Condition
There is an SQL database, search through which Sharepoint is implemented using Business Connectivity Services (BCS) and Enterprise Search. The output of the search results to the user Sharepoint is customized using the appropriate XSLT transformations. Thus, the user, when searching, gets the result approximately in this form (the base is working - hereinafter the names and all that are crossed out).
Using Sharepoint, users can assign alerts for each search request that trigger when changes are made to the database. When triggered, the user receives an email similar to the following.
That is, the standard alert does not take into account XSLT transformations and gives the user unreadable information. Therefore, the task arises of converting email text into readable one when forming, preferably the same as when searching.
Standard alert customization solution
First, I will briefly describe a standard solution that is not suitable for search alerts , but parts of which (paragraphs 2, 5-11) will then be needed to launch my version.
1. Create an AlertHandler dll with class Class1 that implements the IAlertNotifyHandler interface, in the OnNotification method of which we can get the current alert text and modify it accordingly.
2. Place the dll in the GAC
3. Make a copy of the alertTemplates.xml file which is located: C: \ Program Files \ Common Files \ Microsoft Shared \ Web Server Extensions \ 14 \ TEMPLATE \ XML. In this case, always modify the copy of the file and not the original (this is important, so that later you could return to the standard handler and not mess up).
4. Name the new file (copy alertTemplates.xml) CustomAlertTemplates and save it. Modify the file as follows. Find the properties block and add the following lines to this block:
AlertHandler, Version=1.0.0.0, Culture=neutral, PublicKeyToken=d59ecf2a3bd66904
AlertHandler.Class1
* This source code was highlighted with Source Code Highlighter.
Now the whole block will look something like this:
ID;Author;Editor;Modified_x0020_By;Created_x0020_By;_UIVersionString;ContentType;TaskGroup;IsCurrent;Attachments;NumComments;
ID;Author;Editor;Modified_x0020_By;Created_x0020_By;_UIVersionString;ContentType;TaskGroup;IsCurrent;Attachments;NumComments;
AlertHandler, Version=1.0.0.0, Culture=neutral, PublicKeyToken=d59ecf2a3bd66904
AlertHandler.Class1
* This source code was highlighted with Source Code Highlighter.
Note: PublicKeyToken can be viewed in the dll properties by finding it in C: \ windows \ assembly.
5. Run the command from C: \ Program Files \ Common Files \ Microsoft Shared \ web server extensions \ 14 \ BIN: stsadm -o updatealerttemplates -filename "C: \ Program Files \ Common Files \ Microsoft Shared \ Web Server Extensions \ 14 \ TEMPLATE \ XML \ customalerttemplates.xml "-url 6. Run the command: stsadm -o setproperty -pn job-immediate-alerts -pv" every 1 minutes "this is for testing only. Return back after testing.
7. Verify that SharePoint is configured to send outgoing emails.
8. Make sure the alert is turned on for example a document library if you are testing on a document library.
9. Run the command: iisreset
10. Run the command: services.msc
11. Restart Windows SharePoint Services Timer.
Standard Solution Problem
Everything is wonderful, everything works, but not for the case of search alerts. For search alerts in the OnNotification method (SPAlertHandlerParams ahp), the values of the ahp.eventData and ahp.body fields that actually are needed for customization do not come.
In response to my questions on the social.technet.microsoft.com forums , I received the following information.
First, you can custom the SharePoint alert using IAlertNotifyHandler as following:
blogs.msdn.com/b/sharepointdeveloperdocs/archive/2007/12/14/how-to-customizing-alert-emails-using-ialertnotificationhandler.aspx
Second, there is no search alert email template for you to modify.
All the Alert template: msdn.microsoft.com/en-us/library/bb802738.aspx
That is, it turns out that there is no solution?! .. But since I was not used to giving up without a fight, I continued to collect bit by bit information from the Internet. And this is what came of it.
My version
1. Reading the forums showed that for processing search alerts, first of all, you need to add a new type of AlertTemplate - “OSS.Search”. Add to the CustomAlertTemplates file.
$Resources:Microsoft.Office.Server.Search,SearchResults_ATEventDiscovered;
$Resources:Microsoft.Office.Server.Search,SearchResults_ATEventModified;
$Resources:Microsoft.Office.Server.Search,SearchResults_ATEventAll;
mySearchAlert, Version=1.0.0.0, Culture=neutral, PublicKeyToken=aa1e89f3cc0ef56b
mySearchAlert.MySearchAlertHandler
mySearchAlert, Version=1.0.0.0, Culture=neutral, PublicKeyToken=aa1e89f3cc0ef56b
mySearchAlert.MySearchUpdateAlertHandler
* This source code was highlighted with Source Code Highlighter.
It’s important to note that in order to return back to the standard handler, the previously saved standard file is no longer enough, therefore we copy the standard alertTemplates.xml to a file, for example, in alertTemplates_forRestore.xml and add the following section
$Resources:Microsoft.Office.Server.Search,SearchResults_ATEventDiscovered;
$Resources:Microsoft.Office.Server.Search,SearchResults_ATEventModified;
$Resources:Microsoft.Office.Server.Search,SearchResults_ATEventAll;
Microsoft.Office.Server.Search, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c
Microsoft.Office.Server.Search.Query.SearchAlertHandler
Microsoft.Office.Server.Search, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c
Microsoft.Office.Server.Search.Query.SearchAlertHandler
* This source code was highlighted with Source Code Highlighter.
We carefully store the alertTemplates_forRestore.xml file - rollback to the standard handler is impossible without it.
2. Having puzzled my brain, I thought that, when using the standard solution, some part of the information still comes to us, then we can start from it. And I found maybe not the most elegant, but a working solution.
- Using information about the processed alert, I create a programmatically new temporary search alert SearchAlert alert2 = new SearchAlert (s1, q1);
- I create a search query to the Sharepoint server using the search alert alert search line - Search.Query.Query query = alert2.CreateSearchQuery
using Reflection I set the AlertInfo internal property of our query query by setting LastUpdateTime to the value we need (to get only changed records from the last alert ) and get the result of the request - I get additional information from the SQL database using FullTextSqlQuery
- delete the alert2 I created
- I generate my HTML body text based on the SQL database fields and Saherepoint 2010 fields received in query.
I won’t give all the code (it’s long) - only the main points.
public class MySearchAlertHandler : IAlertNotifyHandler
// обработчик оповещений
public bool OnNotification(SPAlertHandlerParams alertHandler)
{
...
return CustomAlertNotification(alertHandler);
...
}
public bool CustomAlertNotification(SPAlertHandlerParams alertHandlerParams)
{
string myBody = "";
SPSite site = null;
int searchAlertNotificationQuota = 200;
TimeSpan span;
SPAlert a = alertHandlerParams.a;
site = new SPSite(alertHandlerParams.siteUrl+ alertHandlerParams.webUrl;);
// alertTime и span нам понадобятся далее
DateTime alertTime = a.AlertTime;
if (a.AlertFrequency == SPAlertFrequency.Weekly)
{
span = TimeSpan.FromDays(7.0);
}
else
{
span = TimeSpan.FromDays(1.0);
}
if ((alertTime + span) <= DateTime.Now)
{
return true;
}
using (SPWeb web = site.OpenWeb())
{
// из полученного обработчиком оповещения (он перебирает все оповещения которые должны сработать) получаем строку поиска queryText
string queryText = Utils.GetValueFromXML(a.Properties["p_query"], "QueryText");
SPSite s1 = new SPSite (alertHandlerParams.siteUrl+alertHandlerParams.webUrl );
Query q1 = new KeywordQuery(s1);
q1.QueryText = queryText;
// программно создаем новое оповещение alert2, используя все свойства из обрабатываемого оповещения
SearchAlert alert2 = new SearchAlert(s1,q1);
alert2.ChangeType = AlertChangeType.DiscoveredOrModified;
alert2.InnerAlert.AlertFrequency = alertHandlerParams.a.AlertFrequency ;
alert2.InnerAlert.Title = "Temp#1";
alert2.InnerAlert.EventType = alertHandlerParams.a.EventType;
alert2.InnerAlert.User = s1.OpenWeb().CurrentUser ;
alert2.InnerAlert.AlertType = alertHandlerParams.a.AlertType;
alert2.Update();
// создаем новый запрос query к серверу основываясь на alert2 и queryText
using (Microsoft.Office.Server.Search.Query.Query query = alert2.CreateSearchQuery ())
{
ResultTableCollection tables;
query.QueryText = queryText;
query.RowLimit = searchAlertNotificationQuota;
query.TrimDuplicates = false;
// а вот тут главный фокус, с помощью которого мы получим только ту информацию, которая была изменена с момента последнего срабатывания оповещения, а не всю которая удовлетворяет запросу.
Для этого, с помощью Reflection задаем internal свойство AlertInfo нашего запроса query, установив LastUpdateTime в нужное нам значение
AlertInfo ai = new AlertInfo();
ai.ChangeType = alert2.ChangeType;
ai.LastUpdateTime = alertTime - span;
Type t1 = query.GetType();
if (t1.GetProperty("AlertInfo", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic |
System.Reflection.BindingFlags.Instance) == null)
throw new ArgumentOutOfRangeException("propName", string.Format("Property {0} was not found in Type {1}",
"AlertInfo", query.GetType().FullName));
t1.InvokeMember("AlertInfo", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.SetProperty |
System.Reflection.BindingFlags.Instance, null, query, new object[] { ai });
// Выполняем запрос query
ResultTable result = null;
try
{
tables = query.Execute();
}
catch
{
alert2.Delete();
return false;
}
result = tables[ResultType.RelevantResults];
// Получаем result - результат запроса и удаляем наш alert2 - он уже нам не нужен
alert2.Delete();
// Загружаем результат в DataTable и можем делать с ним что угодно. В таблицу попадают следующие столбцы - WorkId, Rank, Title, Author, Size, Path, Description, Write, SiteName, CollapsingStatus, HitHighlightedSummary, HitHighlightedProperties, ContentClass, IsDocument, PictureThumbnailURL
// по DiscoveredTime мы можем отобрать добавленные или измененные записи
System.Data.DataTable myTable = new System.Data.DataTable();
myTable.Load(result2, System.Data.LoadOption.OverwriteChanges);
foreach (System.Data.DataRow myrow in myTable.Rows)
{
if (Convert.ToDateTime(myrow["DiscoveredTime"]) > alertTime - span)
{
// это добавленные записи
}
else
{
// это измененные записи
}
}
// Кроме полученных столбцов, для кастомизации нам могут понадобится и столбцы из таблицы SQL, воспользуемся FullTextSqlQuery для их получения
FullTextSqlQuery fts = new FullTextSqlQuery(site2);
fts.QueryText = "SELECT WorkId, Rank, Title, Author, Size, Path, Description, Write, SiteName, CollapsingStatus, HitHighlightedSummary, HitHighlightedProperties, ContentClass, IsDocument, PictureThumbnailURL, PopularSocialTags, PictureWidth, PictureHeight, DatePictureTaken, ServerRedirectedURL, ErgebnisKenntnis, LetzterKontakt, Kandidatenmail FROM SCOPE() WHERE Path='" + myrow["Path"].ToString() + "'";
fts.ResultTypes = ResultType.RelevantResults;
fts.RowLimit = 300;
ResultTableCollection rtc = fts.Execute();
* This source code was highlighted with Source Code Highlighter.
Where ErgebnisKenntnis, LetzterKontakt, Kandidatenmail are the fields of the SQL database (shown as an example). To be able to search through them, it is important that these fields are registered in the Metadata property of the Sharepoint 2010 server.
Now we can create the HTML body of the myBody message using any fields in the SQL, Sharepoint database and at the end we do not forget to send it to the user.
SPUtility.SendEmail(web, false, false, string.Format("{0}", alertHandlerParams.headers["To"]),
string.Format("{0}", alertHandlerParams.headers["Subject"]), myBody);
* This source code was highlighted with Source Code Highlighter.
Now, after completing paragraphs 2, 5-11 of the standard solution, users will receive search alerts, for example, in this form.
Everything, the problem is solved. I did not find other ways on the Internet, so I will be glad if my solution helps someone.