Reputation: 4883
I'm working on a REST API on ASP.NET. I want to know which is the best way to do a background work indepentently of sending the response to the client. Basically, I have a PUT method where I have to send several emails. Suppose they are 10 emails. I will resume all the methods to make everything more clear:
I tried the following and both ways the response was sent after the emails were sent and they took the same time:
Way 1:
[EnableCors(origins: "*", headers: "*", methods: "*", SupportsCredentials = true)]
public async Task<HttpResponseMessage> Put(string id, [FromBody]InformeModel informe)
{
if (CookieManager.ValidarCookie(Request.Headers.GetCookies("mb-session").FirstOrDefault()) == EstadoCookie.VALIDA)
{
await SendMails()
return Request.CreateResponse(HttpStatusCode.OK);
}
else
{
return Request.CreateResponse(HttpStatusCode.Unauthorized);
}
}
private async Task SendMails()
{
await Task.Run(() => {
foreach (string m in mails)
{
Mail mail = new Mail("[email protected]", 25, "[email protected]", "myPass");
mail.SendMailHTML(m, "Email Title", "This is the Email");
}
});
}
Way 2:
// PUT: api/Informes/5
[EnableCors(origins: "*", headers: "*", methods: "*", SupportsCredentials = true)]
public HttpResponseMessage Put(string id, [FromBody]InformeModel informe)
{
if (CookieManager.ValidarCookie(Request.Headers.GetCookies("mb-session").FirstOrDefault()) == EstadoCookie.VALIDA)
{
SendMails()
return Request.CreateResponse(HttpStatusCode.OK);
}
else
{
return Request.CreateResponse(HttpStatusCode.Unauthorized);
}
}
private async void SendMails()
{
await Task.Run(() => {
foreach (string m in mails)
{
Mail mail = new Mail("[email protected]", 25, "[email protected]", "myPass");
mail.SendMailHTML(m, "Email Title", "This is the Email");
}
});
}
Which is the best approach in order to send the response to the client and not have to wait that all emails have been sent?. Do I have to go with ThreadPool?
Upvotes: 1
Views: 572
Reputation: 3853
If you do not want to wait for emails to be sent (or any related errors), you should do a "fire and forget". Solutions in order of robustness:
Use HangFire, robust fire-and-forget lib
HostingEnvironment.QueueBackgroundWorkItem - will at least try to stop the app pool from killing of your emails in case of a recycle
Unawaited Task.Run with continuation for error management:
Ex:
Task.Run(async () => await ..send emails.)
.ContinueWith(t => logError(t.Exception.GetBaseException()),
TaskContinuationOptions.OnlyOnFaulted);
Upvotes: 1
Reputation: 29302
I'd go a step further with the decoupling and use a generic event, similar to a domain event.
If the controller can do its job and return a response whether or not the email has been sent, that strongly suggests that sending an email is way outside the responsibility of the controller. (With emails in particular, the controller can never know for sure whether an email has been sent anyway.)
In that scenario I'd use some sort of an event bus. Some examples use a static instance, or it could be something that you inject into the controller.
Then, instead of sending an email, the controller just raises an event, like
_eventBus.Raise(new SomethingHappenedEvent(argsThatIncludeRelevantInfo args));
Then you have an event handler (you could even have multiple) and an event handler sends the email, including making it async
and handling any exceptions.
That makes unit testing much easier. You don't need to test whether your controller has sent an email. You can mock the event bus and just test that an event was raised. Then the event handlers can be tested separately.
Here is an article on domain events that was referenced a lot for a while. I'm sure that there's more up-to-date information out there.
A lot of information about these events is in the context of Domain Driven Development, but the principles and mechanics apply whether or not you're doing DDD. Your class becomes decoupled from other things that result from its methods, but which it itself shouldn't know or care about - like a controller that returns a response, and a side effect is sending an email.
Here's an event bus implementation I did a while back. I don't think I included async handling, but that might make sense when you want to fire-and-forget. That's something I should revisit.
And as mentioned in a comment, fire-and-forget doesn't mean that the result of the event is trivial. To make sure that something happens it might make sense that the event handler puts a request in a queue instead of doing the work itself. That makes it more durable so you can make sure that whatever gets put in the queue gets processed.
Upvotes: 4