I recently added SendGrid to my ASP.net Core 2.2 MVC project. In order to make the automated account confirmation and password reset emails more professional I've tried to implement the dynamic templates. Email was working fine before, but as soon as I added the Template ID using the SendGrid helper, the emails won't send. When I remove the template ID, everything works fine again. I've sent the JSON snippet (i think that's what it's called?) to SendGrid support and they said it runs fine on their end, so something is stopping it from executing in the program. I've tried adding and removing subject and content in case it didn't like that, but that didn't change anything. My code is below. Thank you so much for any help or ideas to try.
public interface IEmailSender
{
Task SendEmailAsync(string email, string subject, string message);
}
public Task SendEmailAsync(string email, string subject, string message)
{
return Execute(Options.SendGridKey, email);
}
public Task Execute(string apiKey, string email)
{
var client = new SendGridClient(apiKey);
var msg = new SendGridMessage()
{
From = new EmailAddress("FROM EMAIL ADDRESS", "SENDER NAME"),
Subject = "testtest",
PlainTextContent = "test1",
HtmlContent = "test2"
};
msg.AddTo(new EmailAddress(email));
// removing the msg.SetTemplateId line allows email to start working again. Adding it back in breaks it.
msg.SetTemplateId("MY TEMPLATE ID");
// Disable click tracking.
// See https://sendgrid.com/docs/User_Guide/Settings/tracking.html
msg.SetClickTracking(false, false);
var debug = (msg.Serialize());
return client.SendEmailAsync(msg);
}
public async Task<IActionResult> Register(RegisterViewModel model, string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
var result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
// For more information on how to enable account confirmation and password reset please visit https://go.microsoft.com/fwlink/?LinkID=532713
// Send an email with this link
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
var callbackUrl = Url.Action(nameof(ConfirmEmail), "Account", new { userId = user.Id, code = code }, protocol: HttpContext.Request.Scheme);
await _emailSender.SendEmailAsync(model.Email, "EMAIL SUBJECT",
$"Please confirm your account by clicking this link: <a href='{callbackUrl}'>link</a>");
await _signInManager.SignInAsync(user, isPersistent: false);
_logger.LogInformation(3, "User created a new account with password.");
return RedirectToLocal(returnUrl);
}
AddErrors(result);
}
// If we got this far, something failed, redisplay form
return View(model);
}
Email was working fine before, but as soon as I added the Template ID using the SendGrid helper, the emails won't send. When I remove the template ID, everything works fine again.
I did a test using following code snippet with my SendGrid email service and an existing dynamic template, which work well for me.
You can test it with your apiKey and templateID, and please make sure the templateID you specified is valid.
var client = new SendGridClient(apiKey);
var msg = new SendGridMessage();
msg.SetFrom(new EmailAddress("{email_address_here}", "SENDER NAME"));
msg.AddTo(new EmailAddress("{email_address_here}", "RECEIVER NAME"));
msg.SetTemplateId("{templateID_here}"); // templateID look like: d-4f0a1515501a4a9dad5646a8c1xxxxx
var response = await client.SendEmailAsync(msg);
Note: in above test, install and use Sendgrid v9.12.7.
Related
I am trying to implement a feature to change the email of a user. Asp net core identity by default allows the user to change an email however it requires a confirmation link. I was wondering if it was possible to not use the confirmation link and just edit the function to update the email of the user with the new email. Thanks in advance
I have tried doing
await _userManager.ChangeEmailAsync(user, Input.NewEmail, code);
and
var changingser = _userManager.Users.First(u => u.Email.Equals(email));
changingser.Email = Input.NewEmail;
Which did not seem to work
Email.cshtml: Change Email function
public async Task<IActionResult> OnPostChangeEmailAsync()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
if (!ModelState.IsValid)
{
await LoadAsync(user);
return Page();
}
var email = await _userManager.GetEmailAsync(user);
if (Input.NewEmail != email)
{
var userId = await _userManager.GetUserIdAsync(user);
var code = await _userManager.GenerateChangeEmailTokenAsync(user, Input.NewEmail);
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
await _userManager.ChangeEmailAsync(user, Input.NewEmail, code);
/* var callbackUrl = Url.Page(
"/Account/ConfirmEmailChange",
pageHandler: null,
values: new { userId = userId, email = Input.NewEmail, code = code },
protocol: Request.Scheme);*/
//await _emailSender.SendEmailAsync(
// Input.NewEmail,
// "Confirm your email",
// $"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
StatusMessage = "Email has been changed";
return RedirectToPage();
}
StatusMessage = "Your email is unchanged.";
return RedirectToPage();
}
Edit: Failed invalid token?
Yes, you can.
Use SetEmailAsync to see the user email.
This will still require the email to be 'confirmed' so generate a token, then immediately confirm the new email address.
Something like:
await _userManager.SetEmailAsync(user, Input.NewEmail);
var token = await _userManager.GenerateEmailConfirmationTokenAsync(existingUser);
await _userManager.ConfirmEmailAsync(existingUser, token);
I am building a web app in .Net Core 3.0 and everything is running smooth but I found a small issue:
I have a method to create a user in a controller that runs well, saves the user and its all ok.
But I have the need to have another method in another controler that can also create users, and this method is copy / paste from the other working method that works, uses the same UserManager that is IOC'ed, it returns sucess, but at the end the user is not in the database.
This has to be in another method as this one if for creating users for other people, where the password is auto generated. The password passes and does not give any error on the createAsync() and it complies with the settings in the startup.cs file
I have changed the method, tryed to stimulate the EF context, to check if it was something on it, but no....
[HttpPut]
[Authorize(Roles = "Admin")]
[Route("CreateEmployee")]
public async Task<IActionResult> CreateEmployee([FromQuery]string email)
{
var user = new IdentityUser { UserName = email, Email = email };
var pw = UserHelper.GenerateRandomPassword(10);
var userResult = await _UserManager.CreateAsync(user, pw);
var resultRole = await _UserManager.AddToRoleAsync(user, "Employee");
var code = await _UserManager.GenerateEmailConfirmationTokenAsync(user);
var callbackUrl = Url.Page(
"/Account/ConfirmEmail",
pageHandler: null,
values: new { userId = user.Id, code = code },
protocol: Request.Scheme);
EmailSender sender = new EmailSender();
await sender.SendWelcomeEmailAsync("Email Header",
new NewUserData { ConfirmUrl = callbackUrl, Password = pw, Email = email, Username = $"" });
}
Inspecting the object userResult :
userResult .succeded = true
uuserResult er.Errors.Count = 0
Inspecting the object resultRole :
resultRole.succeded = true
resultRole.Errors.Count = 0
I have ASP.NET Core web api project and I need to confirm email. Everything is working fine and generated token and token received in ConfirmEmail() method are equal. Token seems valid but result gives invalid token:
var result = await userManager.ConfirmEmailAsync(user, code);
Generating token:
var code = await userManager.GenerateEmailConfirmationTokenAsync(user);
var callbackUrl = Url.EmailConfirmationLink(user.Id, code, Request.Scheme);
var email = user.Email;
await emailSender.SendEmailConfirmationAsync(email, callbackUrl);
EmailConfirmationLink() and SendEmailConfirmationAsync() methods:
public static string EmailConfirmationLink(this IUrlHelper urlHelper, string userId, string code, string scheme)
{
var result = urlHelper.Action(
action: "ConfirmEmail",
controller: "ApplicationUsers",
values: new { userId, code },
protocol: scheme);
return result;
}
public static Task SendEmailConfirmationAsync(this IEmailSender emailSender, string email, string link)
{
return emailSender.SendEmailAsync(email, "Confirm your email",
$"Please confirm your account by clicking this link: <a href='{HtmlEncoder.Default.Encode(link)}'>Confirm email</a>");
}
ConfirmEmail() method:
public async Task<IActionResult> ConfirmEmail(string userId, string code)
{
if (userId == null || code == null)
{
// ....
}
var user = await userManager.FindByIdAsync(userId);
if (user == null)
{
throw new ApplicationException($"Unable to load user with ID '{userId}'.");
}
var result = await userManager.ConfirmEmailAsync(user, code);
I was also having this issue and i tried the answers above and till got no luck.
Setting the isHtml parameter to true in the SendAsync method fixed my problem.
_emailService.SendAsync(vm.Email, "Click the link below to validate.", $"Validate email",true);
In my ASP.NET Core 1.1.1 app developed on VS2017 Ver 15.3.3, I'm using Account confirmation and password recovery in ASP.NET Core and MailKit to implement the above article's functionality but I'm getting the following error:
Note:
The error occurs at line await client.ConnectAsync("smtp.relay.uri", 25, SecureSocketOptions.None).ConfigureAwait(false); of SendEmailAsync(...) method below and
at line await _emailSender.SendEmailAsync(model.Email, "Confirm your account", $"Please confirm your account by clicking this link: <a href='{callbackUrl}'>link</a>"); of the Register(...) post method also shown below:
ERROR
SocketException: No such host is known
MessageServices.cs class
public class AuthMessageSender : IEmailSender, ISmsSender
{
public async Task SendEmailAsync(string email, string subject, string message)
{
// Plug in your email service here to send an email.
//return Task.FromResult(0);
var emailMessage = new MimeMessage();
//You can if required (and I have done in my code) set the LocalDomain used when communicating with the SMTP server
//This will be presented as the origin of the emails. In my case I needed to supply the domain so that our internal testing SMTP server would accept and relay my emails.
//We then asynchronously connect to the SMTP server. The ConnectAsync method can take just the uri of the SMTP server or as I’ve done here be overloaded with a port and SSL option. For my case when testing with our local test SMTP server no SSL was required so I specified this explicitly to make it work.
emailMessage.From.Add(new MailboxAddress("MyName", "MyEmail#MyDomain.com"));
emailMessage.To.Add(new MailboxAddress("", email));
emailMessage.Subject = subject;
emailMessage.Body = new TextPart("plain") { Text = message };
using (var client = new SmtpClient())
{
client.LocalDomain = "smtp.MyDomain.com";
await client.ConnectAsync("smtp.relay.uri", 25, SecureSocketOptions.None).ConfigureAwait(false);
await client.SendAsync(emailMessage).ConfigureAwait(false);
await client.DisconnectAsync(true).ConfigureAwait(false);
}
}
Register post method in AccountController.cs
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Register(RegisterViewModel model, string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
var result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
// For more information on how to enable account confirmation and password reset please visit https://go.microsoft.com/fwlink/?LinkID=532713
// Send an email with this link
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
var callbackUrl = Url.Action(nameof(ConfirmEmail), "Account", new { userId = user.Id, code = code }, protocol: HttpContext.Request.Scheme);
await _emailSender.SendEmailAsync(model.Email, "Confirm your account",
$"Please confirm your account by clicking this link: <a href='{callbackUrl}'>link</a>");
await _signInManager.SignInAsync(user, isPersistent: false);
_logger.LogInformation(3, "User created a new account with password.");
return RedirectToLocal(returnUrl);
}
AddErrors(result);
}
UPDATE
Not certain, but the error may be related to me not using my email host info correctly:
I'm using a website/email hosting company DiscountASP.NET's webmail feature. Their SMTP Server name for each subscriber is smtp.YourDomainName.com. And hence, in the SendEmailAsync(...) method above, I'm using client.LocalDomain = "smtp.MyDomain.com";
For MailKit implementation I'm following Sending email via a SMTP server section of this article.
Open your cmd and write ipconfig /all for windows and search for your hostname of your PC
then enter your Hostname in this function
using (var client = new SmtpClient())
{
client.LocalDomain = "smtp.MyDomain.com";
await client.ConnectAsync("YourHostName", 25,false);
await client.SendAsync(emailMessage).ConfigureAwait(false);
await client.DisconnectAsync(true).ConfigureAwait(false);
}
I have two websites using one database, I use asp.net identity (2.2.1.40403) and I have a problem I can't understand. Now, this is a third time this happened and I have no idea where the problem can be.
I have a register and send email method like this
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new User { UserName = model.Email, Email = model.Email, RegisterDate = DateTime.Now };
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
//await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);
// For more information on how to enable account confirmation and password reset please visit http://go.microsoft.com/fwlink/?LinkID=320771
await SendConfirmationEmail(user);
return View("ConfirmationEmailSent");
}
AddErrors(result);
}
// If we got this far, something failed, redisplay form
return View(model);
}
private async Task SendConfirmationEmail(Dal.Models.User user)
{
// Send an email with this link
string code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id);
var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = user.Id, code = code }, protocol: Request.Url.Scheme);
await UserManager.SendEmailAsync(user.Id, "Potvrzení Vašeho účtu", "Prosím potvrďte svou emailovou adresu kliknutím zde.");
}
What happened is that when user registered he received URL when userId was set to 3d847c51-7217-49fe-ae9d-d8e46e291559, but in database the user was created with 95789d6e-b66e-4c9e-8ee4-fe384b82e838. I don't understand how this can happen. By the way there is no user in database with Id 3d847c51-7217-49fe-ae9d-d8e46e291559. Do you have any idea why and how this can happen?
I would suggest calling back the user by an identifier after create was successful to make sure the properties match up.
//...other code removed for brevity
var user = new User { UserName = model.Email, Email = model.Email, RegisterDate = DateTime.Now };
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
//pick one
//user = await UserManager.FindById(user.Id);
//user = await UserManager.FindByName(user.UserName);
user = await UserManager.FindByEmailAsync(user.Email);
// For more information on how to enable account confirmation and password reset please visit http://go.microsoft.com/fwlink/?LinkID=320771
await SendConfirmationEmail(user);
return View("ConfirmationEmailSent");
}
AddErrors(result);
//...other code removed for brevity
I am also suspect that issue is related to UserManager.CreateAsync() method. You are using correctly. I will rather use manually generated user id instead generated by UserManager.
In your case will be:
var user = new User { UserName = model.Email, Email = model.Email, RegisterDate = DateTime.Now };
user.Id = Guid.NewGuid().ToString();
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
await SendConfirmationEmail(user);
return View("ConfirmationEmailSent");
}