Middleware for sending PartialViews via email

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP





.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;







up vote
4
down vote

favorite
2












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?







share|improve this question



























    up vote
    4
    down vote

    favorite
    2












    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?







    share|improve this question























      up vote
      4
      down vote

      favorite
      2









      up vote
      4
      down vote

      favorite
      2






      2





      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?







      share|improve this question













      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?









      share|improve this question












      share|improve this question




      share|improve this question








      edited Mar 4 at 10:04
























      asked Feb 24 at 22:26









      t3chb0t

      32.1k54195




      32.1k54195




















          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);







          share|improve this answer























          • 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


















          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 ;-)






          share|improve this answer























          • 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











          • @Nkosi I guess it's because how Tasks 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




            @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




            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










          Your Answer




          StackExchange.ifUsing("editor", function ()
          return StackExchange.using("mathjaxEditing", function ()
          StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
          StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
          );
          );
          , "mathjax-editing");

          StackExchange.ifUsing("editor", function ()
          StackExchange.using("externalEditor", function ()
          StackExchange.using("snippets", function ()
          StackExchange.snippets.init();
          );
          );
          , "code-snippets");

          StackExchange.ready(function()
          var channelOptions =
          tags: "".split(" "),
          id: "196"
          ;
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function()
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled)
          StackExchange.using("snippets", function()
          createEditor();
          );

          else
          createEditor();

          );

          function createEditor()
          StackExchange.prepareEditor(
          heartbeatType: 'answer',
          convertImagesToLinks: false,
          noModals: false,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: null,
          bindNavPrevention: true,
          postfix: "",
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          );



          );








           

          draft saved


          draft discarded


















          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






























          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);







          share|improve this answer























          • 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















          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);







          share|improve this answer























          • 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













          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);







          share|improve this answer















          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);








          share|improve this answer















          share|improve this answer



          share|improve this answer








          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

















          • 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













          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 ;-)






          share|improve this answer























          • 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











          • @Nkosi I guess it's because how Tasks 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




            @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




            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














          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 ;-)






          share|improve this answer























          • 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











          • @Nkosi I guess it's because how Tasks 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




            @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




            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












          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 ;-)






          share|improve this answer















          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 ;-)







          share|improve this answer















          share|improve this answer



          share|improve this answer








          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 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 Tasks 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




            @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




            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










          • 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 Tasks 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




            @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




            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 Tasks 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 Tasks 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












           

          draft saved


          draft discarded


























           


          draft saved


          draft discarded














          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













































































          Popular posts from this blog

          Greedy Best First Search implementation in Rust

          Function to Return a JSON Like Objects Using VBA Collections and Arrays

          C++11 CLH Lock Implementation