Beispiel #1
0
        /// <summary>
        /// Queues the notifications in the database and if the database table is clearn queues them immediately for processing.
        /// If the database contains stale notifications, then do not queue the new ones immediately. Instead wait for <see cref="SmsPollingJob"/>
        /// to pick them out in order and schedule them, this prevent notifications being dispatched grossly out of order
        /// </summary>
        public async Task Enqueue(
            int tenantId,
            List <Email> emails                       = null,
            List <SmsMessage> smsMessages             = null,
            List <PushNotification> pushNotifications = null,
            CancellationToken cancellation            = default)
        {
            // (1) Map notifications to Entities and validate them
            // Email
            emails ??= new List <Email>();
            var validEmails   = new List <Email>(emails.Count);
            var emailEntities = new List <EmailForSave>(emails.Count);

            foreach (var email in emails)
            {
                var emailEntity = ToEntity(email);
                emailEntities.Add(emailEntity);

                var error = EmailValidation.Validate(email);
                if (error != null)
                {
                    emailEntity.State        = EmailState.ValidationFailed;
                    emailEntity.ErrorMessage = error;
                }
                else
                {
                    validEmails.Add(email);
                }
            }

            // SMS
            smsMessages ??= new List <SmsMessage>();
            var validSmsMessages = new List <SmsMessage>(smsMessages.Count);
            var smsEntities      = new List <SmsMessageForSave>(smsMessages.Count);

            foreach (var sms in smsMessages)
            {
                var smsEntity = ToEntity(sms);
                smsEntities.Add(smsEntity);

                var error = SmsValidation.Validate(sms);
                if (error != null)
                {
                    smsEntity.State        = SmsState.ValidationFailed;
                    smsEntity.ErrorMessage = error;
                }
                else
                {
                    validSmsMessages.Add(sms);
                }
            }

            // Push
            pushNotifications ??= new List <PushNotification>();
            var validPushNotifications = new List <PushNotification>(pushNotifications.Count);
            var pushEntities           = new List <PushNotificationForSave>(pushNotifications.Count);

            // TODO

            // Start a serializable transaction
            using var trx = new TransactionScope(TransactionScopeOption.RequiresNew, new TransactionOptions { IsolationLevel = IsolationLevel.Serializable }, TransactionScopeAsyncFlowOption.Enabled);

            // If the table already contains notifications that are 90% expired, do not queue the new notifications
            int expiryInSeconds = _options.PendingNotificationExpiryInSeconds * 9 / 10;

            // (2) Call the stored procedure
            // Persist the notifications in the database, the returned booleans will tell us which notifications we can queue immediately
            var(queueEmails, queueSmsMessages, queuePushNotifications) = await _repo.Notifications_Enqueue(
                tenantId, expiryInSeconds, emailEntities, smsEntities, pushEntities, cancellation);

            // (3) Map the Ids back to the DTOs and queue valid notifications
            // Email
            if (queueEmails && validEmails.Any())
            {
                // Map the Ids
                for (int i = 0; i < emailEntities.Count; i++)
                {
                    var entity = emailEntities[i];
                    var dto    = emails[i];
                    dto.EmailId  = entity.Id;
                    dto.TenantId = tenantId;
                }

                // Queue (Emails are queued in bulk unlike SMS and Push)
                _emailQueue.QueueBackgroundWorkItem(validEmails);
            }

            // SMS
            if (queueSmsMessages && validSmsMessages.Any())
            {
                // Map the Ids
                for (int i = 0; i < smsEntities.Count; i++)
                {
                    var smsEntity = smsEntities[i];
                    var sms       = smsMessages[i];
                    sms.MessageId = smsEntity.Id;
                    sms.TenantId  = tenantId;
                }

                // Queue
                _smsQueue.QueueAllBackgroundWorkItems(validSmsMessages);
            }

            // Push
            if (queuePushNotifications && validPushNotifications.Any())
            {
                // Map the Ids
                for (int i = 0; i < smsEntities.Count; i++)
                {
                    var entity = pushEntities[i];
                    var dto    = pushNotifications[i];
                    dto.PushId   = entity.Id;
                    dto.TenantId = tenantId;
                }

                // Queue
                _pushQueue.QueueAllBackgroundWorkItems(validPushNotifications);
            }

            trx.Complete();
        }
        public static bool PushEnabled => false; // TODO

        /// <summary>
        /// Queues the notifications in the database and if the database table was clear it queues them immediately for processing.<br/>
        /// If the database contains stale notifications, then do not queue the new ones immediately. Instead wait for <see cref="SmsPollingJob"/>
        /// to pick them out in order and schedule them, this prevent notifications being dispatched grossly out of order.
        /// </summary>
        public async Task Enqueue(
            int tenantId,
            List <EmailToSend> emails           = null,
            List <SmsToSend> smsMessages        = null,
            List <PushToSend> pushNotifications = null,
            NotificationCommandToSend command   = null,
            CancellationToken cancellation      = default)
        {
            // (1) Map notifications to Entities and validate them
            // Email
            emails ??= new List <EmailToSend>();
            if (emails.Count > 0 && !EmailEnabled)
            {
                // Developer mistake
                throw new InvalidOperationException("Attempt to Enqueue emails while email is disabled in this installation.");
            }

            var validEmails   = new List <EmailToSend>(emails.Count);
            var emailEntities = new List <EmailForSave>(emails.Count);
            var blobs         = new List <(string name, byte[] content)>();

            foreach (var email in emails)
            {
                var(emailEntity, emailBlobs) = ToEntity(email);
                emailEntities.Add(emailEntity);
                blobs.AddRange(emailBlobs);

                var error = EmailValidation.Validate(email);
                if (error != null)
                {
                    emailEntity.State        = EmailState.ValidationFailed;
                    emailEntity.ErrorMessage = error;

                    // The following ensures it will fit in the table
                    emailEntity.To      = emailEntity.To?.Truncate(EmailValidation.MaximumEmailAddressLength);
                    emailEntity.Cc      = emailEntity.Cc?.Truncate(EmailValidation.MaximumEmailAddressLength);
                    emailEntity.Bcc     = emailEntity.Bcc?.Truncate(EmailValidation.MaximumEmailAddressLength);
                    emailEntity.Subject = emailEntity.Subject?.Truncate(EmailValidation.MaximumSubjectLength);
                    foreach (var att in emailEntity.Attachments)
                    {
                        if (string.IsNullOrWhiteSpace(att.Name))
                        {
                            att.Name = "(Missing Name)";
                        }
                        else
                        {
                            att.Name = att.Name?.Truncate(EmailValidation.MaximumAttchmentNameLength);
                        }
                    }
                }
                else
                {
                    validEmails.Add(email);
                }
            }

            // SMS
            smsMessages ??= new List <SmsToSend>();
            if (smsMessages.Count > 0 && !SmsEnabled)
            {
                // Developer mistake
                throw new InvalidOperationException("Attempt to Enqueue SMS messages while SMS is disabled in this installation.");
            }
            var validSmsMessages = new List <SmsToSend>(smsMessages.Count);
            var smsEntities      = new List <MessageForSave>(smsMessages.Count);

            foreach (var sms in smsMessages)
            {
                var smsEntity = ToEntity(sms);
                smsEntities.Add(smsEntity);

                var error = SmsValidation.Validate(sms);
                if (error != null)
                {
                    smsEntity.State        = MessageState.ValidationFailed;
                    smsEntity.ErrorMessage = error;
                }
                else
                {
                    validSmsMessages.Add(sms);
                }
            }

            // Push
            pushNotifications ??= new List <PushToSend>();
            if (pushNotifications.Count > 0 && !PushEnabled)
            {
                // Developer mistake
                throw new InvalidOperationException("Attempt to Enqueue Push notifications while Push is disabled in this installation.");
            }
            var validPushNotifications = new List <PushToSend>(pushNotifications.Count);
            var pushEntities           = new List <PushNotificationForSave>(pushNotifications.Count);

            // TODO

            // Start a serializable transaction
            using var trx = TransactionFactory.Serializable(TransactionScopeOption.RequiresNew);

            // If the table already contains notifications that are 90% expired, do not queue the new notifications
            int expiryInSeconds = _options.PendingNotificationExpiryInSeconds * 9 / 10;

            // (2) Call the stored procedure
            // Persist the notifications in the database, the returned booleans will tell us which notifications we can queue immediately
            var repo = _repoFactory.GetRepository(tenantId);

            var(queueEmails, queueSmsMessages, queuePushNotifications, emailCommandId, messageCommandId) = await repo.Notifications_Enqueue(
                expiryInSeconds : expiryInSeconds,
                emails : emailEntities,
                messages : smsEntities,
                pushes : pushEntities,
                templateId : command?.TemplateId,
                entityId : command?.EntityId,
                caption : command?.Caption,
                createdbyId : command?.CreatedById,
                cancellation : cancellation);

            await _blobService.SaveBlobsAsync(tenantId, blobs);

            // (3) Map the Ids back to the DTOs and queue valid notifications
            // Email
            if (queueEmails && validEmails.Any())
            {
                // Map the Ids
                for (int i = 0; i < emailEntities.Count; i++)
                {
                    var entity = emailEntities[i];
                    var dto    = emails[i];
                    dto.EmailId  = entity.Id;
                    dto.TenantId = tenantId;
                }

                // Queue (Emails are queued in bulk unlike SMS and Push)
                _emailQueue.QueueBackgroundWorkItem(validEmails);
            }

            // SMS
            if (queueSmsMessages && validSmsMessages.Any())
            {
                // Map the Ids
                for (int i = 0; i < smsEntities.Count; i++)
                {
                    var smsEntity = smsEntities[i];
                    var sms       = smsMessages[i];
                    sms.MessageId = smsEntity.Id;
                    sms.TenantId  = tenantId;
                }

                // Queue
                _smsQueue.QueueAllBackgroundWorkItems(validSmsMessages);
            }

            // Push
            if (queuePushNotifications && validPushNotifications.Any())
            {
                // Map the Ids
                for (int i = 0; i < smsEntities.Count; i++)
                {
                    var entity = pushEntities[i];
                    var dto    = pushNotifications[i];
                    dto.PushId   = entity.Id;
                    dto.TenantId = tenantId;
                }

                // Queue
                _pushQueue.QueueAllBackgroundWorkItems(validPushNotifications);
            }

            if (command != null)
            {
                command.EmailCommandId   = emailCommandId;
                command.MessageCommandId = messageCommandId;
            }

            trx.Complete();
        }