44using Microsoft . Azure . Devices . Client . Exceptions ;
55using Microsoft . Extensions . Logging ;
66using System ;
7+ using System . Collections . Generic ;
8+ using System . Linq ;
79using System . Text ;
810using System . Threading ;
911using System . Threading . Tasks ;
@@ -14,22 +16,35 @@ public class DeviceReconnectionSample
1416 {
1517 private static readonly Random s_randomGenerator = new Random ( ) ;
1618 private const int TemperatureThreshold = 30 ;
19+
1720 private static readonly TimeSpan s_sleepDuration = TimeSpan . FromSeconds ( 5 ) ;
1821 private static readonly TimeSpan s_operationTimeout = TimeSpan . FromHours ( 1 ) ;
1922
20- private readonly string _deviceConnectionString ;
23+ private readonly object _initLock = new object ( ) ;
24+ private readonly List < string > _deviceConnectionStrings ;
2125 private readonly TransportType _transportType ;
22- private readonly ILogger _logger ;
23- private readonly object _lock = new object ( ) ;
2426
27+ private readonly ILogger _logger ;
2528 private static DeviceClient s_deviceClient ;
29+
2630 private static ConnectionStatus s_connectionStatus ;
2731 private static bool s_wasEverConnected ;
2832
29- public DeviceReconnectionSample ( string deviceConnectionString , TransportType transportType , ILogger logger )
33+ public DeviceReconnectionSample ( List < string > deviceConnectionStrings , TransportType transportType , ILogger logger )
3034 {
3135 _logger = logger ;
32- _deviceConnectionString = deviceConnectionString ;
36+
37+ // This class takes a list of potentially valid connection strings (most likely the currently known good primary and secondary keys)
38+ // and will attempt to connect with the first. If it receives feedback that a connection string is invalid, it will discard it, and
39+ // if any more are remaining, will try the next one.
40+ // To test this, either pass an invalid connection string as the first one, or rotate it while the sample is running, and wait about
41+ // 5 minutes.
42+ if ( deviceConnectionStrings == null
43+ || ! deviceConnectionStrings . Any ( ) )
44+ {
45+ throw new ArgumentException ( "At least one connection string must be provided." , nameof ( deviceConnectionStrings ) ) ;
46+ }
47+ _deviceConnectionStrings = deviceConnectionStrings ;
3348 _transportType = transportType ;
3449
3550 InitializeClient ( ) ;
@@ -59,9 +74,10 @@ public async Task RunSampleAsync()
5974 private void InitializeClient ( )
6075 {
6176 // If the client reports Connected status, it is already in operational state.
62- if ( s_connectionStatus != ConnectionStatus . Connected )
77+ if ( s_connectionStatus != ConnectionStatus . Connected
78+ && _deviceConnectionStrings . Any ( ) )
6379 {
64- lock ( _lock )
80+ lock ( _initLock )
6581 {
6682 _logger . LogDebug ( $ "Attempting to initialize the client instance, current status={ s_connectionStatus } ") ;
6783
@@ -73,11 +89,21 @@ private void InitializeClient()
7389 s_wasEverConnected = false ;
7490 }
7591
76- s_deviceClient = DeviceClient . CreateFromConnectionString ( _deviceConnectionString , _transportType ) ;
92+ s_deviceClient = DeviceClient . CreateFromConnectionString ( _deviceConnectionStrings . First ( ) , _transportType ) ;
7793 s_deviceClient . SetConnectionStatusChangesHandler ( ConnectionStatusChangeHandler ) ;
7894 s_deviceClient . OperationTimeoutInMilliseconds = ( uint ) s_operationTimeout . TotalMilliseconds ;
95+ }
96+
97+ try
98+ {
99+ // Force connection now
100+ s_deviceClient . OpenAsync ( ) . GetAwaiter ( ) . GetResult ( ) ;
79101 _logger . LogDebug ( $ "Initialized the client instance.") ;
80102 }
103+ catch ( UnauthorizedException )
104+ {
105+ // Handled by the ConnectionStatusChangeHandler
106+ }
81107 }
82108 }
83109
@@ -107,8 +133,19 @@ private void ConnectionStatusChangeHandler(ConnectionStatus status, ConnectionSt
107133 switch ( reason )
108134 {
109135 case ConnectionStatusChangeReason . Bad_Credential :
110- _logger . LogWarning ( "### The supplied credentials were invalid." +
111- "\n Fix the input and then create a new device client instance." ) ;
136+ // When getting this reason, the current connection string being used is not valid.
137+ // If we had a backup, we can try using that.
138+ string badCs = _deviceConnectionStrings [ 0 ] ;
139+ _deviceConnectionStrings . RemoveAt ( 0 ) ;
140+ if ( _deviceConnectionStrings . Any ( ) )
141+ {
142+ // Not great to print out a connection string, but this is done for sample/demo purposes.
143+ _logger . LogWarning ( $ "The current connection string { badCs } is invalid. Trying another.") ;
144+ InitializeClient ( ) ;
145+ break ;
146+ }
147+
148+ _logger . LogWarning ( "### The supplied credentials are invalid. Update the parameters and run again." ) ;
112149 break ;
113150
114151 case ConnectionStatusChangeReason . Device_Disabled :
@@ -146,24 +183,27 @@ private void ConnectionStatusChangeHandler(ConnectionStatus status, ConnectionSt
146183
147184 private async Task SendMessagesAsync ( CancellationToken cancellationToken )
148185 {
149- int count = 0 ;
186+ int messageCount = 0 ;
150187
151188 while ( ! cancellationToken . IsCancellationRequested )
152189 {
153- _logger . LogInformation ( $ "Device sending message { count ++ } to IoTHub...") ;
154-
155- try
156- {
157- await SendMessageAsync ( count ) ;
158- }
159- catch ( Exception ex ) when ( ex is IotHubException exception && exception . IsTransient )
190+ if ( s_connectionStatus == ConnectionStatus . Connected )
160191 {
161- // Inspect the exception to figure out if operation should be retried, or if user-input is required.
162- _logger . LogError ( $ "An IotHubException was caught, but will try to recover and retry explicitly: { ex } ") ;
163- }
164- catch ( Exception ex ) when ( ExceptionHelper . IsNetworkExceptionChain ( ex ) )
165- {
166- _logger . LogError ( $ "A network related exception was caught, but will try to recover and retry explicitly: { ex } ") ;
192+ _logger . LogInformation ( $ "Device sending message { ++ messageCount } to IoT Hub...") ;
193+
194+ try
195+ {
196+ await SendMessageAsync ( messageCount ) ;
197+ }
198+ catch ( IotHubException ex ) when ( ex . IsTransient )
199+ {
200+ // Inspect the exception to figure out if operation should be retried, or if user-input is required.
201+ _logger . LogError ( $ "An IotHubException was caught, but will try to recover and retry explicitly: { ex } ") ;
202+ }
203+ catch ( Exception ex ) when ( ExceptionHelper . IsNetworkExceptionChain ( ex ) )
204+ {
205+ _logger . LogError ( $ "A network related exception was caught, but will try to recover and retry explicitly: { ex } ") ;
206+ }
167207 }
168208
169209 await Task . Delay ( s_sleepDuration ) ;
@@ -174,18 +214,24 @@ private async Task ReceiveMessagesAsync(CancellationToken cancellationToken)
174214 {
175215 while ( ! cancellationToken . IsCancellationRequested )
176216 {
217+ if ( s_connectionStatus != ConnectionStatus . Connected )
218+ {
219+ await Task . Delay ( s_sleepDuration ) ;
220+ continue ;
221+ }
222+
177223 _logger . LogInformation ( $ "Device waiting for C2D messages from the hub - for { s_sleepDuration } ...") ;
178224 _logger . LogInformation ( "Use the IoT Hub Azure Portal or Azure IoT Explorer to send a message to this device." ) ;
179225
180226 try
181227 {
182228 await ReceiveMessageAndCompleteAsync ( ) ;
183229 }
184- catch ( Exception ex ) when ( ex is DeviceMessageLockLostException )
230+ catch ( DeviceMessageLockLostException ex )
185231 {
186232 _logger . LogWarning ( $ "Attempted to complete a received message whose lock token has expired; ignoring: { ex } ") ;
187233 }
188- catch ( Exception ex ) when ( ex is IotHubException exception && exception . IsTransient )
234+ catch ( IotHubException ex ) when ( ex . IsTransient )
189235 {
190236 // Inspect the exception to figure out if operation should be retried, or if user-input is required.
191237 _logger . LogError ( $ "An IotHubException was caught, but will try to recover and retry explicitly: { ex } ") ;
@@ -197,35 +243,35 @@ private async Task ReceiveMessagesAsync(CancellationToken cancellationToken)
197243 }
198244 }
199245
200- private async Task SendMessageAsync ( int count )
246+ private async Task SendMessageAsync ( int messageId )
201247 {
202- var _temperature = s_randomGenerator . Next ( 20 , 35 ) ;
203- var _humidity = s_randomGenerator . Next ( 60 , 80 ) ;
204- string dataBuffer = $ "{{\" temperature\" :{ _temperature } ,\" humidity\" :{ _humidity } }}";
248+ var temperature = s_randomGenerator . Next ( 20 , 35 ) ;
249+ var humidity = s_randomGenerator . Next ( 60 , 80 ) ;
250+ string messagePayload = $ "{{\" temperature\" :{ temperature } ,\" humidity\" :{ humidity } }}";
205251
206- using var eventMessage = new Message ( Encoding . UTF8 . GetBytes ( dataBuffer ) )
252+ using var eventMessage = new Message ( Encoding . UTF8 . GetBytes ( messagePayload ) )
207253 {
208- MessageId = count . ToString ( ) ,
254+ MessageId = messageId . ToString ( ) ,
209255 ContentEncoding = Encoding . UTF8 . ToString ( ) ,
210256 ContentType = "application/json" ,
211257 } ;
212- eventMessage . Properties . Add ( "temperatureAlert" , ( _temperature > TemperatureThreshold ) ? "true" : "false" ) ;
258+ eventMessage . Properties . Add ( "temperatureAlert" , ( temperature > TemperatureThreshold ) ? "true" : "false" ) ;
213259
214260 await s_deviceClient . SendEventAsync ( eventMessage ) ;
215- _logger . LogInformation ( $ "Sent message: { count } , Data: [ { dataBuffer } ]") ;
261+ _logger . LogInformation ( $ "Sent message { messageId } with data [ { messagePayload } ]") ;
216262 }
217263
218264 private async Task ReceiveMessageAndCompleteAsync ( )
219265 {
220266 using Message receivedMessage = await s_deviceClient . ReceiveAsync ( s_sleepDuration ) ;
221267 if ( receivedMessage == null )
222268 {
223- _logger . LogInformation ( "No message received, timed out" ) ;
269+ _logger . LogInformation ( "No message received; timed out. " ) ;
224270 return ;
225271 }
226272
227273 string messageData = Encoding . ASCII . GetString ( receivedMessage . GetBytes ( ) ) ;
228- var formattedMessage = new StringBuilder ( $ "Received message: { messageData } \n ") ;
274+ var formattedMessage = new StringBuilder ( $ "Received message: [ { messageData } ] \n ") ;
229275
230276 foreach ( var prop in receivedMessage . Properties )
231277 {
0 commit comments