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

原文地址:https://www.cnblogs.com/DarkRoger/p/14700712.html