I am using ASP.NET MVC5 with Identity 2, theres a file called IdentityConfig.cs which has EmailSerive that I have implemented like this:
public class EmailService : IIdentityMessageService
{
public async Task SendAsync(IdentityMessage message)
{
using (var client = new SmtpClient())
{
using (var mailMessage = new MailMessage("info#mydomain.com", message.Destination, message.Subject, message.Body))
{
mailMessage.IsBodyHtml = true;
await client.SendMailAsync(mailMessage);
}
}
}
}
With this setup, I am able to send user emails by calling this method on UserManager:
await UserManager.SendEmailAsync(user.Id, emailSubject, emailBody);
So far so good, but Id want the email to be sent from different senders depending on the subject. For instance, account registration/reset password will have sender account#mydomain.com, information inquiry emails will have sender info#mydomain.com, sales/order placement should have sender sales#mydomain.com.
Unfortunately, the method SendEmailAsync has no way to set the sender, and I have no idea how to achieve this in the EmailService either. Can anyone help with this problem? Is there a way to add an extension method to UserManager or EmailSerivce so I can optionally specify a different sender?
If I am in your place I would put all my emails in the web.config file and access it in a private method in the EmailService class that returns relevant email based on the subject and then call this method at the place of email parameter. e.g:
public async Task SendAsync(IdentityMessage message)
{
using (var client = new SmtpClient())
{
//calling getEmail() instead of email
using (var mailMessage = new MailMessage(getEmail(message.Subject),
message.Destination, message.Subject, message.Body))
{
mailMessage.IsBodyHtml = true;
await client.SendMailAsync(mailMessage);
}
}
}
private string getEmail(string subject)
{
var emails = ConfigurationManager.AppSettings["Emails"];
string[] emailAddresses = emails.Split(',');
//your logic here
return email;
}
Related
I am trying to find out why the account verification email is not being sent from my app when creating a new user account.
I was able to send two emails on one of my first attempts. The emails ended up in my spam filter, but they did get through. I do not know what may have changed since then.
Checking the SendGrid control panel, I can confirm that two emails was sent the first day I tried it, but none of my later attempts have generated any emails.
This article suggests to set a breakpoint on EmailSender.Execute. I have done that, and found that it is indeed not being hit. How do I debug further?
The SendGrid account information is specified in secrets.json:
{
"SendGridUser": "{account name}",
"SendGridKey": "{api-key}"
}
A service is configured in Startup.cs:
services.AddTransient<IEmailSender, EmailSender>();
services.Configure<AuthMessageSenderOptions>(Configuration);
AuthMessageSenderOptions:
public class AuthMessageSenderOptions
{
public string SendGridUser { get; set; }
public string SendGridKey { get; set; }
}
The EmailSender service:
public class EmailSender : IEmailSender
{
public EmailSender(IOptions<AuthMessageSenderOptions> optionsAccessor)
{
Options = optionsAccessor.Value;
}
public AuthMessageSenderOptions Options { get; } //set only via Secret Manager
public Task SendEmailAsync(string email, string subject, string message)
{
return Execute(Options.SendGridKey, subject, message, email);
}
public Task Execute(string apiKey, string subject, string message, string email)
{
var client = new SendGridClient(apiKey);
var msg = new SendGridMessage()
{
From = new EmailAddress("my#email.com", Options.SendGridUser),
Subject = subject,
PlainTextContent = message,
HtmlContent = message
};
msg.AddTo(new EmailAddress(email));
// Disable click tracking.
// See https://sendgrid.com/docs/User_Guide/Settings/tracking.html
msg.SetClickTracking(false, false);
return client.SendEmailAsync(msg);
}
}
A user is created like this:
// user is an ApplicationUser-object
IdentityResult result = await userManager.CreateAsync(auto.Map<ApplicationUser>(user), "P&55w0rd");
if (result.Succeeded)
{
ApplicationUser u = await userManager.FindByNameAsync(user.UserName);
if (u != null)
{
// newUser.RN is the role name to be assigned to the new user
await userManager.AddToRoleAsync(u, newUser.RN);
}
return RedirectToAction("Details", new { id = user.Id });
}
else
{
foreach (var error in result.Errors)
{
ModelState.AddModelError("", error.Description);
}
}
A new user is created, added to the role and we are redirected to Users/Details/{id}, but the account verification email is not sent.
Sendgrid does not send the emails if the sender's email is not authenticated properly.
Hope this will be helpful for someone.
Doc: https://app.sendgrid.com/settings/sender_auth
I'd be surprised if Identity does it by default, only by setting up the EmailSender. You do not seem to provide any logic for the confirmation and nowhere to call the EmailSender.
You need to inject the IEmailSender as a service in your controller where you are creating the user, and add the logic to generate a confirmation token and actually send the email.
I'd expect something in the lines of:
var token = await userManager.GenerateEmailConfirmationTokenAsync(user);
var confirmationLink = Url.Action(nameof(ConfirmEmail), "Account",
new { token , email = user.Email },
Request.Scheme);
await _emailSender.SendEmailAsync(user.Email, "Confirmation email link", confirmationLink);
Of course you could further look how to make your email prettier, but that's the core of it.
Also, just to make sure that you have the whole picture, Identity does not also provide an email implementation by default, you also have to set it up: https://learn.microsoft.com/en-us/aspnet/core/security/authentication/accconfirm?view=aspnetcore-3.1&tabs=visual-studio#install-sendgrid
I am following this article: https://learn.microsoft.com/en-us/aspnet/identity/overview/features-api/account-confirmation-and-password-recovery-with-aspnet-identity
But still not able to enable account confirmation email sending using asp.net MVC. It's showing
type or namespace web could not be found
public class EmailService : IIdentityMessageService
{
public Task SendAsync(IdentityMessage message)
{
return configSendGridasync(message);
}
private Task configSendGridasync(IdentityMessage message)
{
var myMessage = new SendGridMessage();
myMessage.AddTo(message.Destination);
myMessage.From = new MailAddress("xxxx#.com", "ABC");
myMessage.Subject = message.Subject;
myMessage.PlainTextContent = message.Body;
myMessage.HtmlContent = message.Body;
var credentials = new NetworkCredential(
ConfigurationManager.AppSettings["mailAccount"],
ConfigurationManager.AppSettings["mailPassword"]
);
// Create a Web transport for sending email.
var transportWeb = new System.Web(credentials);
// Send the email.
if (transportWeb != null)
{
return transportWeb.DeliverAsync(myMessage);
}
else
{
return Task.FromResult(0);
}
}
}
I've successfully created custom email service provider for UserManager by extending IIdentityMessageService:
public class ExchangeEmailService : IIdentityMessageService
{
public async Task SendAsync(IdentityMessage message)
{
using (var client = new SmtpClient())
{
client.Host = "mail.example.com";
client.DeliveryMethod = SmtpDeliveryMethod.Network;
client.UseDefaultCredentials = false;
client.Credentials = new NetworkCredential(#"noreply", "P#ssw0rd");
var from = new MailAddress("no-reply#example.com");
var to = new MailAddress(message.Destination);
var mailMessage = new MailMessage(from, to)
{
Subject = message.Subject,
Body = message.Body,
IsBodyHtml = true
};
await client.SendMailAsync(mailMessage);
}
}
}
Inside UserManager's Create I'm creating new instance of my service and assigning it to EmailService:
userManager.EmailService = new ExchangeEmailService();
This all works fine, but I have requirement to send emails from different email addresses, based on place this is called from, unfortunately UserManager.SendEmailAsync isn't allowing me to pass anything except userId, subject and body.
Ideally I'd like to be able to specify email address when calling SendEmailAcync or enum value.
I've searched in UserManager source and I thought about creating custom SendEmailAsync implementation, but I'd have to change multiple places - IIdentityMessageService, UserManager.
I know I can create new interface and build my email sending class on it, but I'd like to keep changes to minimum.
What I'd like to get:
I'd like to have enum containing names of mailboxes:
public enum Mailboxes
{
Noreply = 0,
Service = 1,
Contact = 2
}
and be able to call SendEmailAsync with that additional parameter:
await UserManager.SendEmailAsync(user.Id, "Account created", "Nice email content.", Mailboxes.Noreply );
I'm aware that I can set different title and use same email address, but this is requirement I got.
How can I do that?
A hack would be to create a model to store what you want sent to the service
public class MessageBody {
public string Body { get; set; }
public MailBoxes Source { get; set; }
}
and then serialize it (JSON maybe) as the body parameter of the SendEmailAsync method call.
var message = new MessageBody {
Body = "Nice email content.",
Source = Mailboxes.Noreply
};
var body = JsonConvert.SerializeObject(message);
await UserManager.SendEmailAsync(user.Id, "Account created", body);
The service would then deserialize the model, construct the email with the custom info and then send the email.
public class ExchangeEmailService : IIdentityMessageService {
readonly IMailBoxProvider provider;
public ExchangeEmailService(IMailBoxProvider provider) {
this.provider = provider;
}
public async Task SendAsync(IdentityMessage message) {
using (var client = new SmtpClient()) {
client.Host = "mail.example.com";
client.DeliveryMethod = SmtpDeliveryMethod.Network;
client.UseDefaultCredentials = false;
client.Credentials = new NetworkCredential(#"noreply", "P#ssw0rd");
//Get the body and from address
var fromEmailAddress = "default-email#example.com";
var body = message.Body;
try {
var msg = JsonConvert.DeserializeObject<MessageBody>(body);
if(msg != null) {
body = msg.Body;
fromEmailAddress = provider.GetMailbox(msg.Source);
}
} catch { }
var from = new MailAddress(fromEmailAddress);
var to = new MailAddress(message.Destination);
var mailMessage = new MailMessage(from, to)
{
Subject = message.Subject,
Body = body,
IsBodyHtml = true
};
await client.SendMailAsync(mailMessage);
}
}
}
And just make sure to assigning it to UserManager.EmailService:
userManager.EmailService = new ExchangeEmailService(new MailBoxProvider());
I am using this tutorial to register a user with email confirmation, but I can't find the emailservice class.
http://www.asp.net/mvc/overview/security/create-an-aspnet-mvc-5-web-app-with-email-confirmation-and-password-reset
I am using MVC 5 with Identity 2.2.
Thanks but i already find the solution
I have created the class emailservice and them
Selected the email from usermanager, like this
userManager.EmailService = new EmailService();
As far as I know, there is no such .net class as EmailService Typically you use the SmtpClient class to send emails:
SmtpClient client = new SmtpClient("server.address.com");
MailAddress from = new MailAddress(fromAddress, fromName);
MailMessage msg = new MailMessage();
msg.From = from;
foreach(string addr in to)
msg.To.Add(addr);
msg.Body = content;
msg.Subject = subject;
client.Send(msg);
I had the same question, and used the following solution. First, create a new folder in your project called services, and create a new class with the following code from the tutorial:
namespace YourProject.Services
{
public class EmailService : IIdentityMessageService
{
public async Task SendAsync(IdentityMessage message)
{
await configSendGridasync(message);
}
// Use NuGet to install SendGrid (Basic C# client lib)
private async Task configSendGridasync(IdentityMessage message)
{
var myMessage = new SendGridMessage();
myMessage.AddTo(message.Destination);
myMessage.From = new System.Net.Mail.MailAddress("no-reply#your-domain.com", "Customer Service");
myMessage.Subject = message.Subject;
myMessage.Text = message.Body;
myMessage.Html = message.Body;
var credentials = new NetworkCredential(
ConfigurationManager.AppSettings["SendGridUsername"],
ConfigurationManager.AppSettings["SendGridPassword"]
);
// Create a Web transport for sending email.
var transportWeb = new Web(credentials);
// Send the email.
if (transportWeb != null)
{
await transportWeb.DeliverAsync(myMessage);
}
else
{
Trace.TraceError("Failed to create Web transport.");
await Task.FromResult(0);
}
}
}
}
Then, what I did was set the EmailService property of the UserManager object in my controller to a new instance of the EmailService class (scroll all the way to the right to see it, sorry for that):
namespace YourProject.Controllers
{
public class UserManagementController : Controller
{
private UserManager<ApplicationUser> UserManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext())){EmailService = new Services.EmailService()};
// GET: UserManagement
Then, when you call
UserManager.SendEmailAsync(user.Id, subject, body);
It will send the email using your SendGrid accound as configured in the EmailService class above.
I am trying to send email from my MVC application, it sends fine when I use the .Send() method but takes a while to come back so I wanted to use the .SendMailAsync() function, but I am receiving the following error during execution.
An asynchronous operation cannot be started at this time. Asynchronous operations may only be started within an asynchronous handler or module or during certain events in the Page lifecycle. If this exception occurred while executing a Page, ensure that the Page is marked <%# Page Async="true" %>
This is my code sample. How can I configure this to send using .SendMailAsync()
Email Wrapper Class:
using System.Net.Mail;
namespace Helpers
{
public class Email
{
// constants
private const string HtmlEmailHeader = "<html><head><title></title></head><body style='font-family:arial; font-size:14px;'>";
private const string HtmlEmailFooter = "</body></html>";
// properties
public List<string> To { get; set; }
public List<string> CC { get; set; }
public List<string> BCC { get; set; }
public string From { get; set; }
public string Subject { get; set; }
public string Body { get; set; }
// constructor
public Email()
{
To = new List<string>();
CC = new List<string>();
BCC = new List<string>();
}
// send
public void Send()
{
MailMessage message = new MailMessage();
foreach (var x in To)
{
message.To.Add(x);
}
foreach (var x in CC)
{
message.CC.Add(x);
}
foreach (var x in BCC)
{
message.Bcc.Add(x);
}
message.Subject = Subject;
message.Body = string.Concat(HtmlEmailHeader, Body, HtmlEmailFooter);
message.BodyEncoding = System.Text.Encoding.UTF8;
message.From = new MailAddress(From);
message.SubjectEncoding = System.Text.Encoding.UTF8;
message.IsBodyHtml = true;
SmtpClient client = new SmtpClient("relay.mail.server");
client.SendMailAsync(message);
}
}
}
Controller:
public ActionResult Index()
{
Email email = new Email();
email.To.Add("to#email.com");
email.From = "from#email.com";
email.Subject = "Subject";
email.Body = "<p><strong>Hello</strong></p><p>This is my first Email Message</p>";
email.Send();
}
EDIT
Further to the actual question asked, the underlying issue was the delay created when sending emails. I looked further into the actual issue and with the help of this post:
ASP.Net MVC background threads for email creation and sending
modified my Email Wrapper class to spawn off a new thread to perform the email processing:
using System.Net.Mail;
namespace Helpers
{
public class Email
{
// constants
private const string HtmlEmailHeader = "<html><head><title></title></head><body style='font-family:arial; font-size:14px;'>";
private const string HtmlEmailFooter = "</body></html>";
// properties
public List<string> To { get; set; }
public List<string> CC { get; set; }
public List<string> BCC { get; set; }
public string From { get; set; }
public string Subject { get; set; }
public string Body { get; set; }
// constructor
public Email()
{
To = new List<string>();
CC = new List<string>();
BCC = new List<string>();
}
// send
public void Send()
{
MailMessage message = new MailMessage();
foreach (var x in To)
{
message.To.Add(x);
}
foreach (var x in CC)
{
message.CC.Add(x);
}
foreach (var x in BCC)
{
message.Bcc.Add(x);
}
message.Subject = Subject;
message.Body = string.Concat(HtmlEmailHeader, Body, HtmlEmailFooter);
message.BodyEncoding = System.Text.Encoding.UTF8;
message.From = new MailAddress(From);
message.SubjectEncoding = System.Text.Encoding.UTF8;
message.IsBodyHtml = true;
SmtpClient client = new SmtpClient("relay.mail.server");
new Thread(() => { client.Send(message); }).Start();
}
}
}
Admittedly, the error is a bit obtuse, but all it's really telling you is that you're calling an asynchronous method from a synchronous method, which isn't allowed. If you're going to use async, you have to use async all the way up the chain.
So, first you need to change your Send method definition to return a Task:
public async Task Send()
And set your async method call to await:
await client.SendMailAsync(message);
Then, do the same for your action:
public async Task<ActionResult> Index()
And:
await email.Send();
UPDATE
Async doesn't do what I think you think it does. When your action is invoked by a request, it will not return a response until all code inside the action has fully executed. Async is not a magic wand that makes the action return the response quicker. Your task (in this case, sending an email) takes as long as it takes and async or not, the action will not return a response until the task has completed.
So why use async then? Because what async does do is let go the thread from the server pool. Let's say IIS is running in a pretty standard config, you'll likely have somewhere around 1000 threads available. This is often called the "max requests", because typically 1 request == 1 thread. So, if you server comes under heavy load and you're fielding more than the "max requests", each subsequent request is queued until a thread from the pool becomes available again. If all the threads are tied up waiting on something to complete, then your server essentially deadlocks. But, when you use async, you tell IIS essentially, "I'm waiting on something. Here's my thread back, so you can use it to field another request. I'll let you know when I need it back." That allows requests in the queue to proceed.
Long and short, do always use async when you are doing anything that involves waiting, because it allows server resources to be used more efficiently, but remember that it doesn't make things happen quicker.
EDIT 12/11/14 - Updated terminology a bit to make clear that async is only useful when a thread is waiting, not just involved in some long-running task. For example, running complex financial calculations could "take a while", but would not be a good fit for async because all the work is CPU-bound. The task may be long-running, but if the thread is not in a wait-state, it can't be used for other tasks and your async method will essentially just run as sync, but with extra overhead.
This might help you out.
public void Send(MailAddress toAddress, string subject, string body, bool priority)
{
Task.Factory.StartNew(() => SendEmail(toAddress, subject, body, priority), TaskCreationOptions.LongRunning);
}
private void SendEmail(MailAddress toAddress, string subject, string body, bool priority)
{
MailAddress fromAddress = new MailAddress(WebConfigurationManager.AppSettings["SmtpFromAddress"]);
string serverName = WebConfigurationManager.AppSettings["SmtpServerName"];
int port = Convert.ToInt32(WebConfigurationManager.AppSettings["SmtpPort"]);
string userName = WebConfigurationManager.AppSettings["SmtpUserName"];
string password = WebConfigurationManager.AppSettings["SmtpPassword"];
var message = new MailMessage(fromAddress, toAddress);
message.Subject = subject;
message.Body = body;
message.IsBodyHtml = true;
message.HeadersEncoding = Encoding.UTF8;
message.SubjectEncoding = Encoding.UTF8;
message.BodyEncoding = Encoding.UTF8;
if (priority) message.Priority = MailPriority.High;
Thread.Sleep(1000);
SmtpClient client = new SmtpClient(serverName, port);
client.DeliveryMethod = SmtpDeliveryMethod.Network;
client.EnableSsl = Convert.ToBoolean(WebConfigurationManager.AppSettings["SmtpSsl"]);
client.UseDefaultCredentials = false;
NetworkCredential smtpUserInfo = new NetworkCredential(userName, password);
client.Credentials = smtpUserInfo;
client.Send(message);
client.Dispose();
message.Dispose();
}
The Thread.Sleep is there because this will send mail through so fast that many SMTP servers will report too many emails from same IP error message. Although ASP.NET handles asynchronous send mail, it will not send more than one message at a time. It waits until callback occurs before sending another email. This approach will send messages in parallel as fast as the code can call Send().
I think the below does what you're trying to accomplish:
Modify your controller as below:
public async Task<ActionResult> Index()
{
Email email = new Email();
email.SendAsync();
}
And in your Email class add the SendAsync method as below
public async Task SendAsync()
{
await Task.Run(() => this.send());
}
The action will return before the email is sent and the request will not be blocked.
Try to see the behaviour with default mvc template with this code:
[AllowAnonymous]
public ActionResult Login(string returnUrl)
{
LongRunningTaskAsync();
return View();
}
public static async Task LongRunningTaskAsync()
{
await Task.Run(() => LongRunningTask());
}
public static void LongRunningTask()
{
Debug.WriteLine("LongRunningTask started");
Thread.Sleep(10000);
Debug.WriteLine("LongRunningTask completed");
}
The login page will be displayed instantly. But output window will display "LongRunningTask completed" 10 seconds later.