protected virtual void Message(
            IEnumerable <IServerAddress> servers,
            ICacheRefresher refresher,
            MessageType messageType,
            IEnumerable <object> ids = null,
            Type idArrayType         = null,
            string jsonPayload       = null)
        {
            LogHelper.Debug <WebServiceServerMessenger>(
                "Performing distributed call for {0}/{1} on servers ({2}), ids: {3}, json: {4}",
                refresher.GetType,
                () => messageType,
                () => string.Join(";", servers.Select(x => x.ToString())),
                () => ids == null ? "" : string.Join(";", ids.Select(x => x.ToString())),
                () => jsonPayload ?? "");

            try
            {
                // NOTE: we are messaging ALL servers including the local server
                // at the moment, the web service,
                //  for bulk (batched) checks the origin and does NOT process the instructions again
                //  for anything else, processes the instructions again (but we don't use this anymore, batched is the default)
                // TODO: see WebServerHelper, could remove local server from the list of servers

                // the default server messenger uses http requests
                using (var client = new ServerSyncWebServiceClient())
                {
                    var asyncResults = new List <IAsyncResult>();

                    LogStartDispatch();

                    // go through each configured node submitting a request asynchronously
                    // NOTE: 'asynchronously' in this case does not mean that it will continue while we give the page back to the user!
                    foreach (var n in servers)
                    {
                        // set the server address
                        client.Url = n.ServerAddress;

                        // add the returned WaitHandle to the list for later checking
                        switch (messageType)
                        {
                        case MessageType.RefreshByJson:
                            asyncResults.Add(client.BeginRefreshByJson(refresher.UniqueIdentifier, jsonPayload, Login, Password, null, null));
                            break;

                        case MessageType.RefreshAll:
                            asyncResults.Add(client.BeginRefreshAll(refresher.UniqueIdentifier, Login, Password, null, null));
                            break;

                        case MessageType.RefreshById:
                            if (idArrayType == null)
                            {
                                throw new InvalidOperationException("Cannot refresh by id if the idArrayType is null.");
                            }

                            if (idArrayType == typeof(int))
                            {
                                // bulk of ints is supported
                                var json   = JsonConvert.SerializeObject(ids.Cast <int>().ToArray());
                                var result = client.BeginRefreshByIds(refresher.UniqueIdentifier, json, Login, Password, null, null);
                                asyncResults.Add(result);
                            }
                            else     // must be guids
                            {
                                // bulk of guids is not supported, iterate
                                asyncResults.AddRange(ids.Select(i =>
                                                                 client.BeginRefreshByGuid(refresher.UniqueIdentifier, (Guid)i, Login, Password, null, null)));
                            }

                            break;

                        case MessageType.RemoveById:
                            if (idArrayType == null)
                            {
                                throw new InvalidOperationException("Cannot remove by id if the idArrayType is null.");
                            }

                            // must be ints
                            asyncResults.AddRange(ids.Select(i =>
                                                             client.BeginRemoveById(refresher.UniqueIdentifier, (int)i, Login, Password, null, null)));
                            break;
                        }
                    }

                    // wait for all requests to complete
                    var waitHandles = asyncResults.Select(x => x.AsyncWaitHandle);
                    WaitHandle.WaitAll(waitHandles.ToArray());

                    // handle results
                    var errorCount = 0;
                    foreach (var asyncResult in asyncResults)
                    {
                        try
                        {
                            switch (messageType)
                            {
                            case MessageType.RefreshByJson:
                                client.EndRefreshByJson(asyncResult);
                                break;

                            case MessageType.RefreshAll:
                                client.EndRefreshAll(asyncResult);
                                break;

                            case MessageType.RefreshById:
                                if (idArrayType == typeof(int))
                                {
                                    client.EndRefreshById(asyncResult);
                                }
                                else
                                {
                                    client.EndRefreshByGuid(asyncResult);
                                }
                                break;

                            case MessageType.RemoveById:
                                client.EndRemoveById(asyncResult);
                                break;
                            }
                        }
                        catch (WebException ex)
                        {
                            LogDispatchNodeError(ex);
                            errorCount++;
                        }
                        catch (Exception ex)
                        {
                            LogDispatchNodeError(ex);
                            errorCount++;
                        }
                    }

                    LogDispatchBatchResult(errorCount);
                }
            }
            catch (Exception ee)
            {
                LogDispatchBatchError(ee);
            }
        }
        protected virtual void PerformDistributedCall(
            IEnumerable <IServerAddress> servers,
            ICacheRefresher refresher,
            MessageType dispatchType,
            IEnumerable <object> ids = null,
            Type idArrayType         = null,
            string jsonPayload       = null)
        {
            //We are using distributed calls, so lets make them...
            try
            {
                //TODO: We should try to figure out the current server's address and if it matches any of the ones
                // in the ServerAddress list, then just refresh directly on this server and exclude that server address
                // from the list, this will save an internal request.

                using (var cacheRefresher = new ServerSyncWebServiceClient())
                {
                    var asyncResultsList = new List <IAsyncResult>();

                    LogStartDispatch();

                    // Go through each configured node submitting a request asynchronously
                    //NOTE: 'asynchronously' in this case does not mean that it will continue while we give the page back to the user!
                    foreach (var n in servers)
                    {
                        //set the server address
                        cacheRefresher.Url = n.ServerAddress;

                        // Add the returned WaitHandle to the list for later checking
                        switch (dispatchType)
                        {
                        case MessageType.RefreshByJson:
                            asyncResultsList.Add(
                                cacheRefresher.BeginRefreshByJson(
                                    refresher.UniqueIdentifier, jsonPayload, Login, Password, null, null));
                            break;

                        case MessageType.RefreshAll:
                            asyncResultsList.Add(
                                cacheRefresher.BeginRefreshAll(
                                    refresher.UniqueIdentifier, Login, Password, null, null));
                            break;

                        case MessageType.RefreshById:
                            if (idArrayType == null)
                            {
                                throw new InvalidOperationException("Cannot refresh by id if the idArrayType is null");
                            }

                            if (idArrayType == typeof(int))
                            {
                                var serializer = new JavaScriptSerializer();
                                var jsonIds    = serializer.Serialize(ids.Cast <int>().ToArray());
                                //we support bulk loading of Integers
                                var result = cacheRefresher.BeginRefreshByIds(refresher.UniqueIdentifier, jsonIds, Login, Password, null, null);
                                asyncResultsList.Add(result);
                            }
                            else
                            {
                                //we don't currently support bulk loading of GUIDs (not even sure if we have any Guid ICacheRefreshers)
                                //so we'll just iterate
                                asyncResultsList.AddRange(
                                    ids.Select(i => cacheRefresher.BeginRefreshByGuid(
                                                   refresher.UniqueIdentifier, (Guid)i, Login, Password, null, null)));
                            }

                            break;

                        case MessageType.RemoveById:
                            //we don't currently support bulk removing so we'll iterate
                            asyncResultsList.AddRange(
                                ids.Select(i => cacheRefresher.BeginRemoveById(
                                               refresher.UniqueIdentifier, (int)i, Login, Password, null, null)));
                            break;
                        }
                    }

                    List <WaitHandle> waitHandlesList;
                    var asyncResults = GetAsyncResults(asyncResultsList, out waitHandlesList);

                    var errorCount = 0;

                    // Once for each WaitHandle that we have, wait for a response and log it
                    // We're previously submitted all these requests effectively in parallel and will now retrieve responses on a FIFO basis
                    foreach (var t in asyncResults)
                    {
                        var handleIndex = WaitHandle.WaitAny(waitHandlesList.ToArray(), TimeSpan.FromSeconds(15));

                        try
                        {
                            // Find out if the call succeeded
                            switch (dispatchType)
                            {
                            case MessageType.RefreshByJson:
                                cacheRefresher.EndRefreshByJson(t);
                                break;

                            case MessageType.RefreshAll:
                                cacheRefresher.EndRefreshAll(t);
                                break;

                            case MessageType.RefreshById:
                                if (idArrayType == null)
                                {
                                    throw new InvalidOperationException("Cannot refresh by id if the idArrayType is null");
                                }

                                if (idArrayType == typeof(int))
                                {
                                    cacheRefresher.EndRefreshById(t);
                                }
                                else
                                {
                                    cacheRefresher.EndRefreshByGuid(t);
                                }
                                break;

                            case MessageType.RemoveById:
                                cacheRefresher.EndRemoveById(t);
                                break;
                            }
                        }
                        catch (WebException ex)
                        {
                            LogDispatchNodeError(ex);

                            errorCount++;
                        }
                        catch (Exception ex)
                        {
                            LogDispatchNodeError(ex);

                            errorCount++;
                        }
                    }

                    LogDispatchBatchResult(errorCount);
                }
            }
            catch (Exception ee)
            {
                LogDispatchBatchError(ee);
            }
        }