/// <summary>
        /// This is the main entry point for your service replica.
        /// This method executes when this replica of your service becomes primary and has write status.
        /// </summary>
        /// <param name="cancellationToken">Canceled when Service Fabric needs to shut down this service replica.</param>
        protected override async Task RunAsync(CancellationToken cancellationToken)
        {
            // Get the IoT Hub connection string from the Settings.xml config file
            // from a configuration package named "Config"
            string iotHubConnectionString =
                this.Context.CodePackageActivationContext
                .GetConfigurationPackageObject("Config")
                .Settings
                .Sections["IoTHubConfigInformation"]
                .Parameters["ConnectionString"]
                .Value;
            string iotHubProcessOnlyFutureEvents =
                this.Context.CodePackageActivationContext
                .GetConfigurationPackageObject("Config")
                .Settings
                .Sections["IoTHubConfigInformation"]
                .Parameters["ProcessOnlyFutureEvents"]
                .Value.ToLower();

            ServiceEventSource.Current.ServiceMessage(this.Context, $"RouterService - {ServiceUniqueId} - RunAsync - Starting service  - Process Only Future Events[{iotHubProcessOnlyFutureEvents}] - IoTHub Connection String[{iotHubConnectionString}]");

            // These Reliable Dictionaries are used to keep track of our position in IoT Hub.
            // If this service fails over, this will allow it to pick up where it left off in the event stream.
            IReliableDictionary <string, string> offsetDictionary =
                await this.StateManager.GetOrAddAsync <IReliableDictionary <string, string> >(OffsetDictionaryName);

            IReliableDictionary <string, long> epochDictionary =
                await this.StateManager.GetOrAddAsync <IReliableDictionary <string, long> >(EpochDictionaryName);

            // Each partition of this service corresponds to a partition in IoT Hub.
            // IoT Hub partitions are numbered 0..n-1, up to n = 32.
            // This service needs to use an identical partitioning scheme.
            // The low key of every partition corresponds to an IoT Hub partition.
            Int64RangePartitionInformation partitionInfo = (Int64RangePartitionInformation)this.Partition.PartitionInfo;
            long servicePartitionKey = partitionInfo.LowKey;

            EventHubReceiver eventHubReceiver = null;
            MessagingFactory messagingFactory = null;

            try
            {
                // HttpClient is designed as a shared object.
                // A single instance should be used throughout the lifetime of RunAsync.
                using (HttpClient httpClient = new HttpClient(new HttpServiceClientHandler()))
                {
                    int  offsetIteration = 0;
                    bool IsConnected     = false;

                    while (true)
                    {
                        cancellationToken.ThrowIfCancellationRequested();

                        if (!IsConnected)
                        {
                            // Get an EventHubReceiver and the MessagingFactory used to create it.
                            // The EventHubReceiver is used to get events from IoT Hub.
                            // The MessagingFactory is just saved for later so it can be closed before RunAsync exits.
                            Tuple <EventHubReceiver, MessagingFactory> iotHubInfo = await this.ConnectToIoTHubAsync(iotHubConnectionString, servicePartitionKey, epochDictionary, offsetDictionary, iotHubProcessOnlyFutureEvents);

                            eventHubReceiver = iotHubInfo.Item1;
                            messagingFactory = iotHubInfo.Item2;

                            IsConnected = true;
                        }

                        Uri postUrl = null;

                        try
                        {
                            // It's important to set a low wait time here in lieu of a cancellation token
                            // so that this doesn't block RunAsync from exiting when Service Fabric needs it to complete.
                            // ReceiveAsync is a long-poll operation, so the timeout should not be too low,
                            // yet not too high to block RunAsync from exiting within a few seconds.
                            using (EventData eventData = await eventHubReceiver.ReceiveAsync(TimeSpan.FromSeconds(5)))
                            {
                                if (eventData == null)
                                {
                                    ServiceEventSource.Current.ServiceMessage(this.Context, $"RouterService - {ServiceUniqueId} - RunAsync - No event data available on hub '{eventHubReceiver.Name}'");
                                    await Task.Delay(global::Iot.Common.Names.IoTHubRetryWaitIntervalsInMills);

                                    continue;
                                }
                                else
                                {
                                    ServiceEventSource.Current.ServiceMessage(this.Context, $"RouterService - {ServiceUniqueId} - RunAsync - Received event data from hub '{eventHubReceiver.Name}' - Enqueued Time[{eventData.EnqueuedTimeUtc}] - Partition '{eventData.PartitionKey}' Sequence # '{eventData.SequenceNumber}'");
                                }

                                string targetSite = (string)eventData.Properties[global::Iot.Common.Names.EventKeyFieldTargetSite];
                                string deviceId   = (string)eventData.Properties[global::Iot.Common.Names.EventKeyFieldDeviceId];

                                // This is the named service instance of the target site data service that the event should be sent to.
                                // The targetSite id is part of the named service instance name.
                                // The incoming device data stream specifie which target site the data belongs to.
                                string prefix                        = global::Iot.Common.Names.InsightApplicationNamePrefix;
                                string serviceName                   = global::Iot.Common.Names.InsightDataServiceName;
                                Uri    targetSiteServiceName         = new Uri($"{prefix}/{targetSite}/{serviceName}");
                                long   targetSiteServicePartitionKey = FnvHash.Hash(deviceId);

                                ServiceEventSource.Current.ServiceMessage(this.Context, $"RouterService - {ServiceUniqueId} - RunAsync - About to post data to Insight Data Service from device '{deviceId}' to target site '{targetSite}' - partitionKey '{targetSiteServicePartitionKey}' - Target Service Name '{targetSiteServiceName}'");

                                // The target site data service exposes an HTTP API.
                                // For incoming device events, the URL is /api/events/{deviceId}
                                // This sets up a URL and sends a POST request with the device JSON payload.
                                postUrl = new HttpServiceUriBuilder()
                                          .SetServiceName(targetSiteServiceName)
                                          .SetPartitionKey(targetSiteServicePartitionKey)
                                          .SetServicePathAndQuery($"/api/events/{deviceId}")
                                          .Build();

                                ServiceEventSource.Current.ServiceMessage(this.Context, $"RouterService - {ServiceUniqueId} - RunAsync - Ready to post data to Insight Data Service from device '{deviceId}' to taget site '{targetSite}' - partitionKey '{targetSiteServicePartitionKey}' - Target Service Name '{targetSiteServiceName}' - url '{postUrl.PathAndQuery}'");

                                // The device stream payload isn't deserialized and buffered in memory here.
                                // Instead, we just can just hook the incoming stream from Iot Hub right into the HTTP request stream.
                                using (Stream eventStream = eventData.GetBodyStream())
                                {
                                    using (StreamContent postContent = new StreamContent(eventStream))
                                    {
                                        postContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");

                                        HttpResponseMessage response = await httpClient.PostAsync(postUrl, postContent, cancellationToken);

                                        if (response.StatusCode == System.Net.HttpStatusCode.BadRequest)
                                        {
                                            // This service expects the receiving target site service to return HTTP 400 if the device message was malformed.
                                            // In this example, the message is simply logged.
                                            // Your application should handle all possible error status codes from the receiving service
                                            // and treat the message as a "poison" message.
                                            // Message processing should be allowed to continue after a poison message is detected.

                                            string responseContent = await response.Content.ReadAsStringAsync();

                                            ServiceEventSource.Current.ServiceMessage(
                                                this.Context,
                                                $"RouterService - {ServiceUniqueId} - RunAsync - Insight service '{targetSiteServiceName}' returned HTTP 400 due to a bad device message from device '{deviceId}'. Error message: '{responseContent}'");
                                        }

                                        ServiceEventSource.Current.ServiceMessage(
                                            this.Context,
                                            $"RouterService - {ServiceUniqueId} - RunAsync - Sent event data to Insight service '{targetSiteServiceName}' with partition key '{targetSiteServicePartitionKey}'. Result: {response.StatusCode.ToString()}");
                                    }
                                }

                                // Save the current Iot Hub data stream offset.
                                // This will allow the service to pick up from its current location if it fails over.
                                // Duplicate device messages may still be sent to the the target site service
                                // if this service fails over after the message is sent but before the offset is saved.
                                if (++offsetIteration % OffsetInterval == 0)
                                {
                                    ServiceEventSource.Current.ServiceMessage(
                                        this.Context,
                                        $"RouterService - {ServiceUniqueId} - RunAsync - Saving offset {eventData.Offset}");

                                    using (ITransaction tx = this.StateManager.CreateTransaction())
                                    {
                                        await offsetDictionary.SetAsync(tx, "offset", eventData.Offset);

                                        await tx.CommitAsync();
                                    }

                                    offsetIteration = 0;
                                }
                            }
                        }
                        catch (Microsoft.ServiceBus.Messaging.ReceiverDisconnectedException rde)
                        {
                            // transient error. Retry.
                            ServiceEventSource.Current.ServiceMessage(this.Context, $"RouterService - {ServiceUniqueId} - RunAsync - Receiver Disconnected Exception in RunAsync: {rde.ToString()}");

                            IsConnected = false;
                        }
                        catch (TimeoutException te)
                        {
                            // transient error. Retry.
                            ServiceEventSource.Current.ServiceMessage(this.Context, $"RouterService - {ServiceUniqueId} - RunAsync - TimeoutException in RunAsync: {te.ToString()}");
                        }
                        catch (FabricTransientException fte)
                        {
                            // transient error. Retry.
                            ServiceEventSource.Current.ServiceMessage(this.Context, $"RouterService - {ServiceUniqueId} - RunAsync - FabricTransientException in RunAsync: {fte.ToString()}");
                        }
                        catch (FabricNotPrimaryException fnpe)
                        {
                            ServiceEventSource.Current.ServiceMessage(this.Context, $"RouterService - {ServiceUniqueId} - RunAsync - FabricNotPrimaryException Exception - Message=[{fnpe}]");

                            // not primary any more, time to quit.
                            return;
                        }
                        catch (Exception ex)
                        {
                            IsConnected = false;
                            string url = postUrl == null ? "Url undefined" : postUrl.ToString();
                            //ServiceEventSource.Current.ServiceMessage(this.Context, $"RouterService - {ServiceUniqueId} - RunAsync - General Exception Url=[{url}]- Message=[{ex}] - Inner Exception=[{ex.InnerException.Message ?? "ex.InnerException is null"}] Call Stack=[{ex.StackTrace ?? "ex.StackTrace is null"}] - Stack trace of inner exception=[{ex.InnerException.StackTrace ?? "ex.InnerException.StackTrace is null"}]");

                            ServiceEventSource.Current.ServiceMessage(this.Context, $"RouterService - {ServiceUniqueId} - RunAsync - General Exception Message[{ex.Message}] for url[{url}]");
                        }
                    }
                }
            }
            finally
            {
                if (messagingFactory != null)
                {
                    await messagingFactory.CloseAsync();
                }
            }
        }