public void ExceptionExclusion()
        {
            RetryHandler retryHandler = new RetryHandler(5, 15, 7, false);

            retryHandler
            .ExcludeForRetry <TimeoutException>()
            .ExcludeForRetry <NullReferenceException>();

            Assert.False(retryHandler.IsForRetry(new TimeoutException()));
            Assert.False(retryHandler.IsForRetry(new NullReferenceException()));
            Assert.True(retryHandler.IsForRetry(new ArgumentNullException()));
        }
        public IExcludeForRetry Subscribe <TSubscriber, TEvent>(string tag, AbstractValidator <TEvent> validator, string retryCron, ushort?retryLimit) where TSubscriber : IEventHandler <TEvent> where TEvent : class
        {
            //naming validation
            if (tag != null)
            {
                tag.CheckNaming();
            }

            //subscriber registration in a container
            Injection.AddTransient(typeof(TSubscriber));

            //retry handler
            IRetry retryHandler = new RetryHandler(0, 0, 0, false);

            //creates queue and exchanges
            string directory         = typeof(TEvent).GetTypeInfo().GetCustomAttribute <BusNamespace>().Directory;
            string subdirectory      = typeof(TEvent).GetTypeInfo().GetCustomAttribute <BusNamespace>().Subdirectory;
            string exchange          = "event_" + directory.ToLower() + "_" + subdirectory.ToLower();
            string routingKey        = typeof(TEvent).Name.ToLower() + "." + (tag != null ? tag.ToLower() : "*");
            string restoreRoutingKey = _appId.ToLower() + "." + directory.ToLower() + "." + subdirectory.ToLower() + "." + typeof(TEvent).Name.ToLower() + (tag != null ? ("." + tag.ToLower()) : "");
            string queue             = _appId.ToLower() + "-event-" + directory.ToLower() + "-" + subdirectory.ToLower() + "-" + typeof(TEvent).Name.ToLower() + (tag != null ? ("-" + tag.ToLower()) : "");

            _eventHandlerChannel.ExchangeDeclare(exchange, ExchangeType.Topic, true, false);
            _eventHandlerChannel.ExchangeDeclare(DEAD_LETTER_QUEUE_EXCHANGE, ExchangeType.Direct, true, false);
            _eventHandlerChannel.QueueDeclare(queue, true, false, false, new Dictionary <string, object> {
                { "x-queue-mode", "lazy" }
            });
            _eventHandlerChannel.QueueBind(queue, exchange, routingKey);
            _eventHandlerChannel.QueueBind(queue, DEAD_LETTER_QUEUE_EXCHANGE, restoreRoutingKey);

            //creates dead letter queue
            string deadLetterQueue = queue + "-dlq";
            string dlqRoutingKey   = _appId.ToLower() + "." + directory.ToLower() + "." + subdirectory.ToLower() + "." + typeof(TEvent).Name.ToLower() + (tag != null ? ("." + tag.ToLower()) : "") + ".dlq";

            _deadLetterQueueChannel.QueueDeclare(deadLetterQueue, true, false, false, new Dictionary <string, object> {
                { "x-queue-mode", "lazy" }
            });
            _deadLetterQueueChannel.QueueBind(deadLetterQueue, DEAD_LETTER_QUEUE_EXCHANGE, dlqRoutingKey);

            //message listener
            EventingBasicConsumer consumer = new EventingBasicConsumer(_eventHandlerChannel);

            consumer.Received += (obj, args) =>
            {
                Task.Factory.StartNew(async() =>
                {
                    string messageBody = Encoding.UTF8.GetString(args.Body.ToArray());

                    try
                    {
                        TEvent messageEvent = _jsonConverter.Deserialize <TEvent>(messageBody);
                        if (validator != null)
                        {
                            await validator.ValidateAndThrowAsync(messageEvent, (directory + "." + subdirectory + "." + typeof(TEvent).Name + " is not valid"));
                        }

                        using (IServiceScope serviceScope = _serviceProvider.CreateScope())
                            using (ITraceScope traceScope = new TraceScope("Handle-" + directory + "." + subdirectory + "." + typeof(TEvent).Name, _tracer))
                            {
                                TSubscriber subscriber = serviceScope.ServiceProvider.GetService <TSubscriber>();
                                subscriber.Bus         = this;
                                subscriber.TraceScope  = traceScope;
                                traceScope.Attributes.Add("AppId", _appId);
                                traceScope.Attributes.Add("MessageId", args.BasicProperties.MessageId);
                                await subscriber.HandleAsync(messageEvent);
                            }

                        _logger.Send(new MessageDetail
                        {
                            Id            = args.BasicProperties.MessageId,
                            CorrelationId = null,
                            Type          = MessageType.Event,
                            Directory     = directory,
                            Subdirectory  = subdirectory,
                            Name          = typeof(TEvent).Name,
                            Body          = messageBody,
                            AppId         = args.BasicProperties.AppId,
                            Exception     = null,
                            ToRetry       = false
                        });
                    }
                    catch (Exception exception)
                    {
                        bool toRetry      = false;
                        ushort retryIndex = ushort.Parse(Encoding.UTF8.GetString((byte[])args.BasicProperties.Headers["RetryIndex"]));
                        if (retryHandler.IsForRetry(exception) && !string.IsNullOrEmpty(retryCron) && retryLimit != null && retryIndex < retryLimit)
                        {
                            IBasicProperties properties = _deadLetterQueueChannel.CreateBasicProperties();
                            properties.MessageId        = args.BasicProperties.MessageId;
                            properties.AppId            = args.BasicProperties.AppId;
                            properties.Headers          = new Dictionary <string, object>();
                            properties.Headers.Add("RetryIndex", (++retryIndex).ToString());
                            properties.Persistent = true;
                            _deadLetterQueueChannel.BasicPublish(DEAD_LETTER_QUEUE_EXCHANGE, dlqRoutingKey, properties, args.Body);

                            toRetry = true;
                        }

                        _logger.Send(new MessageDetail
                        {
                            Id            = args.BasicProperties.MessageId,
                            CorrelationId = null,
                            Type          = MessageType.Event,
                            Directory     = directory,
                            Subdirectory  = subdirectory,
                            Name          = typeof(TEvent).Name,
                            Body          = messageBody,
                            AppId         = args.BasicProperties.AppId,
                            Exception     = exception,
                            ToRetry       = toRetry
                        });
                    }

                    //acknowledgment
                    _eventHandlerChannel.BasicAck(args.DeliveryTag, false);
                },
                                      _cancellationTokenSource.Token,
                                      TaskCreationOptions.DenyChildAttach,
                                      TaskScheduler.Default);
            };

            _toActivateConsumers.Add(new Tuple <IModel, string, EventingBasicConsumer>(_eventHandlerChannel, queue, consumer));

            //message restore
            Task.Factory.StartNew(async() =>
            {
                while (true)
                {
                    await retryCron.CronDelay();

                    List <BasicGetResult> bgrs = new List <BasicGetResult>();
                    for (int i = 0; i < DEAD_LETTER_QUEUE_RECOVERY_LIMIT; i++)
                    {
                        BasicGetResult bgr = _deadLetterQueueChannel.BasicGet(deadLetterQueue, false);
                        if (bgr == null)
                        {
                            break;
                        }

                        bgrs.Add(bgr);
                    }

                    foreach (BasicGetResult bgr in bgrs)
                    {
                        _deadLetterQueueChannel.BasicPublish(DEAD_LETTER_QUEUE_EXCHANGE, restoreRoutingKey, bgr.BasicProperties, bgr.Body);
                        _deadLetterQueueChannel.BasicAck(bgr.DeliveryTag, false);
                    }
                }
            },
                                  _cancellationTokenSource.Token,
                                  TaskCreationOptions.DenyChildAttach,
                                  _deadLetterQueueTaskScheduler);

            return(retryHandler);
        }