4

I'm implementing EmailServer for ASP.NET Identity.

I don't like how async...await is not compatible with using, and so my email method is synchronous.

So how can I call it from the framework's SendAsync method?

public class EmailService : IIdentityMessageService
{
    public Task SendAsync(IdentityMessage message)
    {
        Email email = new Email();
        return Task.FromResult<void>(email.Send(message));
    }
}

In the code above, Task.FromResult() gives me an error saying void can't be used as an argument type. But email.Send() returns void!

How to get out of this quagmire?

Jonathan Wood
  • 65,341
  • 71
  • 269
  • 466
  • 3
    What exactly you don't like about `using` and `async` (like shown in http://stackoverflow.com/questions/16566547/do-using-statements-and-await-keywords-play-nicely-in-c-sharp)? – Alexei Levenkov Apr 26 '17 at 02:55
  • @AlexeiLevenkov: I found examples saying that wasn't valid code, and I saw that `SmtpClient.SendAsync()` takes a second argument that appears related to a callback. That's what I wanted to avoid. – Jonathan Wood Apr 26 '17 at 03:01
  • 3
    OT: The [documentation for SmtpClient](https://learn.microsoft.com/dotnet/api/system.net.mail.smtpclient) recommends using [MailKit](https://github.com/jstedfast/MailKit) and [MimeKit](https://github.com/jstedfast/MimeKit). – Paulo Morgado Apr 26 '17 at 06:31

1 Answers1

9

If you don't have a result, then don't try to return a result. Just return a plain, completed Task:

public class EmailService : IIdentityMessageService
{
    public Task SendAsync(IdentityMessage message)
    {
        new Email().Send(message);
        return Task.CompletedTask;
    }
}

If you are stuck in pre-4.6 land, then you can use Task.FromResult<bool>(true) instead.

All that said, I'm confused by your comment about "async...await is not compatible with using". In my experience, that works fine. And it would be much better if your method were in fact asynchronous. I think you would be better served by focusing on how to do that, rather than what the best/correct syntax is for faking an async method.

Addendum:

I am still not clear on your concern about the use of using. However, based on your comment, it seems you would like to use SmtpClient.SendAsync() but are uncertain as to how to apply that in the context of async/await.

Unfortunately, even before async/await, we had lots of asynchronous methods in .NET, and those methods used the same naming as the convention for new awaitable methods. (To be clear: it's the naming that's unfortunate, not that the async methods existed :) ). However, in all cases, it is possible to adapt the old API to the new.

In some cases, it's as simple as using the Task.FromAsync() method. That works with anything that supports the old Begin.../End... model. But the SmtpClient.SendAsync() model is an event-based callback approach, which requires a slightly different approach.

NOTE: I noticed after writing the example below that the SmtpClient class has a Task-based method for asynchronous operation, SendMailAsync(). So in reality, there's no need to adapt the older SendAsync() method. But it's a useful example to use, to show how one might do such adaptation when a Task-based alternative hasn't been provided.

Briefly, you can use TaskCompletionSource with the SendCompleted event on the SmtpClient object. Here's an outline of what that would look like:

public class EmailService : IIdentityMessageService
{
    public async Task SendAsync(IdentityMessage message)
    {
        // I'm not familiar with "IdentityMessage". Let's assume for the sake
        // of this example that you can somehow adapt it to the "MailMessage"
        // type required by the "SmtpClient" object. That's a whole other question.
        // Here, "IdentityToMailMessage" is some hypothetical method you write
        // to handle that. I have no idea what goes inside that. :)
        using (MailMessage mailMessage = IdentityToMailMessage(message))
        using (SmtpClient smtpClient = new SmtpClient())
        {
            TaskCompletionSource<bool> taskSource = new TaskCompletionSource<bool>();

            // Configure "smtpClient" as needed, such as provided host information.
            // Not shown here!

            smtpClient.SendCompleted += (sender, e) => taskSource.SetResult(true);
            smtpClient.SendAsync(mailMessage, null);

            await taskSource.Task;
        }
    }
}

The above will start the asynchronous operation, and use the SendCompleted event's handler (i.e. the "callback" to which the documentation refers) to set the result for the TaskCompletionSource<bool> object (the result value is never really used, but there's no plain-vanilla Task version of TaskCompletionSource…you have to have some value).

It uses await, rather than returning the taskSource.Task object directly, because that way it can correctly handle disposing the SmtpClient object when the email operation has in fact completed.

Peter Duniho
  • 68,759
  • 7
  • 102
  • 136
  • I've got the latest bits. But I found a bunch of examples that avoided a `using` block in an asychronous method. I'm looking deepter into this but `SmtpClient.SendAsync()` requires a second argument, which only appears to make sense in the context of some sort of callback. – Jonathan Wood Apr 26 '17 at 03:00
  • You can pass `null` for the callback data, if you don't have any need for it. See edit above for one way to adapt the `SmtpClient.SendAsync()` method to the `async`/`await` pattern – Peter Duniho Apr 26 '17 at 03:20
  • I did not notice before, but there is in fact a `Task`-compatible send method in `SmtpClient`: [`SendMailAsync()`](https://msdn.microsoft.com/en-us/library/hh193922(v=vs.110).aspx). I will leave the above, as an example of how to generally adapt event-based asynchronous APIs to `Task`-based, but in fact in this particular scenario, it would be better to just use the `SendMailAsync()` method. – Peter Duniho Apr 26 '17 at 03:23
  • Thanks for the additional comments. I had also just found `SendMailAsync()` before your update. That one appears to behave as expected, and the one I'll likely use. But that _is_ a little confusing. – Jonathan Wood Apr 26 '17 at 03:26
  • Well, surely the `SendAsync()` method existed long before TPL came along, never mind `async`/`await`. So I understand the presence of two different methods. If in C# a method overload could vary only by return type, they might even have kept the same name. But they couldn't do that. I'm not sure I'd have picked the name `SendMailAsync()`, as it's not really different enough from `SendAsync()` and doesn't adequately distinguish the two. But they didn't ask me. :) – Peter Duniho Apr 26 '17 at 03:34