public virtual void ReceiveEvent(IEvent <TAuthenticationToken> @event) { switch (@event.Framework) { case FrameworkType.Akka: Logger.LogInfo(string.Format("An event arrived of the type '{0}' but was marked as coming from the '{1}' framework, so it was dropped.", @event.GetType().FullName, @event.Framework)); return; } CorrelationIdHelper.SetCorrelationId(@event.CorrelationId); AuthenticationTokenHelper.SetAuthenticationToken(@event.AuthenticationToken); Type eventType = @event.GetType(); bool isRequired = BusHelper.IsEventRequired(eventType); IEnumerable <Action <IMessage> > handlers = Routes.GetHandlers(@event, isRequired).Select(x => x.Delegate).ToList(); // This check doesn't require an isRequired check as there will be an exception raised above and handled below. if (!handlers.Any()) { Logger.LogDebug(string.Format("The event handler for '{0}' is not required.", eventType.FullName)); } foreach (Action <IMessage> handler in handlers) { handler(@event); } }
/// <summary> /// The default event handler that /// check if the <see cref="IEvent{TAuthenticationToken}"/> has already been processed by this framework, /// checks if the <see cref="IEvent{TAuthenticationToken}"/> is required, /// finds the handler from the provided <paramref name="routeManager"/>. /// </summary> /// <param name="event">The <see cref="IEvent{TAuthenticationToken}"/> to process.</param> /// <param name="routeManager">The <see cref="RouteManager"/> to get the <see cref="IEventHandler{TAuthenticationToken,TCommand}"/> from.</param> /// <param name="framework">The current framework.</param> /// <returns> /// True indicates the <paramref name="event"/> was successfully handled by a handler. /// False indicates the <paramref name="event"/> wasn't handled, but didn't throw an error, so by convention, that means it was skipped. /// Null indicates the <paramref name="event"/> wasn't handled as it was already handled. /// </returns> public virtual bool?DefaultReceiveEvent(IEvent <TAuthenticationToken> @event, RouteManager routeManager, string framework) { Type eventType = @event.GetType(); if (@event.Frameworks != null && @event.Frameworks.Contains(framework)) { // if this is the only framework in the list, then it's fine to handle as it's just pre-stamped, if there is more than one framework, then exit. if (@event.Frameworks.Count() != 1) { Logger.LogInfo("The provided event has already been processed in Azure.", string.Format("{0}\\DefaultReceiveEvent({1})", GetType().FullName, eventType.FullName)); return(null); } } CorrelationIdHelper.SetCorrelationId(@event.CorrelationId); AuthenticationTokenHelper.SetAuthenticationToken(@event.AuthenticationToken); bool isRequired = BusHelper.IsEventRequired(eventType); IEnumerable <Action <IMessage> > handlers = routeManager.GetHandlers(@event, isRequired).Select(x => x.Delegate).ToList(); // This check doesn't require an isRequired check as there will be an exception raised above and handled below. if (!handlers.Any()) { Logger.LogDebug(string.Format("The event handler for '{0}' is not required.", eventType.FullName)); return(false); } foreach (Action <IMessage> handler in handlers) { handler(@event); } return(true); }
public virtual void DefaultReceiveCommand(ICommand <TAuthenticationToken> command, RouteManager routeManager, string framework) { Type commandType = command.GetType(); if (command.Frameworks != null && command.Frameworks.Contains(framework)) { Logger.LogInfo("The provided command has already been processed in Azure.", string.Format("{0}\\DefaultReceiveCommand({1})", GetType().FullName, commandType.FullName)); return; } CorrelationIdHelper.SetCorrelationId(command.CorrelationId); AuthenticationTokenHelper.SetAuthenticationToken(command.AuthenticationToken); bool isRequired = BusHelper.IsEventRequired(commandType); RouteHandlerDelegate commandHandler = routeManager.GetSingleHandler(command, isRequired); // This check doesn't require an isRequired check as there will be an exception raised above and handled below. if (commandHandler == null) { Logger.LogDebug(string.Format("The command handler for '{0}' is not required.", commandType.FullName)); return; } Action <IMessage> handler = commandHandler.Delegate; handler(command); }
public virtual bool PrepareAndValidateEvent <TEvent>(TEvent @event, string framework, out IEnumerable <RouteHandlerDelegate> handlers) where TEvent : IEvent <TAuthenticationToken> { Type eventType = @event.GetType(); if (@event.Frameworks != null && @event.Frameworks.Contains(framework)) { Logger.LogInfo("The provided event has already been processed in Akka.", string.Format("{0}\\PrepareAndValidateEvent({1})", GetType().FullName, eventType.FullName)); handlers = Enumerable.Empty <RouteHandlerDelegate>(); return(false); } PrepareEvent(@event, framework); bool isRequired = BusHelper.IsEventRequired(eventType); handlers = Routes.GetHandlers(@event, isRequired); // This check doesn't require an isRequired check as there will be an exception raised above and handled below. if (handlers == null || !handlers.Any()) { Logger.LogDebug(string.Format("An event handler for '{0}' is not required.", eventType.FullName)); } return(true); }
/// <summary> /// The default command handler that /// check if the <see cref="ICommand{TAuthenticationToken}"/> has already been processed by this framework, /// checks if the <see cref="ICommand{TAuthenticationToken}"/> is required, /// finds the handler from the provided <paramref name="routeManager"/>. /// </summary> /// <param name="command">The <see cref="ICommand{TAuthenticationToken}"/> to process.</param> /// <param name="routeManager">The <see cref="RouteManager"/> to get the <see cref="ICommandHandler{TAuthenticationToken,TCommand}"/> from.</param> /// <param name="framework">The current framework.</param> /// <returns> /// True indicates the <paramref name="command"/> was successfully handled by a handler. /// False indicates the <paramref name="command"/> wasn't handled, but didn't throw an error, so by convention, that means it was skipped. /// Null indicates the command<paramref name="command"/> wasn't handled as it was already handled. /// </returns> public virtual bool?DefaultReceiveCommand(ICommand <TAuthenticationToken> command, RouteManager routeManager, string framework) { Type commandType = command.GetType(); if (command.Frameworks != null && command.Frameworks.Contains(framework)) { // if this is the only framework in the list, then it's fine to handle as it's just pre-stamped, if there is more than one framework, then exit. if (command.Frameworks.Count() != 1) { Logger.LogInfo("The provided command has already been processed in Azure.", string.Format("{0}\\DefaultReceiveCommand({1})", GetType().FullName, commandType.FullName)); return(null); } } CorrelationIdHelper.SetCorrelationId(command.CorrelationId); AuthenticationTokenHelper.SetAuthenticationToken(command.AuthenticationToken); bool isRequired = BusHelper.IsEventRequired(commandType); RouteHandlerDelegate commandHandler = routeManager.GetSingleHandler(command, isRequired); // This check doesn't require an isRequired check as there will be an exception raised above and handled below. if (commandHandler == null) { Logger.LogDebug(string.Format("The command handler for '{0}' is not required.", commandType.FullName)); return(false); } Action <IMessage> handler = commandHandler.Delegate; handler(command); return(true); }
protected virtual bool PrepareAndValidateCommand <TCommand>(TCommand command, out RouteHandlerDelegate commandHandler) where TCommand : ICommand <TAuthenticationToken> { Type commandType = command.GetType(); if (command.Frameworks != null && command.Frameworks.Contains("Akka")) { // if this is the only framework in the list, then it's fine to handle as it's just pre-stamped, if there is more than one framework, then exit. if (command.Frameworks.Count() != 1) { Logger.LogInfo("The provided command has already been processed in Akka.", string.Format("{0}\\PrepareAndValidateEvent({1})", GetType().FullName, commandType.FullName)); commandHandler = null; return(false); } } ICommandValidator <TAuthenticationToken, TCommand> commandValidator = null; try { commandValidator = DependencyResolver.Resolve <ICommandValidator <TAuthenticationToken, TCommand> >(); } catch (Exception exception) { Logger.LogDebug("Locating an ICommandValidator failed.", string.Format("{0}\\Handle({1})", GetType().FullName, commandType.FullName), exception); } if (commandValidator != null && !commandValidator.IsCommandValid(command)) { Logger.LogInfo("The provided command is not valid.", string.Format("{0}\\Handle({1})", GetType().FullName, commandType.FullName)); commandHandler = null; return(false); } PrepareCommand(command); bool isRequired = BusHelper.IsEventRequired(commandType); commandHandler = Routes.GetSingleHandler(command, isRequired); // This check doesn't require an isRequired check as there will be an exception raised above and handled below. if (commandHandler == null) { Logger.LogDebug(string.Format("The command handler for '{0}' is not required.", commandType.FullName)); } return(true); }
protected virtual void ReceiveEvent(BrokeredMessage message) { var brokeredMessageRenewCancellationTokenSource = new CancellationTokenSource(); try { Logger.LogDebug(string.Format("An event message arrived with the id '{0}'.", message.MessageId)); string messageBody = message.GetBody <string>(); IEvent <TAuthenticationToken> @event; try { @event = MessageSerialiser.DeserialiseEvent(messageBody); } catch (JsonSerializationException exception) { JsonSerializationException checkException = exception; bool safeToExit = false; do { if (checkException.Message.StartsWith("Could not load assembly")) { safeToExit = true; break; } } while ((checkException = checkException.InnerException as JsonSerializationException) != null); if (safeToExit) { const string pattern = @"(?<=^Error resolving type specified in JSON ').+?(?='\. Path '\$type')"; Match match = new Regex(pattern).Match(exception.Message); if (match.Success) { string[] typeParts = match.Value.Split(','); if (typeParts.Length == 2) { string classType = typeParts[0]; bool isRequired = BusHelper.IsEventRequired(string.Format("{0}.IsRequired", classType)); if (!isRequired) { // Remove message from queue message.Complete(); Logger.LogDebug(string.Format("An event message arrived with the id '{0}' but processing was skipped due to event settings.", message.MessageId)); return; } } } } throw; } CorrelationIdHelper.SetCorrelationId(@event.CorrelationId); Logger.LogInfo(string.Format("An event message arrived with the id '{0}' was of type {1}.", message.MessageId, @event.GetType().FullName)); bool canRefresh; if (!ConfigurationManager.TryGetSetting(string.Format("{0}.ShouldRefresh", @event.GetType().FullName), out canRefresh)) { canRefresh = false; } if (canRefresh) { Task.Factory.StartNew(() => { while (!brokeredMessageRenewCancellationTokenSource.Token.IsCancellationRequested) { //Based on LockedUntilUtc property to determine if the lock expires soon if (DateTime.UtcNow > message.LockedUntilUtc.AddSeconds(-10)) { // If so, repeat the message message.RenewLock(); } Thread.Sleep(500); } }, brokeredMessageRenewCancellationTokenSource.Token); } ReceiveEvent(@event); // Remove message from queue message.Complete(); Logger.LogDebug(string.Format("An event message arrived and was processed with the id '{0}'.", message.MessageId)); IList <IEvent <TAuthenticationToken> > events; if (EventWaits.TryGetValue(@event.CorrelationId, out events)) { events.Add(@event); } } catch (Exception exception) { // Indicates a problem, unlock message in queue Logger.LogError(string.Format("An event message arrived with the id '{0}' but failed to be process.", message.MessageId), exception: exception); message.Abandon(); } finally { // Cancel the lock of renewing the task brokeredMessageRenewCancellationTokenSource.Cancel(); } }
public virtual IEvent <TAuthenticationToken> ReceiveEvent(string messageBody, Action <IEvent <TAuthenticationToken> > receiveEventHandler, string messageId, Action skippedAction = null, Action lockRefreshAction = null) { IEvent <TAuthenticationToken> @event; try { @event = MessageSerialiser.DeserialiseEvent(messageBody); } catch (JsonSerializationException exception) { JsonSerializationException checkException = exception; bool safeToExit = false; do { if (checkException.Message.StartsWith("Could not load assembly")) { safeToExit = true; break; } } while ((checkException = checkException.InnerException as JsonSerializationException) != null); if (safeToExit) { const string pattern = @"(?<=^Error resolving type specified in JSON ').+?(?='\. Path '\$type')"; Match match = new Regex(pattern).Match(exception.Message); if (match.Success) { string[] typeParts = match.Value.Split(','); if (typeParts.Length == 2) { string classType = typeParts[0]; bool isRequired = BusHelper.IsEventRequired(string.Format("{0}.IsRequired", classType)); if (!isRequired) { if (skippedAction != null) { skippedAction(); } return(null); } } } } throw; } string eventTypeName = @event.GetType().FullName; CorrelationIdHelper.SetCorrelationId(@event.CorrelationId); Logger.LogInfo(string.Format("An event message arrived with the {0} was of type {1}.", messageId, eventTypeName)); bool canRefresh; if (!ConfigurationManager.TryGetSetting(string.Format("{0}.ShouldRefresh", eventTypeName), out canRefresh)) { canRefresh = false; } if (canRefresh) { if (lockRefreshAction == null) { Logger.LogWarning(string.Format("An event message arrived with the {0} was of type {1} and was destined to support lock renewal, but no action was provided.", messageId, eventTypeName)); } else { lockRefreshAction(); } } receiveEventHandler(@event); return(@event); }
/// <summary> /// Deserialises and processes the <paramref name="messageBody"/> received from the network through the provided <paramref name="receiveCommandHandler"/>. /// </summary> /// <param name="messageBody">A serialised <see cref="IMessage"/>.</param> /// <param name="receiveCommandHandler">The handler method that will process the <see cref="ICommand{TAuthenticationToken}"/>.</param> /// <param name="messageId">The network id of the <see cref="IMessage"/>.</param> /// <param name="skippedAction">The <see cref="Action"/> to call when the <see cref="ICommand{TAuthenticationToken}"/> is being skipped.</param> /// <param name="lockRefreshAction">The <see cref="Action"/> to call to refresh the network lock.</param> /// <returns>The <see cref="ICommand{TAuthenticationToken}"/> that was processed.</returns> public virtual ICommand <TAuthenticationToken> ReceiveCommand(string messageBody, Func <ICommand <TAuthenticationToken>, bool?> receiveCommandHandler, string messageId, Action skippedAction = null, Action lockRefreshAction = null) { ICommand <TAuthenticationToken> command; try { command = MessageSerialiser.DeserialiseCommand(messageBody); } catch (JsonSerializationException exception) { JsonSerializationException checkException = exception; bool safeToExit = false; do { if (checkException.Message.StartsWith("Could not load assembly")) { safeToExit = true; break; } } while ((checkException = checkException.InnerException as JsonSerializationException) != null); if (safeToExit) { const string pattern = @"(?<=^Error resolving type specified in JSON ').+?(?='\. Path '\$type')"; Match match = new Regex(pattern).Match(exception.Message); if (match.Success) { string[] typeParts = match.Value.Split(','); if (typeParts.Length == 2) { string classType = typeParts[0]; bool isRequired = BusHelper.IsEventRequired(string.Format("{0}.IsRequired", classType)); if (!isRequired) { if (skippedAction != null) { skippedAction(); } return(null); } } } } throw; } string commandTypeName = command.GetType().FullName; CorrelationIdHelper.SetCorrelationId(command.CorrelationId); AuthenticationTokenHelper.SetAuthenticationToken(command.AuthenticationToken); Logger.LogInfo(string.Format("A command message arrived with the {0} was of type {1}.", messageId, commandTypeName)); bool canRefresh; if (!ConfigurationManager.TryGetSetting(string.Format("{0}.ShouldRefresh", commandTypeName), out canRefresh)) { canRefresh = false; } if (canRefresh) { if (lockRefreshAction == null) { Logger.LogWarning(string.Format("A command message arrived with the {0} was of type {1} and was destined to support lock renewal, but no action was provided.", messageId, commandTypeName)); } else { lockRefreshAction(); } } // a false response means the action wasn't handled, but didn't throw an error, so we assume, by convention, that this means it was skipped. bool?result = receiveCommandHandler(command); if (result != null && !result.Value) { if (skippedAction != null) { skippedAction(); } } return(command); }
/// <summary> /// Using a <see cref="Task"/>, clears all dead letters from the topic and subscription of the /// provided <paramref name="topicName"/> and <paramref name="topicSubscriptionName"/>. /// </summary> /// <param name="topicName">The name of the topic.</param> /// <param name="topicSubscriptionName">The name of the subscription.</param> /// <returns></returns> protected virtual CancellationTokenSource CleanUpDeadLetters(string topicName, string topicSubscriptionName) { var brokeredMessageRenewCancellationTokenSource = new CancellationTokenSource(); IDictionary <string, string> telemetryProperties = new Dictionary <string, string> { { "Type", "Azure/Servicebus" } }; int lockIssues = 0; Action <BrokeredMessage, IMessage> leaveDeadlLetterInQueue = (deadLetterBrokeredMessage, deadLetterMessage) => { // Remove message from queue try { deadLetterBrokeredMessage.Abandon(); lockIssues = 0; } catch (MessageLockLostException) { lockIssues++; Logger.LogWarning(string.Format("The lock supplied for abandon for the skipped dead-letter message '{0}' is invalid.", deadLetterBrokeredMessage.MessageId)); } Logger.LogDebug(string.Format("A dead-letter message of type {0} arrived with the id '{1}' but left in the queue due to settings.", deadLetterMessage.GetType().FullName, deadLetterBrokeredMessage.MessageId)); }; Action <BrokeredMessage> removeDeadlLetterFromQueue = deadLetterBrokeredMessage => { // Remove message from queue try { deadLetterBrokeredMessage.Complete(); lockIssues = 0; } catch (MessageLockLostException) { lockIssues++; Logger.LogWarning(string.Format("The lock supplied for complete for the skipped dead-letter message '{0}' is invalid.", deadLetterBrokeredMessage.MessageId)); } Logger.LogDebug(string.Format("A dead-letter message arrived with the id '{0}' but was removed as processing was skipped due to settings.", deadLetterBrokeredMessage.MessageId)); }; Task.Factory.StartNewSafely(() => { int loop = 0; while (!brokeredMessageRenewCancellationTokenSource.Token.IsCancellationRequested) { lockIssues = 0; MessagingFactory factory = MessagingFactory.CreateFromConnectionString(ConnectionString); string deadLetterPath = SubscriptionClient.FormatDeadLetterPath(topicName, topicSubscriptionName); MessageReceiver client = factory.CreateMessageReceiver(deadLetterPath, ReceiveMode.PeekLock); IEnumerable <BrokeredMessage> brokeredMessages = client.ReceiveBatch(1000); foreach (BrokeredMessage brokeredMessage in brokeredMessages) { if (lockIssues > 10) { break; } try { Logger.LogDebug(string.Format("A dead-letter message arrived with the id '{0}'.", brokeredMessage.MessageId)); string messageBody = brokeredMessage.GetBody <string>(); // Closure protection BrokeredMessage message = brokeredMessage; try { AzureBusHelper.ReceiveEvent ( messageBody, @event => { bool isRequired = BusHelper.IsEventRequired(@event.GetType()); if (!isRequired) { removeDeadlLetterFromQueue(message); } else { leaveDeadlLetterInQueue(message, @event); } return(true); }, string.Format("id '{0}'", brokeredMessage.MessageId), () => { removeDeadlLetterFromQueue(message); }, () => { } ); } catch { AzureBusHelper.ReceiveCommand ( messageBody, command => { bool isRequired = BusHelper.IsEventRequired(command.GetType()); if (!isRequired) { removeDeadlLetterFromQueue(message); } else { leaveDeadlLetterInQueue(message, command); } return(true); }, string.Format("id '{0}'", brokeredMessage.MessageId), () => { removeDeadlLetterFromQueue(message); }, () => { } ); } } catch (Exception exception) { TelemetryHelper.TrackException(exception, null, telemetryProperties); // Indicates a problem, unlock message in queue Logger.LogError(string.Format("A dead-letter message arrived with the id '{0}' but failed to be process.", brokeredMessage.MessageId), exception: exception); try { brokeredMessage.Abandon(); } catch (MessageLockLostException) { lockIssues++; Logger.LogWarning(string.Format("The lock supplied for abandon for the skipped dead-letter message '{0}' is invalid.", brokeredMessage.MessageId)); } } } client.Close(); if (loop++ % 5 == 0) { loop = 0; Thread.Yield(); } else { Thread.Sleep(500); } } try { brokeredMessageRenewCancellationTokenSource.Dispose(); } catch (ObjectDisposedException) { } }, brokeredMessageRenewCancellationTokenSource.Token); return(brokeredMessageRenewCancellationTokenSource); }
protected virtual void ReceiveCommand(BrokeredMessage message) { try { Logger.LogDebug(string.Format("A command message arrived with the id '{0}'.", message.MessageId)); string messageBody = message.GetBody <string>(); ICommand <TAuthenticationToken> command; try { command = MessageSerialiser.DeserialiseCommand(messageBody); } catch (JsonSerializationException exception) { JsonSerializationException checkException = exception; bool safeToExit = false; do { if (checkException.Message.StartsWith("Could not load assembly")) { safeToExit = true; break; } } while ((checkException = checkException.InnerException as JsonSerializationException) != null); if (safeToExit) { const string pattern = @"(?<=^Error resolving type specified in JSON ').+?(?='\. Path '\$type')"; Match match = new Regex(pattern).Match(exception.Message); if (match.Success) { string[] typeParts = match.Value.Split(','); if (typeParts.Length == 2) { string classType = typeParts[0]; bool isRequired = BusHelper.IsEventRequired(string.Format("{0}.IsRequired", classType)); if (!isRequired) { // Remove message from queue message.Complete(); Logger.LogDebug(string.Format("A command message arrived with the id '{0}' but processing was skipped due to command settings.", message.MessageId)); return; } } } } throw; } CorrelationIdHelper.SetCorrelationId(command.CorrelationId); Logger.LogInfo(string.Format("A command message arrived with the id '{0}' was of type {1}.", message.MessageId, command.GetType().FullName)); ReceiveCommand(command); // Remove message from queue message.Complete(); Logger.LogDebug(string.Format("A command message arrived and was processed with the id '{0}'.", message.MessageId)); } catch (Exception exception) { // Indicates a problem, unlock message in queue Logger.LogError(string.Format("A command message arrived with the id '{0}' but failed to be process.", message.MessageId), exception: exception); message.Abandon(); } }
/// <summary> /// Using a <see cref="Task"/>, clears all dead letters from the topic and subscription of the /// provided <paramref name="topicName"/> and <paramref name="topicSubscriptionName"/>. /// </summary> /// <param name="topicName">The name of the topic.</param> /// <param name="topicSubscriptionName">The name of the subscription.</param> /// <returns></returns> protected virtual CancellationTokenSource CleanUpDeadLetters(string topicName, string topicSubscriptionName) { var brokeredMessageRenewCancellationTokenSource = new CancellationTokenSource(); IDictionary <string, string> telemetryProperties = new Dictionary <string, string> { { "Type", "Azure/Servicebus" } }; int lockIssues = 0; #if NET452 Action <BrokeredMessage, IMessage> leaveDeadlLetterInQueue = (deadLetterBrokeredMessage, deadLetterMessage) => #endif #if NETSTANDARD2_0 Action <IMessageReceiver, BrokeredMessage, IMessage> leaveDeadlLetterInQueue = (client, deadLetterBrokeredMessage, deadLetterMessage) => #endif { // Remove message from queue try { #if NET452 deadLetterBrokeredMessage.Abandon(); #endif #if NETSTANDARD2_0 client.AbandonAsync(deadLetterBrokeredMessage.SystemProperties.LockToken).Wait(1500); #endif lockIssues = 0; } catch (MessageLockLostException) { lockIssues++; Logger.LogWarning(string.Format("The lock supplied for abandon for the skipped dead-letter message '{0}' is invalid.", deadLetterBrokeredMessage.MessageId)); } Logger.LogDebug(string.Format("A dead-letter message of type {0} arrived with the id '{1}' but left in the queue due to settings.", deadLetterMessage.GetType().FullName, deadLetterBrokeredMessage.MessageId)); }; #if NET452 Action <BrokeredMessage> removeDeadlLetterFromQueue = (deadLetterBrokeredMessage) => #endif #if NETSTANDARD2_0 Action <IMessageReceiver, BrokeredMessage> removeDeadlLetterFromQueue = (client, deadLetterBrokeredMessage) => #endif { // Remove message from queue try { #if NET452 deadLetterBrokeredMessage.Complete(); #endif #if NETSTANDARD2_0 client.CompleteAsync(deadLetterBrokeredMessage.SystemProperties.LockToken).Wait(1500); #endif lockIssues = 0; } catch (MessageLockLostException) { lockIssues++; Logger.LogWarning(string.Format("The lock supplied for complete for the skipped dead-letter message '{0}' is invalid.", deadLetterBrokeredMessage.MessageId)); } Logger.LogDebug(string.Format("A dead-letter message arrived with the id '{0}' but was removed as processing was skipped due to settings.", deadLetterBrokeredMessage.MessageId)); }; Task.Factory.StartNewSafely(() => { int loop = 0; while (!brokeredMessageRenewCancellationTokenSource.Token.IsCancellationRequested) { lockIssues = 0; IEnumerable <BrokeredMessage> brokeredMessages; #if NET452 MessagingFactory factory = MessagingFactory.CreateFromConnectionString(ConnectionString); string deadLetterPath = SubscriptionClient.FormatDeadLetterPath(topicName, topicSubscriptionName); MessageReceiver client = factory.CreateMessageReceiver(deadLetterPath, ReceiveMode.PeekLock); brokeredMessages = client.ReceiveBatch(1000); #endif #if NETSTANDARD2_0 string deadLetterPath = EntityNameHelper.FormatDeadLetterPath(EntityNameHelper.FormatSubscriptionPath(topicName, topicSubscriptionName)); MessageReceiver client = new MessageReceiver(ConnectionString, deadLetterPath, ReceiveMode.PeekLock); Task <IList <BrokeredMessage> > receiveTask = client.ReceiveAsync(1000); receiveTask.Wait(10000); if (receiveTask.IsCompleted && receiveTask.Result != null) { brokeredMessages = receiveTask.Result; } else { brokeredMessages = Enumerable.Empty <BrokeredMessage>(); } #endif foreach (BrokeredMessage brokeredMessage in brokeredMessages) { if (lockIssues > 10) { break; } try { Logger.LogDebug(string.Format("A dead-letter message arrived with the id '{0}'.", brokeredMessage.MessageId)); string messageBody = brokeredMessage.GetBodyAsString(); // Closure protection BrokeredMessage message = brokeredMessage; try { AzureBusHelper.ReceiveEvent ( messageBody, @event => { bool isRequired = BusHelper.IsEventRequired(@event.GetType()); if (!isRequired) { #if NET452 removeDeadlLetterFromQueue(message); #endif #if NETSTANDARD2_0 removeDeadlLetterFromQueue(client, message); #endif } else { #if NET452 leaveDeadlLetterInQueue(message, @event); #endif #if NETSTANDARD2_0 leaveDeadlLetterInQueue(client, message, @event); #endif } return(true); }, string.Format("id '{0}'", brokeredMessage.MessageId), ExtractSignature(message), SigningTokenConfigurationKey, () => { #if NET452 removeDeadlLetterFromQueue(message); #endif #if NETSTANDARD2_0 removeDeadlLetterFromQueue(client, message); #endif }, () => { } ); } catch { AzureBusHelper.ReceiveCommand ( messageBody, command => { bool isRequired = BusHelper.IsEventRequired(command.GetType()); if (!isRequired) { #if NET452 removeDeadlLetterFromQueue(message); #endif #if NETSTANDARD2_0 removeDeadlLetterFromQueue(client, message); #endif } else { #if NET452 leaveDeadlLetterInQueue(message, command); #endif #if NETSTANDARD2_0 leaveDeadlLetterInQueue(client, message, command); #endif } return(true); }, string.Format("id '{0}'", brokeredMessage.MessageId), ExtractSignature(message), SigningTokenConfigurationKey, () => { #if NET452 removeDeadlLetterFromQueue(message); #endif #if NETSTANDARD2_0 removeDeadlLetterFromQueue(client, message); #endif }, () => { } ); } } catch (Exception exception) { TelemetryHelper.TrackException(exception, null, telemetryProperties); // Indicates a problem, unlock message in queue Logger.LogError(string.Format("A dead-letter message arrived with the id '{0}' but failed to be process.", brokeredMessage.MessageId), exception: exception); try { #if NET452 brokeredMessage.Abandon(); #endif #if NETSTANDARD2_0 client.AbandonAsync(brokeredMessage.SystemProperties.LockToken).Wait(1500); #endif } catch (MessageLockLostException) { lockIssues++; Logger.LogWarning(string.Format("The lock supplied for abandon for the skipped dead-letter message '{0}' is invalid.", brokeredMessage.MessageId)); } } } #if NET452 client.Close(); #endif #if NETSTANDARD2_0 client.CloseAsync().Wait(1500); #endif if (loop++ % 5 == 0) { loop = 0; Thread.Yield(); } else { Thread.Sleep(500); } } try { brokeredMessageRenewCancellationTokenSource.Dispose(); } catch (ObjectDisposedException) { } }, brokeredMessageRenewCancellationTokenSource.Token); return(brokeredMessageRenewCancellationTokenSource); }
/// <summary> /// Deserialises and processes the <paramref name="messageBody"/> received from the network through the provided <paramref name="receiveEventHandler"/>. /// </summary> /// <param name="messageBody">A serialised <see cref="IMessage"/>.</param> /// <param name="receiveEventHandler">The handler method that will process the <see cref="IEvent{TAuthenticationToken}"/>.</param> /// <param name="messageId">The network id of the <see cref="IMessage"/>.</param> /// <param name="signature">The signature of the <see cref="IMessage"/>.</param> /// <param name="signingTokenConfigurationKey">The configuration key for the signing token as used by <see cref="IConfigurationManager"/>.</param> /// <param name="skippedAction">The <see cref="Action"/> to call when the <see cref="IEvent{TAuthenticationToken}"/> is being skipped.</param> /// <param name="lockRefreshAction">The <see cref="Action"/> to call to refresh the network lock.</param> /// <returns>The <see cref="IEvent{TAuthenticationToken}"/> that was processed.</returns> public virtual IEvent <TAuthenticationToken> ReceiveEvent(string messageBody, Func <IEvent <TAuthenticationToken>, bool?> receiveEventHandler, string messageId, string signature, string signingTokenConfigurationKey, Action skippedAction = null, Action lockRefreshAction = null) { IEvent <TAuthenticationToken> @event; try { @event = MessageSerialiser.DeserialiseEvent(messageBody); } catch (JsonSerializationException exception) { JsonSerializationException checkException = exception; bool safeToExit = false; do { if (checkException.Message.StartsWith("Could not load assembly")) { safeToExit = true; break; } } while ((checkException = checkException.InnerException as JsonSerializationException) != null); if (safeToExit) { const string pattern = @"(?<=^Error resolving type specified in JSON ').+?(?='\. Path '\$type')"; Match match = new Regex(pattern).Match(exception.Message); if (match.Success) { string[] typeParts = match.Value.Split(','); if (typeParts.Length == 2) { string classType = typeParts[0]; bool isRequired = BusHelper.IsEventRequired(string.Format("{0}.IsRequired", classType)); if (!isRequired) { if (skippedAction != null) { skippedAction(); } return(null); } } } } throw; } string eventTypeName = @event.GetType().FullName; CorrelationIdHelper.SetCorrelationId(@event.CorrelationId); AuthenticationTokenHelper.SetAuthenticationToken(@event.AuthenticationToken); object identifyMessage = null; var identifiedEvent = @event as IEventWithIdentity <TAuthenticationToken>; if (identifiedEvent != null) { identifyMessage = string.Format(" for aggregate {0}", identifiedEvent.Rsn); } Logger.LogInfo(string.Format("An event message arrived with the {0} was of type {1}{2}.", messageId, eventTypeName, identifyMessage)); VerifySignature(signingTokenConfigurationKey, signature, "An event", messageId, eventTypeName, identifyMessage, messageBody); bool canRefresh; if (!ConfigurationManager.TryGetSetting(string.Format("{0}.ShouldRefresh", eventTypeName), out canRefresh)) { if (!ConfigurationManager.TryGetSetting(DefaultMessagesShouldRefreshConfigurationKey, out canRefresh)) { canRefresh = false; } } if (canRefresh) { if (lockRefreshAction == null) { Logger.LogWarning(string.Format("An event message arrived with the {0} was of type {1} and was destined to support lock renewal, but no action was provided.", messageId, eventTypeName)); } else { lockRefreshAction(); } } // a false response means the action wasn't handled, but didn't throw an error, so we assume, by convention, that this means it was skipped. bool?result = receiveEventHandler(@event); if (result != null && !result.Value) { if (skippedAction != null) { skippedAction(); } } return(@event); }