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, EmailCommandToSend 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, scheduledTime : command?.ScheduledTime, 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(); }
/// <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(); }