public async Task <IActionResult> Post(
            [FromForm] PostEmailRequest args,
            CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();

            var applicationId = User.GetApplicationId();

            // the token will be used by both the processing engine and the
            // client to track this request from start to finish
            var token = EmailQueueToken.Create(applicationId);

            _logger.LogInformation("Sending email using token {0}", token);

            if (ModelState.IsValid)
            {
                // create an object that we then store as a BLOB (emails run
                // the risk of being too large to fit in the queue, so BLOB
                // storage is the best option)
                var message = BuildMessage(args);
                message.ApplicationId = applicationId;
                await _blobStore.AddAsync(token, message, cancellationToken);

                // now we can let the back-end processor know that there's a
                // new message that it has to process
                await _sender.SendAsync(token, cancellationToken);

                // log that we queued the message for processing
                await _logWriter.TryLogProcessAttemptAsync(token, 0, ProcessingStatus.Pending, token.TimeStamp, token.TimeStamp, null, cancellationToken);

                // all done - let the client know that we've accepted their
                // request, and what the tracking token is
                Response.StatusCode = (int)HttpStatusCode.Accepted;
                return(Json(new
                {
                    Token = token.EncodeString()
                }));
            }
            else
            {
                return(BadRequest(ModelState));
            }
        }
Beispiel #2
0
        public async Task ProcessMessage(TMessage message, CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();

            _logger.LogTrace("Processing message {0} on try {1}", message.Token, message.DequeueCount);

            // start timing the process for logging purposes
            var start = DateTime.UtcNow;
            var sw    = new Stopwatch();

            sw.Start();

            // load the args from the blob store; it's unlikely that they won't be found,
            // but to be safe we'll check for null (the args are in the blob store because
            // there's a chance that they'll exceed the 64KB limit on queue messages due
            // to including data and HTML)
            var args = await _blobStore.GetAsync(message.Token, cancellationToken);

            if (args != null)
            {
                try
                {
                    // do the actual sending and return information about the email that
                    // was sent so that we can log it; any failures will result in an
                    // exception that will be caught and handled in the catch block
                    var result = await TrySendEmailAsync(args);

                    result.DequeueCount = message.DequeueCount;

                    // after successful processing, the most important thing to do is
                    // to immediately remove this message from the queue; this will prevent
                    // retrieves if any of the subsequent calls fail (avoiding duplicate
                    // messages is more important than correct logging)
                    await _receiver.CompleteAsync(message, cancellationToken);

                    await _blobStore.RemoveAsync(message.Token, cancellationToken);

                    // stop timing the process
                    sw.Stop();

                    // now we can audit the event
                    await _logWriter.TryLogProcessAttemptAsync(message.Token, message.DequeueCount, ProcessingStatus.Succeeded, start, start.Add(sw.Elapsed), null, cancellationToken);

                    await _logWriter.TryLogSentMessageAsync(message.Token, result, cancellationToken);

                    _logger.LogInformation("Successfully sent email for message {0} on try {1}", message.Token, message.DequeueCount);
                }
                catch (Exception ex)
                {
                    sw.Stop();

                    if (message.DequeueCount >= MaxDequeue)
                    {
                        // failed, and reached the maximum retry count; move the message to
                        // the poison queue and move the blob data to the bin of failure
                        await _receiver.MoveToPoisonQueueAsync(message, cancellationToken);

                        await _blobStore.MoveToPoisonStoreAsync(message.Token, cancellationToken);

                        await _logWriter.TryLogProcessAttemptAsync(message.Token, message.DequeueCount, ProcessingStatus.FailedAbandoned, start, start.Add(sw.Elapsed), ex.GetBaseException().Message, cancellationToken);

                        _logger.LogError("Failed to send email for message {0} after {1} tries, giving up\n{2}", message.Token, message.DequeueCount, ex);
                    }
                    else
                    {
                        // failed, but we'll let it retry to see if that fixes it
                        await _logWriter.TryLogProcessAttemptAsync(message.Token, message.DequeueCount, ProcessingStatus.FailedRequeued, start, start.Add(sw.Elapsed), ex.GetBaseException().Message, cancellationToken);

                        _logger.LogWarning("Failed to send email for message {0} after {1} tries\n{2}", message.Token, message.DequeueCount, ex);
                    }
                }
            }
            else
            {
                // if we couldn't find a matching blob, then raise an error and remove
                // the message from the queue
                _logger.LogError("Could not find message params for {0}", message.Token);
                await _receiver.CompleteAsync(message, cancellationToken);
            }
        }