As I formerly wrote, if you enable the incoming mails on a SharePoint calendar, and send a meeting request to the list, the participants’ mail addresses won’t be resolved to meeting attendees.
To workaround this limitation, my first idea was a SPEmailHandler that extracts this info from the mail and stores it into the adequate SharePoint field. However I found, that after I registered my event receiver on the list, all of the default functionality of the incoming mail on calendars (like resolving time and location of the meetings, updating former items in the list on event updates, etc.) were lost, even if I allow in my override of EmailReceived method other registered receivers to be called, like:
base.EmailReceived(list, emailMessage, receiverData);
The reason for this phenomena I have found reflecting the related classes and in this post. That means, specific list types, like calendars, announcements, discussions, etc. have their standard ProcessMessage methods implemented in subclasses of the internal abstract SPEmailHandler class. For example, the incoming mails for a calendar is handled by the SPCalendarEmailHandler class. If you register a custom event receiver (that means the HasExternalEmailHandler property of the list will be true), the standard method will be totally ignored, and the custom event receiver will be called through a SPExternalEMailHandler instance instead. Really bad news!
How could we inject our requirement of resolving attendees into this processing chain without disturbing the standard steps?
Spending a few minutes with Reflector I found, that the SetStandardHeaderFields method of the SPEmailHandler class (called from the ProcessVEvent method of the SPCalendarEmailHandler class) sets (among others) the EmailTo field, and the SetEmailHeadersField method of the same class (called from the SetStandardHeaderFields method) sets the EmailHeaders field.
Both of these fields seem to be undocumented hidden fields. The EmailHeaders field contains the whole (unescaped!) SMTP header block of the original mail that triggered the item creation, while the EmailTo field contains the addresses of the mail in these escaped format:
John Smith <john.smith@contoso.com>; Peter Black <peter.black@contoso.com>
The escaping seems to be important. When I tried setting the values without it, the e-mail addresses were removed from the field, leaving only the display names there.
To resolve users, I used a slightly modified version of the method demonstrated in my former post:
- private SPFieldUserValueCollection GetUsersByMailTo(SPWeb web, string emailTo)
- {
- SPFieldUserValueCollection result = new SPFieldUserValueCollection();
- if (!string.IsNullOrEmpty(emailTo))
- {
- emailTo = emailTo.Replace("<", "<").Replace(">", ">");
- string[] addressees = emailTo.Split(';');
- Array.ForEach(addressees,
- addressee =>
- {
- MailAddress ma = new MailAddress(addressee);
- SPPrincipalInfo pi = SPUtility.ResolveWindowsPrincipal(web.Site.WebApplication, ma.Address, SPPrincipalType.User, true);
- if ((pi != null) && (!string.IsNullOrEmpty(pi.LoginName)))
- {
- SPUser user = web.EnsureUser(pi.LoginName);
- result.Add(new SPFieldUserValue(web, user.ID, null));
- Console.WriteLine("User: {0}", user.LoginName);
- }
- });
- }
- return result;
- }
My next step was to create an ItemUpdated event handler that should resolve the users based on their e-mail addresses. An issue I had to handle there is, that the item can be updated not only due to the incoming mail, but also due to the changes the users make through the UI. It would be rather frustrating for our users, if we set the original value back, after the they altered it through the UI. So I triggered the user resolving process only if the value of the EmailTo was not empty, and clear the value of the field after processing to prohibit further calls of the method (for example, triggered from the UI).
Note: To update the attendees of the event, we should set the value of the ParticipantsPicker field, and not the Participants field.
- public override void ItemUpdated(SPItemEventProperties properties)
- {
- try
- {
- Trace.TraceInformation("ItemUpdated started");
- SPListItem listItem = properties.ListItem;
- string emailTo = listItem[SPBuiltInFieldId.EmailTo] as string;
- Trace.TraceInformation("emailTo: {0}", emailTo);
- SPWeb web = properties.Web;
- if (!string.IsNullOrEmpty(emailTo))
- {
- Trace.TraceInformation("Updating attendees");
- listItem[SPBuiltInFieldId.ParticipantsPicker] = GetUsersByMailTo(web, emailTo);
- // hack to prohibit triggering on updates from UI
- listItem[Microsoft.SharePoint.SPBuiltInFieldId.EmailTo] = null;
- listItem.Update();
- }
- Trace.TraceInformation("ItemUpdated finished");
- }
- catch (Exception ex)
- {
- Trace.TraceInformation("ItemUpdated exception: {0}", ex.Message);
- Trace.TraceInformation(ex.StackTrace);
- }
- base.ItemUpdated(properties);
- }
After deploying my event receiver I’ve checked it through sending meeting requests to attendees including the address of the calendar list, and first the solution seemed to be perfect. However, after a while I found, that when I send an update for an event, the list of the attendees was not updated.
The reason behind this issue is – as it turned out after another round of reflectioning – that the EmailTo field is set only once, when the item is created, but not populated when updates are received. See the output parameter isUpdate of the FindItemToUpdate method of the SPCalendarEmailHandler class for details. If the actual processing is an update, the SetStandardHeaderFields method, and through this method the SetEmailHeadersField method won’t be invoked in the ProcessVEvent method.
In the next post I try to publish the alternative solution for the original request – resolving meeting attendees based on the mail addresses in the incoming mail.
