Exchange学习:EWS 通过流通知和拉取通知订阅Exchange新邮件提醒
原理
EWS 通知以订阅的形式进行。 通常,每个邮箱一个订阅,在邮箱订阅内你还可以订阅一些或所有文件夹。 你可以决定订阅什么种类的通知(流式、拉取、推送)以及接收什么种类的事件(NewMail、Created、Deleted、Modified等),然后可以创建订阅。 然后 EWS 事件会非同步地从邮箱服务器发送到客户端。
EWS流式通知和拉取通知区别
流式通知依赖于一个在服务器挂起的 get 请求来保持流式订阅连接打开,这样任何在连接活动时发生的事件都会马上流给客户端。 多个通知可以在单个连接周期内发送,在至多 30 分钟的间隔期过期前,连接都会保持打开。 连接过期后,客户端再次发送 get 请求。 下面显示了流式订阅和流式通知工作的原理。
拉取通知依靠客户端以一定间隔请求客户端管理的通知。 这可能会在获得 GetEvents 响应时未收到通知。 下面显示了拉取订阅和拉取通知工作的原理。
拉取通知代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Text; using System.Threading.Tasks; using System.Timers; using Microsoft.Exchange.WebServices.Data; namespace SubscribeToPullNotifications { class Program { static ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2010_SP2); static PullSubscription Subscription; static void Main(string[] args) { System.Net.ServicePointManager.ServerCertificateValidationCallback = CertificateValidationCallBack; SubscribePullNotifications(WellKnownFolderName.Inbox); Timer t = new Timer(); t.Elapsed += t_Elapsed; t.Interval = 5000; t.Start(); Console.Read(); } static void t_Elapsed(object sender, ElapsedEventArgs e) { Console.WriteLine("程序运行中"); GetPullNotifications(); } public static void SubscribePullNotifications(FolderId folderId) { service.Url = new Uri("https://xxx.xxx.xxx.xxx/EWS/Exchange.asmx"); service.Credentials = new NetworkCredential("xxx@xxx.xxx", "xxx"); Subscription = service.SubscribeToPullNotifications(new FolderId[] { folderId }, 1440, null, EventType.NewMail, EventType.Created, EventType.Deleted, EventType.Moved); } public static void GetPullNotifications() { IEnumerable<ItemEvent> itemEvents = Subscription.GetEvents().ItemEvents; IEnumerable<FolderEvent> folderEvents = Subscription.GetEvents().FolderEvents; foreach (ItemEvent itemEvent in itemEvents) { switch (itemEvent.EventType) { case EventType.Copied: Console.WriteLine("ItemEvent Copied"); break; case EventType.Created: Console.WriteLine("ItemEvent Created"); break; case EventType.Deleted: Console.WriteLine("ItemEvent Deleted"); break; case EventType.FreeBusyChanged: Console.WriteLine("ItemEvent FreeBusyChanged"); break; case EventType.Modified: Console.WriteLine("ItemEvent Modified"); break; case EventType.Moved: Console.WriteLine("ItemEvent Moved"); break; case EventType.NewMail: Console.WriteLine("ItemEvent New mail"); Console.WriteLine(itemEvent.ItemId.UniqueId); EmailMessage emailMessage = EmailMessage.Bind(service, itemEvent.ItemId.UniqueId); Console.WriteLine(emailMessage.Subject); Console.WriteLine(emailMessage.Body.Text); Console.WriteLine(emailMessage.From); Console.WriteLine(emailMessage.DateTimeReceived); Console.WriteLine(string.Join(";", emailMessage.ToRecipients.Select(x => x.Address).ToArray())); Console.WriteLine(string.Join(",", emailMessage.CcRecipients.Select(u => u.Address).ToArray())); break; case EventType.Status: Console.WriteLine("ItemEvent Status"); break; default: break; } foreach (var folderEvent in folderEvents) { switch (folderEvent.EventType) { case EventType.Copied: Console.WriteLine("FolderEvent Copied"); break; case EventType.Created: Console.WriteLine("FolderEvent Created"); break; case EventType.Deleted: Console.WriteLine("FolderEvent Deleted"); break; case EventType.FreeBusyChanged: Console.WriteLine("FolderEvent FreeBusyChanged"); break; case EventType.Modified: Console.WriteLine("FolderEvent Modified"); break; case EventType.Moved: Console.WriteLine("FolderEvent Moved"); break; case EventType.NewMail: Console.WriteLine("FolderEvent NewMail"); break; default: break; } } } } } }
流通知:
public void SyncGet() { _service = InitService(_service); // Subscribe to pull notifications in the Inbox. PullSubscription subscription = _service.SubscribeToPullNotifications( new FolderId[] { WellKnownFolderName.Inbox }, 30, null, EventType.NewMail, EventType.Created, EventType.Deleted, EventType.Modified, EventType.Moved, EventType.Copied, EventType.FreeBusyChanged); // Call GetEvents to retrieve events from the server. GetEventsResults events = subscription.GetEvents(); } public void SyncEmail() { _service = InitService(_service); // Subscribe to streaming notifications in the Inbox. var StreamingSubscription = _service.SubscribeToStreamingNotifications( new FolderId[] { WellKnownFolderName.Inbox }, EventType.NewMail, EventType.Created, EventType.Deleted, EventType.Modified, EventType.Moved, EventType.Copied, EventType.FreeBusyChanged); // Create a streaming connection to the service object, over which events are returned to the client. // Keep the streaming connection open for 30 minutes. StreamingSubscriptionConnection connection = new StreamingSubscriptionConnection(_service, 30); connection.AddSubscription(StreamingSubscription); connection.OnNotificationEvent += OnNotificationEvent; connection.OnDisconnect += OnDisconnect; connection.Open(); } private void OnDisconnect(object sender, SubscriptionErrorEventArgs args) { SyncEmail(); } private void OnNotificationEvent(object sender, NotificationEventArgs args) { if (args != null) { foreach (NotificationEvent notificationEvent in args.Events) { if (notificationEvent is ItemEvent) { ItemEvent itemEvent = (ItemEvent)notificationEvent; Console.WriteLine(notificationEvent.EventType.ToString()); Item item = Item.Bind(_service, itemEvent.ItemId); switch (notificationEvent.EventType) { case EventType.Moved: Console.WriteLine(item.Subject); break; case EventType.NewMail: Console.WriteLine(item.Subject); break; case EventType.Created: Console.WriteLine($"创建:{item.Subject}");break; case EventType.Deleted: Console.WriteLine($"删除:{item.Subject}"); break; default: break; } } } } }
另外事件分为ItemEvents和FolderEvents,具体区别可以看下图:
现实中,多个通知(甚至是多个同类型的通知)可以由单一用户操作创建。 比如,在文件夹移动操作的情况中,三个文件夹事件被创建:一个关于文件夹修改,一个关于旧文件夹,还有一个关于新文件夹。 因为单一操作可以出现多个事件,所以要创建一个等待事件,这样同步只有在操作完成时才会进行,而不是在操作中多次进行。
如果遇到证书问题则需要在程序中加入:
System.Net.ServicePointManager.ServerCertificateValidationCallback = CertificateValidationCallBack; private static bool CertificateValidationCallBack( object sender, System.Security.Cryptography.X509Certificates.X509Certificate certificate, System.Security.Cryptography.X509Certificates.X509Chain chain, System.Net.Security.SslPolicyErrors sslPolicyErrors) { // If the certificate is a valid, signed certificate, return true. if (sslPolicyErrors == System.Net.Security.SslPolicyErrors.None) { return true; } // If there are errors in the certificate chain, look at each error to determine the cause. if ((sslPolicyErrors & System.Net.Security.SslPolicyErrors.RemoteCertificateChainErrors) != 0) { if (chain != null && chain.ChainStatus != null) { foreach (System.Security.Cryptography.X509Certificates.X509ChainStatus status in chain.ChainStatus) { if ((certificate.Subject == certificate.Issuer) && (status.Status == System.Security.Cryptography.X509Certificates.X509ChainStatusFlags.UntrustedRoot)) { // Self-signed certificates with an untrusted root are valid. continue; } else { if (status.Status != System.Security.Cryptography.X509Certificates.X509ChainStatusFlags.NoError) { // If there are any other errors in the certificate chain, the certificate is invalid, // so the method returns false. return false; } } } } // When processing reaches this line, the only errors in the certificate chain are // untrusted root errors for self-signed certificates. These certificates are valid // for default Exchange server installations, so return true. return true; } else { // In all other cases, return false. return false; } }
注意这个不要在正式环境使用。
参考自:https://docs.microsoft.com/zh-cn/exchange/client-developer/exchange-web-services/notification-subscriptions-mailbox-events-and-ews-in-exchange