Middleware for sending PartialViews via email
Clash Royale CLAN TAG#URR8PPP
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;
up vote
4
down vote
favorite
After all partial views are rendered and all styles are inlined my RESTful service is sending them via email. I implemented this feature with a MailerMiddleware
.
This middleware dumps the response body and uses it as the body of the email. I pass the recipients and the subject via the HttpContext.Items
property from the controller to the middleware. It uses the <system.net>
element in the app.config
for sending emails.
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);
Inside the action method:
[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);
This solution is working great but is there anything that could still be done better?
c# email asp.net-core
add a comment |Â
up vote
4
down vote
favorite
After all partial views are rendered and all styles are inlined my RESTful service is sending them via email. I implemented this feature with a MailerMiddleware
.
This middleware dumps the response body and uses it as the body of the email. I pass the recipients and the subject via the HttpContext.Items
property from the controller to the middleware. It uses the <system.net>
element in the app.config
for sending emails.
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);
Inside the action method:
[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);
This solution is working great but is there anything that could still be done better?
c# email asp.net-core
add a comment |Â
up vote
4
down vote
favorite
up vote
4
down vote
favorite
After all partial views are rendered and all styles are inlined my RESTful service is sending them via email. I implemented this feature with a MailerMiddleware
.
This middleware dumps the response body and uses it as the body of the email. I pass the recipients and the subject via the HttpContext.Items
property from the controller to the middleware. It uses the <system.net>
element in the app.config
for sending emails.
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);
Inside the action method:
[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);
This solution is working great but is there anything that could still be done better?
c# email asp.net-core
After all partial views are rendered and all styles are inlined my RESTful service is sending them via email. I implemented this feature with a MailerMiddleware
.
This middleware dumps the response body and uses it as the body of the email. I pass the recipients and the subject via the HttpContext.Items
property from the controller to the middleware. It uses the <system.net>
element in the app.config
for sending emails.
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);
Inside the action method:
[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);
This solution is working great but is there anything that could still be done better?
c# email asp.net-core
edited Mar 4 at 10:04
asked Feb 24 at 22:26
t3chb0t
32.1k54195
32.1k54195
add a comment |Â
add a comment |Â
2 Answers
2
active
oldest
votes
up vote
1
down vote
accepted
Common practice with custom middleware when manipulating the response body is to replace original stream with one you can rewind as once you start writing to the original stream everything is sent to the client.
You should put the backup body back into the context response so header values like content length can be calculated accurately. A disposed stream was being left in the response.
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);
Now I see my mistake. Making a bakup of the original stream but not restoring it. Also your variable names are much better. Thanks! ;-)
â t3chb0t
Feb 27 at 5:03
I've posted an improved version.
â t3chb0t
Mar 4 at 10:44
add a comment |Â
up vote
2
down vote
Implementing Nkosi's suggestions was a great step forward. This way the client can also recieve the response. However, there was one more thing that could be greatly improved. While reading about Queued background tasks I realized that this is exactly what I need to speed up my middleware becasue sending emails made it hang for a short time. Queueing the task in a background service allows the middleware to return immediately and take its time for emails.
I adjusted the example code for my needs by not using a logger here. Instead I placed the Debug.Fail
inside the catch
clause (and changed a couple of names).
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.");
The updated MailerMiddleware
now enqueues tasks sending emails on that queue so that the service can handle them later and also handles its own exceptions.
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
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;
ASP.NET-Core is really cool ;-)
This is a very good improvement over the initial suggestion provided. Queuing the task was well thought out and would definitely improve the response time. I like this code.
â Nkosi
Mar 4 at 12:39
Curious though. Why useContinueWith
for theEnqueue
work item function instead of atry catch
wrapping anawait
?Enqueue
takes a function returning a task which would mean it can be awaited.
â Nkosi
Mar 4 at 12:45
@Nkosi I guess it's because howTask
s work (still a little bit confusing to me). I initially had atry/catch
aroundSendAsync
there but this did not work. A test exception was risen in the queue-processor and theDebug.Fail
was executed. The exception got out of thetry/catch
and ended up behing handled byDequeueAsync
.
â t3chb0t
Mar 4 at 12:49
1
@Nkosi ok, I got it working withoutContinueWith
(and updated the code). My mistake was to return theTask
forSendAsync
instead of awaiting it.
â t3chb0t
Mar 4 at 15:47
1
Here is a nice recent video on async await that I believe you will find interesting. Youtube - On.Net async/await best practices
â Nkosi
Mar 4 at 15:52
 |Â
show 1 more comment
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
1
down vote
accepted
Common practice with custom middleware when manipulating the response body is to replace original stream with one you can rewind as once you start writing to the original stream everything is sent to the client.
You should put the backup body back into the context response so header values like content length can be calculated accurately. A disposed stream was being left in the response.
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);
Now I see my mistake. Making a bakup of the original stream but not restoring it. Also your variable names are much better. Thanks! ;-)
â t3chb0t
Feb 27 at 5:03
I've posted an improved version.
â t3chb0t
Mar 4 at 10:44
add a comment |Â
up vote
1
down vote
accepted
Common practice with custom middleware when manipulating the response body is to replace original stream with one you can rewind as once you start writing to the original stream everything is sent to the client.
You should put the backup body back into the context response so header values like content length can be calculated accurately. A disposed stream was being left in the response.
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);
Now I see my mistake. Making a bakup of the original stream but not restoring it. Also your variable names are much better. Thanks! ;-)
â t3chb0t
Feb 27 at 5:03
I've posted an improved version.
â t3chb0t
Mar 4 at 10:44
add a comment |Â
up vote
1
down vote
accepted
up vote
1
down vote
accepted
Common practice with custom middleware when manipulating the response body is to replace original stream with one you can rewind as once you start writing to the original stream everything is sent to the client.
You should put the backup body back into the context response so header values like content length can be calculated accurately. A disposed stream was being left in the response.
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);
Common practice with custom middleware when manipulating the response body is to replace original stream with one you can rewind as once you start writing to the original stream everything is sent to the client.
You should put the backup body back into the context response so header values like content length can be calculated accurately. A disposed stream was being left in the response.
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);
edited Feb 27 at 3:54
answered Feb 27 at 3:00
Nkosi
1,870619
1,870619
Now I see my mistake. Making a bakup of the original stream but not restoring it. Also your variable names are much better. Thanks! ;-)
â t3chb0t
Feb 27 at 5:03
I've posted an improved version.
â t3chb0t
Mar 4 at 10:44
add a comment |Â
Now I see my mistake. Making a bakup of the original stream but not restoring it. Also your variable names are much better. Thanks! ;-)
â t3chb0t
Feb 27 at 5:03
I've posted an improved version.
â t3chb0t
Mar 4 at 10:44
Now I see my mistake. Making a bakup of the original stream but not restoring it. Also your variable names are much better. Thanks! ;-)
â t3chb0t
Feb 27 at 5:03
Now I see my mistake. Making a bakup of the original stream but not restoring it. Also your variable names are much better. Thanks! ;-)
â t3chb0t
Feb 27 at 5:03
I've posted an improved version.
â t3chb0t
Mar 4 at 10:44
I've posted an improved version.
â t3chb0t
Mar 4 at 10:44
add a comment |Â
up vote
2
down vote
Implementing Nkosi's suggestions was a great step forward. This way the client can also recieve the response. However, there was one more thing that could be greatly improved. While reading about Queued background tasks I realized that this is exactly what I need to speed up my middleware becasue sending emails made it hang for a short time. Queueing the task in a background service allows the middleware to return immediately and take its time for emails.
I adjusted the example code for my needs by not using a logger here. Instead I placed the Debug.Fail
inside the catch
clause (and changed a couple of names).
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.");
The updated MailerMiddleware
now enqueues tasks sending emails on that queue so that the service can handle them later and also handles its own exceptions.
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
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;
ASP.NET-Core is really cool ;-)
This is a very good improvement over the initial suggestion provided. Queuing the task was well thought out and would definitely improve the response time. I like this code.
â Nkosi
Mar 4 at 12:39
Curious though. Why useContinueWith
for theEnqueue
work item function instead of atry catch
wrapping anawait
?Enqueue
takes a function returning a task which would mean it can be awaited.
â Nkosi
Mar 4 at 12:45
@Nkosi I guess it's because howTask
s work (still a little bit confusing to me). I initially had atry/catch
aroundSendAsync
there but this did not work. A test exception was risen in the queue-processor and theDebug.Fail
was executed. The exception got out of thetry/catch
and ended up behing handled byDequeueAsync
.
â t3chb0t
Mar 4 at 12:49
1
@Nkosi ok, I got it working withoutContinueWith
(and updated the code). My mistake was to return theTask
forSendAsync
instead of awaiting it.
â t3chb0t
Mar 4 at 15:47
1
Here is a nice recent video on async await that I believe you will find interesting. Youtube - On.Net async/await best practices
â Nkosi
Mar 4 at 15:52
 |Â
show 1 more comment
up vote
2
down vote
Implementing Nkosi's suggestions was a great step forward. This way the client can also recieve the response. However, there was one more thing that could be greatly improved. While reading about Queued background tasks I realized that this is exactly what I need to speed up my middleware becasue sending emails made it hang for a short time. Queueing the task in a background service allows the middleware to return immediately and take its time for emails.
I adjusted the example code for my needs by not using a logger here. Instead I placed the Debug.Fail
inside the catch
clause (and changed a couple of names).
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.");
The updated MailerMiddleware
now enqueues tasks sending emails on that queue so that the service can handle them later and also handles its own exceptions.
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
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;
ASP.NET-Core is really cool ;-)
This is a very good improvement over the initial suggestion provided. Queuing the task was well thought out and would definitely improve the response time. I like this code.
â Nkosi
Mar 4 at 12:39
Curious though. Why useContinueWith
for theEnqueue
work item function instead of atry catch
wrapping anawait
?Enqueue
takes a function returning a task which would mean it can be awaited.
â Nkosi
Mar 4 at 12:45
@Nkosi I guess it's because howTask
s work (still a little bit confusing to me). I initially had atry/catch
aroundSendAsync
there but this did not work. A test exception was risen in the queue-processor and theDebug.Fail
was executed. The exception got out of thetry/catch
and ended up behing handled byDequeueAsync
.
â t3chb0t
Mar 4 at 12:49
1
@Nkosi ok, I got it working withoutContinueWith
(and updated the code). My mistake was to return theTask
forSendAsync
instead of awaiting it.
â t3chb0t
Mar 4 at 15:47
1
Here is a nice recent video on async await that I believe you will find interesting. Youtube - On.Net async/await best practices
â Nkosi
Mar 4 at 15:52
 |Â
show 1 more comment
up vote
2
down vote
up vote
2
down vote
Implementing Nkosi's suggestions was a great step forward. This way the client can also recieve the response. However, there was one more thing that could be greatly improved. While reading about Queued background tasks I realized that this is exactly what I need to speed up my middleware becasue sending emails made it hang for a short time. Queueing the task in a background service allows the middleware to return immediately and take its time for emails.
I adjusted the example code for my needs by not using a logger here. Instead I placed the Debug.Fail
inside the catch
clause (and changed a couple of names).
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.");
The updated MailerMiddleware
now enqueues tasks sending emails on that queue so that the service can handle them later and also handles its own exceptions.
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
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;
ASP.NET-Core is really cool ;-)
Implementing Nkosi's suggestions was a great step forward. This way the client can also recieve the response. However, there was one more thing that could be greatly improved. While reading about Queued background tasks I realized that this is exactly what I need to speed up my middleware becasue sending emails made it hang for a short time. Queueing the task in a background service allows the middleware to return immediately and take its time for emails.
I adjusted the example code for my needs by not using a logger here. Instead I placed the Debug.Fail
inside the catch
clause (and changed a couple of names).
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.");
The updated MailerMiddleware
now enqueues tasks sending emails on that queue so that the service can handle them later and also handles its own exceptions.
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
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;
ASP.NET-Core is really cool ;-)
edited Mar 4 at 15:46
answered Mar 4 at 10:02
t3chb0t
32.1k54195
32.1k54195
This is a very good improvement over the initial suggestion provided. Queuing the task was well thought out and would definitely improve the response time. I like this code.
â Nkosi
Mar 4 at 12:39
Curious though. Why useContinueWith
for theEnqueue
work item function instead of atry catch
wrapping anawait
?Enqueue
takes a function returning a task which would mean it can be awaited.
â Nkosi
Mar 4 at 12:45
@Nkosi I guess it's because howTask
s work (still a little bit confusing to me). I initially had atry/catch
aroundSendAsync
there but this did not work. A test exception was risen in the queue-processor and theDebug.Fail
was executed. The exception got out of thetry/catch
and ended up behing handled byDequeueAsync
.
â t3chb0t
Mar 4 at 12:49
1
@Nkosi ok, I got it working withoutContinueWith
(and updated the code). My mistake was to return theTask
forSendAsync
instead of awaiting it.
â t3chb0t
Mar 4 at 15:47
1
Here is a nice recent video on async await that I believe you will find interesting. Youtube - On.Net async/await best practices
â Nkosi
Mar 4 at 15:52
 |Â
show 1 more comment
This is a very good improvement over the initial suggestion provided. Queuing the task was well thought out and would definitely improve the response time. I like this code.
â Nkosi
Mar 4 at 12:39
Curious though. Why useContinueWith
for theEnqueue
work item function instead of atry catch
wrapping anawait
?Enqueue
takes a function returning a task which would mean it can be awaited.
â Nkosi
Mar 4 at 12:45
@Nkosi I guess it's because howTask
s work (still a little bit confusing to me). I initially had atry/catch
aroundSendAsync
there but this did not work. A test exception was risen in the queue-processor and theDebug.Fail
was executed. The exception got out of thetry/catch
and ended up behing handled byDequeueAsync
.
â t3chb0t
Mar 4 at 12:49
1
@Nkosi ok, I got it working withoutContinueWith
(and updated the code). My mistake was to return theTask
forSendAsync
instead of awaiting it.
â t3chb0t
Mar 4 at 15:47
1
Here is a nice recent video on async await that I believe you will find interesting. Youtube - On.Net async/await best practices
â Nkosi
Mar 4 at 15:52
This is a very good improvement over the initial suggestion provided. Queuing the task was well thought out and would definitely improve the response time. I like this code.
â Nkosi
Mar 4 at 12:39
This is a very good improvement over the initial suggestion provided. Queuing the task was well thought out and would definitely improve the response time. I like this code.
â Nkosi
Mar 4 at 12:39
Curious though. Why use
ContinueWith
for the Enqueue
work item function instead of a try catch
wrapping an await
? Enqueue
takes a function returning a task which would mean it can be awaited.â Nkosi
Mar 4 at 12:45
Curious though. Why use
ContinueWith
for the Enqueue
work item function instead of a try catch
wrapping an await
? Enqueue
takes a function returning a task which would mean it can be awaited.â Nkosi
Mar 4 at 12:45
@Nkosi I guess it's because how
Task
s work (still a little bit confusing to me). I initially had a try/catch
around SendAsync
there but this did not work. A test exception was risen in the queue-processor and the Debug.Fail
was executed. The exception got out of the try/catch
and ended up behing handled by DequeueAsync
.â t3chb0t
Mar 4 at 12:49
@Nkosi I guess it's because how
Task
s work (still a little bit confusing to me). I initially had a try/catch
around SendAsync
there but this did not work. A test exception was risen in the queue-processor and the Debug.Fail
was executed. The exception got out of the try/catch
and ended up behing handled by DequeueAsync
.â t3chb0t
Mar 4 at 12:49
1
1
@Nkosi ok, I got it working without
ContinueWith
(and updated the code). My mistake was to return the Task
for SendAsync
instead of awaiting it.â t3chb0t
Mar 4 at 15:47
@Nkosi ok, I got it working without
ContinueWith
(and updated the code). My mistake was to return the Task
for SendAsync
instead of awaiting it.â t3chb0t
Mar 4 at 15:47
1
1
Here is a nice recent video on async await that I believe you will find interesting. Youtube - On.Net async/await best practices
â Nkosi
Mar 4 at 15:52
Here is a nice recent video on async await that I believe you will find interesting. Youtube - On.Net async/await best practices
â Nkosi
Mar 4 at 15:52
 |Â
show 1 more comment
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f188294%2fmiddleware-for-sending-partialviews-via-email%23new-answer', 'question_page');
);
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password