2

I'm trying to send verify emailadress email in asp.net identity 2.0 application but get the TaskCanceledException error.

I tried whats in this thread: Asp.Net Identity 2.0 - How to Implement IIdentityMessageService to do Async SMTP using SmtpClient?

If i use the await the application just runs forever: no error message. but the second option returns the error.

The error is thrown at: return smtpClient.SendMailAsync(msg);

IdentityConfig.cs

    public async Task SendAsync(IdentityMessage message)
    {
        // Plug in your email service here to send an email.

        try
        {
            #region formatter
            string text = string.Format("Please click on this link to {0}: {1}", message.Subject, message.Body);
            string html = "Please confirm your account by clicking this link: <a href=\"" + message.Body + "\">link</a><br/>";

            html += HttpUtility.HtmlEncode(@"Or click on the copy the following link on the browser:" + message.Body);
            #endregion

            MailMessage msg = new MailMessage();
            msg.From = new MailAddress("");
            msg.To.Add(new MailAddress(message.Destination));
            msg.Subject = message.Subject;
            msg.AlternateViews.Add(AlternateView.CreateAlternateViewFromString(text, null, MediaTypeNames.Text.Plain));
            msg.AlternateViews.Add(AlternateView.CreateAlternateViewFromString(html, null, MediaTypeNames.Text.Html));

            using (var smtpClient = new SmtpClient())
            {
                smtpClient.EnableSsl = true;
                smtpClient.SendCompleted += (s, e) => { smtpClient.Dispose(); };
                await smtpClient.SendMailAsync(msg);
            }


        }
        catch (Exception ex)
        {

            throw ex;
        }


    }

Register.aspx.cs

        protected void CreateUser_Click(object sender, EventArgs e)
    {
        var manager = Context.GetOwinContext().GetUserManager<ApplicationUserManager>();
        var user = new ApplicationUser()
        {
            UserName = Email.Text,
            Email = Email.Text,
            UserProfileInfo = new UserProfileInfo
            {
                FirstName = Firstname.Text,
                LastName = Lastname.Text,
                Adress = Adress.Text,
                Zip = Zip.Text,
                City = City.Text,
                Mobile = Mobile.Text

            }

        };
        IdentityResult result = manager.Create(user, Password.Text);
        if (result.Succeeded)
        {
            // For more information on how to enable account confirmation and password reset please visit http://go.microsoft.com/fwlink/?LinkID=320771
            string code = manager.GenerateEmailConfirmationToken(user.Id);
            string callbackUrl = IdentityHelper.GetUserConfirmationRedirectUrl(code, user.Id, Request);
            manager.SendEmail(user.Id, "Confirm your account", "Please confirm your account by clicking <a href=\"" + callbackUrl + "\">here</a>.");

            manager.AddToRole(user.Id, "Konsument");
            IdentityHelper.SignIn(manager, user, isPersistent: false);
            IdentityHelper.RedirectToReturnUrl(Request.QueryString["ReturnUrl"], Response);
        }
        else
        {
            ErrorMessage.Text = result.Errors.FirstOrDefault();
        }
    }
Community
  • 1
  • 1
Madde Persson
  • 413
  • 1
  • 6
  • 26

2 Answers2

3

@ntl correctly identified the problem: the SmtpClient instance is being disposed before the request completes. However, I prefer using async and await as the solution:

public async Task SendAsync(IdentityMessage message)
{
  ...
  using (var smtpClient = new SmtpClient())
  {
    smtpClient.EnableSsl = true;
    smtpClient.SendCompleted += (s, e) => { smtpClient.Dispose(); };
    await smtpClient.SendMailAsync(msg);
  }
}

So, let's solve the original problem:

If i use the await the application just runs forever: no error message.

I have a sense that your code is probably calling Task.Wait or Task<T>.Result after it calls SendAsync. That's the real problem: the calling code should be changed to use await (which makes the calling method async and returning a Task/Task<T>, which makes its calling code have to use await, etc). Allow the async to grow "up" naturally through the codebase. This is why one of the async best practices in my MSDN article on the subject is "async all the way".

The deadlock is caused because when you await a task, then await will by default capture a "current context" (in this case, an ASP.NET SynchronizationContext), and use that context to resume the async method. However, the ASP.NET SynchronizationContext only allows one thread per request at a time, and so when the calling code blocks (by calling Wait or Result), it is blocking a thread in that context and the async method cannot resume. I describe this deadlock situation in more detail on my blog.

The best solution is to use await, and use async all the way.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • still just runs forever – Madde Persson Aug 19 '14 at 08:08
  • @MaddePersson: Please provide a minimal code sample that reproduces the problem. – Stephen Cleary Aug 19 '14 at 12:37
  • I updated my code, and included register.aspx.cs code. – Madde Persson Aug 19 '14 at 12:42
  • @MaddePersson: Your code doesn't call `SendAsync`. What you need to do is 1) Copy your existing solution. 2) Remove **all** the code that you possibly can while keeping the deadlock. 3) Post all the remaining code. – Stephen Cleary Aug 19 '14 at 12:46
  • don't understand. Im just started with identity and haven't worked with tasks and async before – Madde Persson Aug 20 '14 at 08:16
  • Creating a repro doesn't have anything to do with any particular technology. Check out [this blog post](http://codeblog.jonskeet.uk/2010/08/29/writing-the-perfect-question/), especially the section "Sample code and data". – Stephen Cleary Aug 20 '14 at 12:28
  • i dont know what code it is that you want, i copied all the code that sends mail. the SendAsync method was already created when i started the project: empty with the text "// Plug in your email service here to send an email". The code in the register.aspx.cs was already implemented also. I have Send emails before in ordinary methods but never worked with task and async therefore i dont know whats wrong. – Madde Persson Aug 20 '14 at 12:52
  • Tried make the CreateUser_Click method async, not working. protected async void CreateUser_Click(object sender, EventArgs e) – Madde Persson Aug 20 '14 at 13:19
1

Probably the problem is that your smpt client is already disposed when SendMailAsync is executing. That is why exception is thrown, but because this operation runs in separate thread (async) you get TaskCanceledException.

You may try to delete your using statement.

public Task SendAsync(IdentityMessage message)
{
    // Plug in your email service here to send an email.
    try
    {
        #region formatter
        string text = string.Format("Please click on this link to {0}: {1}", message.Subject, message.Body);
        string html = "Please confirm your account by clicking this link: <a href=\"" + message.Body + "\">link</a><br/>";
        html += HttpUtility.HtmlEncode(@"Or click on the copy the following link on the browser:" + message.Body);
        #endregion

        MailMessage msg = new MailMessage();
        msg.From = new MailAddress("info@emailadress.com");
        msg.To.Add(new MailAddress(message.Destination));
        msg.Subject = message.Subject;
        msg.AlternateViews.Add(AlternateView.CreateAlternateViewFromString(text, null, MediaTypeNames.Text.Plain));
        msg.AlternateViews.Add(AlternateView.CreateAlternateViewFromString(html, null, MediaTypeNames.Text.Html));

        var smtpClient = new SmtpClient();            
        smtpClient.EnableSsl = true;
        smtpClient.SendCompleted += (s, e) => { smtpClient.Dispose(); };
        return smtpClient.SendMailAsync(msg);
    }
    catch (Exception)
    {
        return Task.FromResult(0);
    }
}

Smtp client will be disposed with help of SendCompleted handler.

ntl
  • 1,289
  • 1
  • 10
  • 16