msoelvsteen
msoelvsteen

Reputation: 31

Multiple impersonation-threads in Exchange Web Service (EWS)

I have a problem when running multiple impersonations of users in EWS, when I want to recieve notifications on each of the impersonated persons calendars (possible 100 persons).

Currently I have an outlook account who have rights to impersonate all other users, and all the ExchangeService-objects get this accounts credentials

Short version is, that when I try to bind to an appointment via the unique ID it works as long as I only have one thread running. When I start a new thread containing a new Exchangeservice with its own subscription I dont recieve any response on the Appointment.Bind()-request.

When I run two instances of my program with only 1 thread in each it works fine, but as soon as I start a new thread with a new ExchangeService the Appointment.Bind() doesnt give any response.

The weird part about this is, that it worked fine two weeks ago, but suddenly it stopped working and I didnt change my code.

I have created a quick demo of my problem:

class Program
{
    static void Main(string[] args)
    {
        var x = new OutlookListener("[email protected]");
        var y = new OutlookListener("[email protected]");
        new Thread(x.Start).Start();
        new Thread(y.Start).Start();
        while (true)
        {

        }
    }
}
class OutlookListener
{
    private ExchangeService _ExchangeService;
    private AutoResetEvent _Signal;
    public OutlookListener(string emailToImp)
    {
        _ExchangeService = new ExchangeService(ExchangeVersion.Exchange2010_SP1)
        {
            Credentials = new NetworkCredential("[email protected]", "password"),
            Url = new Uri("exchangeUrl"),
            ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress, emailToImp)
        };
    }
    public void Start()
    {
        var subscription = _ExchangeService.SubscribeToStreamingNotifications(new FolderId[] { WellKnownFolderName.Calendar },
                                                                  EventType.Created);
        var connection = CreateStreamingSubscription(_ExchangeService, subscription);
        Console.Out.WriteLine("Subscription created.");
        _Signal = new AutoResetEvent(false);
        _Signal.WaitOne();
        subscription.Unsubscribe();
        connection.Close();
    }

    private StreamingSubscriptionConnection CreateStreamingSubscription(ExchangeService service, StreamingSubscription subscription)                                                                         
    {
        var connection = new StreamingSubscriptionConnection(service, 30);
        connection.AddSubscription(subscription);
        connection.OnNotificationEvent += OnNotificationEvent;
        connection.OnSubscriptionError += OnSubscriptionError;
        connection.OnDisconnect += OnDisconnect;
        connection.Open();

        return connection;
    }
    private void OnNotificationEvent(object sender, NotificationEventArgs args)
    {
        // Extract the item ids for all NewMail Events in the list.
        var newMails = from e in args.Events.OfType<ItemEvent>()
                       where e.EventType == EventType.Created
                       select e.ItemId;

        foreach (var newMail in newMails)
        {
            var appointment= Appointment.Bind(_ExchangeService, newMail); //This is where I dont get a response!
            Console.WriteLine(appointment.Subject);
        }
    }
    private void OnSubscriptionError(object sender, SubscriptionErrorEventArgs args)
    {
    }
    private void OnDisconnect(object sender, SubscriptionErrorEventArgs args)
    {
    }
}

Any suggestions?

Upvotes: 3

Views: 3074

Answers (2)

Rune Andersen
Rune Andersen

Reputation: 421

I have had the same issue and found that my EWS solution was limited by two factors. The System.Net.ServicePointManager.DefaultConnectionLimit is by default set to 2, which I've changed to 20 which i beleive to match the throttling policy of Exchange Online.

Second the ConnectionGroupName property on the ExchangeService object can be used to pool connections into different relevant groups which have a limit of concurrent connections cohernet with the DefaultConnectionLimit property.

A way to override the settings is to set the ConnectionGroupName property to a uniquevalue for each ExchangeService object you create.

ExchangeService exchangeService = new ExchangeService()
{
    ConnectionGroupName = Guid.NewGuid().ToString()
};

Upvotes: 2

Ahmad ElMadi
Ahmad ElMadi

Reputation: 2617

Why do you need multiple threads ?

In my case , I have created a dictionary of Services based on the smtpaddress for each email I want to impersonate, and I subscribe to them all. All can happen in one thread, and all notification from any user will be handled in the OnNotificationEvent . [THIS CODE IS JUST TO SHOW THE LOGIC AND IS NOT COMPLETE FOR FULL COMPILATION AND RUN]

            var service = new ExchangeService(exchangeVersion);

            var serviceCred = ((System.Net.NetworkCredential)(((WebCredentials)(Services.First().Value.Credentials)).Credentials));

            service.Credentials = new WebCredentials(serviceCred.UserName, serviceCred.Password);

            service.AutodiscoverUrl(userSmtp, RedirectionUrlValidationCallback);

            service.ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress, userSmtp);

            Services.Add(userSmtp, service);

Note that Services.First().Value is the service that can impersonate all the other users, and here it is cloned as the number of the user.

After that Subscriptions for the all services (note that now each service is impersonating different user)

foreach (var service in Services.Values)
            {
                SubscribeToService(service);
            }

and the definition for SubscribeToService is as follow

private void SubscribeToService(ExchangeService service)
        {

                if (service.ImpersonatedUserId == null)
                    return;
                if (service.Url == null)
                    return;

                var serviceName = service.ImpersonatedUserId.Id;


                var streamingSubscription =
                                      service.SubscribeToStreamingNotifications(new FolderId[] { WellKnownFolderName.DeletedItems, WellKnownFolderName.Calendar },
                                                    EventType.FreeBusyChanged, EventType.Moved, EventType.Created, EventType.Modified);

                if (!Connections.ContainsKey(service.Url))
                {
                    Connections.Add(service.Url, new StreamingSubscriptionConnection(service, 30));

                }
                var connection = Connections[service.Url];

                CloseConnection(connection);

                if (!_subscriptions.ContainsKey(serviceName))
                {
                    _subscriptions.Add(serviceName, streamingSubscription);
                    connection.AddSubscription(streamingSubscription);
                }

            }

        }

All of this can happen in one single thread, and I hope my answer will help you Cheers

Upvotes: 1

Related Questions