Middleware для отправки PartialViews по электронной почте


Ведь частичные представления отображаются все стили подставлена мой RESTful-сервис отправляет их по электронной почте. Я реализовал эту функцию с MailerMiddleware.

Это-промежуточное дампы реакция тела и использует ее как тело письма. Я передаю получателей и тему по HttpContext.Items собственность от контроллера к middleware. Он использует <system.net> элемент app.config для отправки электронных писем.

public class MailerMiddleware
{
    private readonly RequestDelegate _next;

    private readonly IEmailClient _emailClient;

    public MailerMiddleware(RequestDelegate next, IEmailClient emailClient)
    {
        _next = next;
        _emailClient = emailClient;
    }

    public async Task Invoke(HttpContext context)
    {
        if (context.Request.Method == "POST")
        {
            var bodyBackup = context.Response.Body;

            using (var memory = new MemoryStream())
            {
                context.Response.Body = memory;

                await _next(context);

                memory.Seek(0, SeekOrigin.Begin);
                using (var reader = new StreamReader(memory))
                {
                    var recipients = (string)context.Items["Recipients"];
                    var subject = (string)context.Items["Subject"];
                    var body = await reader.ReadToEndAsync();

                    memory.Seek(0, SeekOrigin.Begin);

                    var restoreBody = memory.CopyToAsync(bodyBackup);
                    var sendEmail = _emailClient.SendAsync(new Email<EmailSubject, EmailBody>
                    {
                        To = recipients,
                        Subject = new PlainTextSubject(subject),
                        Body = new ParialViewEmailBody(body),
                    });

                    await Task.WhenAll(restoreBody, sendEmail);
                }
            }
        }
        else
        {
            await _next(context);
        }
    }
}

Внутри метода действия:

[HttpPost("TestReport")]
[ActionName("TestReport")]
public IActionResult PostTestReport([FromBody] TestReportBody body)
{
    HttpContext.Items["Recipients"] = "example@email.com"; // todo use body
    HttpContext.Items["Subject"] = "Test email"; // todo use body
    return PartialView(body);
}

Это решение работает отлично, но есть все, что еще можно сделать лучше?



183
5
задан 24 февраля 2018 в 10:02 Источник Поделиться
Комментарии
2 ответа

Обычная практика с таможней промежуточного слоя при обработке тело ответа, чтобы заменить исходный поток с одним можно перемотать как только вы начнете писать на исходный поток все отправляется клиенту.

Вы должны положить тело резервной копии обратно в контекст ответа, таким образом, значения заголовка, как длина содержимого, могут быть точно вычислены. А утилизировать поток был оставлен в ответ.

public async Task Invoke(HttpContext context) {
if (context.Request.Method == "POST") {
// Hold on to original body for downstream calls
var originalBody = context.Response.Body;
// buffer the response stream in order to intercept writes in the pipeline
using (var responseBuffer = new MemoryStream()) {
// replace stream
context.Response.Body = responseBuffer;
// Call the next delegate/middleware in the pipeline
await _next(context);
// buffer now holds the response data
var recipients = (string)context.Items["Recipients"];
var subject = (string)context.Items["Subject"];
var body = string.Empty;
// rewind buffer to read data written while upstream
responseBuffer.Seek(0, SeekOrigin.Begin);
using (var reader = new StreamReader(responseBuffer)) {
body = await reader.ReadToEndAsync();
}
// rewind buffer again to copy data to original stream
responseBuffer.Seek(0, SeekOrigin.Begin);
var restoreBody = responseBuffer.CopyToAsync(originalBody);
var sendEmail = _emailClient.SendAsync(new Email<EmailSubject, EmailBody> {
To = recipients,
Subject = new PlainTextSubject(subject),
Body = new ParialViewEmailBody(body),
});
await Task.WhenAll(restoreBody, sendEmail);
// and finally, reset the stream for the
// previous delegate/middleware in the pipeline
context.Response.Body = originalBody;
}
} else {
await _next(context);
}
}

2
ответ дан 27 февраля 2018 в 03:02 Источник Поделиться

Реализация Nkosiбыл это предложения было большим шагом вперед. Таким образом, клиент может получить ответ. Однако была еще одна вещь, которая может быть значительно улучшена. Пока читала про очереди фоновых задач я поняла, что это именно то, что мне нужно, чтобы ускорить мое промежуточное, потому что отправка письма сделали его повесить за короткое время. Массового обслуживания задания в фоновый сервис позволяет промежуточного немедленно вернуться и занять свое время для письма.

Я поправил пример кода для моих нужд не с помощью регистратора. Вместо этого я разместил Debug.Fail внутри catch статья (и заменил пару имен).

public interface IWorkItemQueue
{
void Enqueue(Func<CancellationToken, Task> workItem);

Task<Func<CancellationToken, Task>> DequeueAsync(CancellationToken cancellationToken);
}

public class WorkItemQueue : IWorkItemQueue
{
private readonly ConcurrentQueue<Func<CancellationToken, Task>> _workItemQueue = new ConcurrentQueue<Func<CancellationToken, Task>>();

private readonly SemaphoreSlim _signal = new SemaphoreSlim(0);

public void Enqueue(Func<CancellationToken, Task> workItem)
{
if (workItem == null) { throw new ArgumentNullException(nameof(workItem)); }

_workItemQueue.Enqueue(workItem);
_signal.Release();
}

public async Task<Func<CancellationToken, Task>> DequeueAsync(CancellationToken cancellationToken)
{
await _signal.WaitAsync(cancellationToken);
_workItemQueue.TryDequeue(out var workItem);

return workItem;
}
}

public class WorkItemQueueService : IHostedService
{
private readonly IWorkItemQueue _workItemQueue;

private readonly CancellationTokenSource _shutdown = new CancellationTokenSource();

private Task _backgroundTask;

public WorkItemQueueService(IWorkItemQueue workItemQueue)
{
_workItemQueue = workItemQueue;
}

#region IHostedService

public Task StartAsync(CancellationToken cancellationToken)
{
// ReSharper disable once MethodSupportsCancellation - this task is not supposted to be cancelled until shutdown
_backgroundTask = Task.Run(BackgroundProceessing);

return Task.CompletedTask;
}

public Task StopAsync(CancellationToken cancellationToken)
{
_shutdown.Cancel();
return Task.WhenAny(_backgroundTask, Task.Delay(Timeout.Infinite, cancellationToken));
}

#endregion

public void Enqueue(Func<CancellationToken, Task> workItem)
{
_workItemQueue.Enqueue(workItem);
}

private async Task BackgroundProceessing()
{
while (!_shutdown.IsCancellationRequested)
{
var workItem = await _workItemQueue.DequeueAsync(_shutdown.Token);

try
{
await workItem(_shutdown.Token);
}
catch (Exception)
{
Debug.Fail("Work item should handle its own exceptions.");
}
}
}
}


Обновленный MailerMiddleware теперь ставит задачи, отправка писем на эту очередь, чтобы служба смогла обработать их позже, а также обрабатывает свои собственные исключения.

public class MailerMiddleware
{
private readonly RequestDelegate _next;

private readonly ILogger _logger;

private readonly IWorkItemQueue _workItemQueue;

private readonly IEmailClient _emailClient;

public MailerMiddleware(RequestDelegate next, ILoggerFactory loggerFactory, IWorkItemQueue workItemQueue, IEmailClient emailClient)
{
_next = next;
_logger = loggerFactory.CreateLogger<MailerMiddleware>();
_workItemQueue = workItemQueue;
_emailClient = emailClient;
}

public async Task Invoke(HttpContext context)
{
var bodyBackup = context.Response.Body;

using (var memory = new MemoryStream())
{
context.Response.Body = memory;

await _next(context);

memory.Seek(0, SeekOrigin.Begin);
using (var reader = new StreamReader(memory))
{
var body = await reader.ReadToEndAsync();

var email = context.Email();
if (!(email.To is null || email.Subject is null))
{
_workItemQueue.Enqueue(async cancellationToken =>
{
try
{
await _emailClient.SendAsync(new Email<EmailSubject, EmailBody>
{
To = email.To,
Subject = new PlainTextSubject(email.Subject),
Body = new ParialViewEmailBody(body),
});
}
catch (Exception ex)
{
_logger.Log(Abstraction.Layer.Network().Action().Failed(nameof(IEmailClient.SendAsync)), ex);
}
});
}

// Restore Response.Body
memory.Seek(0, SeekOrigin.Begin);
await memory.CopyToAsync(bodyBackup);
context.Response.Body = bodyBackup;
}
}
}

private class ParialViewEmailBody : EmailBody
{
private readonly string _body;

public ParialViewEmailBody(string body)
{
_body = body;
IsHtml = true;
Encoding = System.Text.Encoding.UTF8;
}
public override string ToString()
{
return _body;
}
}
}

АСП.Нетто-ядро-это круто ;-)

3
ответ дан 4 марта 2018 в 10:03 Источник Поделиться