I don't know how to invoke the SendEmailAsync function
Register Post page
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 http://go.microsoft.com/fwlink/?LinkID=532713
// Send an email with this link
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
var callbackUrl = Url.Action("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);
}
// If we got this far, something failed, redisplay form
return View(model);
}
Email configuration Page
// this code was already there
public interface IEmailSender
{
Task SendEmailAsync(string email, string subject, string message);
}
//I got this code from some other site. I don't know how to use this together.
public class sendMail : IEmailSender // this line is written by me
{
public async Task SendEmailAsync(string email, string subject, string message)
{
var emailMessage = new MimeMessage();
emailMessage.From.Add(new MailboxAddress("Joe Bloggs", "jbloggs#example.com"));
emailMessage.To.Add(new MailboxAddress("", email));
emailMessage.Subject = subject;
emailMessage.Body = new TextPart("plain") { Text = message };
using (var client = new SmtpClient())
{
client.LocalDomain = "some.domain.com";
await client.ConnectAsync("smtp.relay.uri", 25, SecureSocketOptions.None).ConfigureAwait(false);
await client.SendAsync(emailMessage).ConfigureAwait(false);
await client.DisconnectAsync(true).ConfigureAwait(false);
}
}
}
how the write the class definition to send email.
Break point does not reach here, I think there is some problem with my implementation.
in Startup make sure you change this:
services.AddTransient<IEmailSender, AuthMessageSender>();
to
services.AddTransient<IEmailSender, sendMail>();
so that your implementaiton gets injected
Related
I already have a register action
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
var useremail = _userManager.Users.FirstOrDefault(u => u.Email.ToLower() == Input.Email.ToLower());
if (useremail == null)
{
returnUrl = returnUrl ?? Url.Content("~/");
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
if (ModelState.IsValid)
{
var user = new IdentityUser { UserName = Input.UserName, Email = Input.Email };
var result = await _userManager.CreateAsync(user, Input.Password);
if (result.Succeeded)
{
_logger.LogInformation("User created a new account with password.");
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
var callbackUrl = Url.Page(
"/Account/ConfirmEmail",
pageHandler: null,
values: new { area = "Identity", userId = user.Id, code = code },
protocol: Request.Scheme);
await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
$"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
if (_userManager.Options.SignIn.RequireConfirmedAccount)
{
return RedirectToPage("RegisterConfirmation", new { email = Input.Email });
}
else
{
await _signInManager.SignInAsync(user, isPersistent: false);
return LocalRedirect(returnUrl);
}
}
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
}
}
// If we got this far, something failed, redisplay form
ViewData["EmailExists"] = "Try another email that one is used";
return Page();
}
Then I created the sendgrid user and key and registered them by CMD, then I created the action of send email
public class EmailSender : IEmailSender
{
public EmailSender(IOptions<AuthMessageSenderOptions>optionsAccessor)
{
Options = optionsAccessor.Value;
}
public AuthMessageSenderOptions Options { get; }
public Task SendEmailAsync (string email , string subject , string message)
{
return Excute(Options.SendGridKey,subject,message,email);
}
private Task Excute(string apiKey, string subject, string message, string email)
{
var client = new SendGridClient(apiKey);
var msg = new SendGridMessage()
{
From = new EmailAddress("darydress#yahoo.com", "dary dress"),
Subject = subject,
PlainTextContent = message,
HtmlContent = message
};
msg.AddTo(new EmailAddress(email));
msg.SetClickTracking(false, false);
return client.SendEmailAsync(msg);
}
}
Then in startup.cs
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<IdentityUser, IdentityRole>( options => options.SignIn.RequireConfirmedAccount = true)
.AddDefaultUI()
.AddDefaultTokenProviders()
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddControllersWithViews();
services.AddRazorPages();
services.AddMvc();
services.AddTransient<IEmailSender, EmailSender>();
services.Configure<AuthMessageSenderOptions>(Configuration);
services.AddPaging();
services.ConfigureApplicationCookie(o => {
o.ExpireTimeSpan = TimeSpan.FromDays(5);
o.SlidingExpiration = true;
});
services.AddMvc(options =>
{
options.Filters.Add(new RequireHttpsAttribute());
});
services.ConfigureApplicationCookie(options =>
{
options.AccessDeniedPath = new Microsoft.AspNetCore.Http.PathString("/Main/AccessDenied");
});
}
but sending an e-mail doesn't work after registration gives me some words that i need confirm my email and gives me link to confirm my email but doesn't send it to gmail
Does anyone have an idea?
I followed this documentation from microsoft
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/accconfirm?view=aspnetcore-3.1&tabs=visual-studio
When I create a new web application with individual user accounts, this works perfectly, but I noticed that when you scaffold identity and override all pages to have control into an existing app, the behavior you are experiencing is the usual.
Here is how I fixed it:
If you open the file Areas/Identity/Pages/Account/RegisterConfirmation.cshtml.cs look for the comment Once you add a real email sender, you should remove this code that lets you confirm the account, comment everything below that line before the return Page() statement and that should do the job.
"Solved" I asked Sendgrid, and I was told that I cannot use my yahoo email (or gmail,...) as the sender email; this is part of the answer: "Yahoo observes an email security standard called DMARC. DMARC instructs email providers to reject messages where the From domain is a Yahoo domain, but the message originates from a non-approved domain server/service." So I need to use my own mail domain;
MVC Assembly: Microsoft.AspNet.Identity.UserManager
CORE Assembly: Microsoft.Extensions.Identity.Core
I am migrating from MVC to .NET CORE 2.2. I have created a MyManager class that inherits from UserManager. The UserManager class in MVC has the following properties:
public IIdentityMessageService EmailService { get; set; }
public IIdentityMessageService SmsService { get; set; }
In .NET CORE 2.2., the UserManager does not have these properties. I am guessing that the MVC UserManager uses those properties to automatically send messages. It looks like it uses the EmailService to automatically send the message that verifies the email address. However, this behavior does not appear to happen in .NET CORE. So, it looks like the UserManager no longer handles this.
The question is this: How do you verify a user's email address?
Is this being handled in a different Microsoft assembly or has Microsoft gotten out of that business and farmed it off to a 3rd-party assembly? Or, is there something else?
this is how i send email verification using Sendgrid in asp.net core.
step 1 : Create a class called EmailSender that implements IEmailSender Interface(need to include using Microsoft.AspNetCore.Identity.UI.Services; for IEmailSender) :
public class EmailSender : IEmailSender
{
public async Task SendEmailAsync(string email, string subject, string htmlMessage)
{
var apiKey = "send grid api key";
//it looks like this :
// var apiKey = "SG.2MpCzyTvIQ.WhHMg6-VBjuqbn9k-8P9m6X9cHM73fk2fzAT5sA4zKc";
var client = new SendGridClient(apiKey);
var from = new EmailAddress($"noreply#domaimName.com", "Email title");
var to = new EmailAddress(email, email);
var plainTextContent = htmlMessage;
var htmlContent = htmlMessage;
var msg = MailHelper.CreateSingleEmail(from, to, subject, plainTextContent, htmlContent);
try
{
var response = await client.SendEmailAsync(msg);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
//return Task.CompletedTask;
}
}
Step 2 : You need to add it as a service using Dependency Injection inside the Startup.cs file
services.AddTransient<IEmailSender, EmailSender>();
Step 3 : Inject it into the constructor of the class you want to use it in.
[AllowAnonymous]
public class RegisterModel : PageModel
{
private readonly IEmailSender _emailSender;
public RegisterModel(IEmailSender emailSender)
{
_emailSender = emailSender;
}
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
if (ModelState.IsValid)
{
var user = new ApplicationUser
{
UserName = Input.Email,
Email = Input.Email,
DateCreated = DateTime.Now
};
var result = await _userManager.CreateAsync(user, Input.Password);
if (result.Succeeded)
{
_logger.LogInformation("User created a new account with password.");
returnUrl = Url.Action("ConfirmEmail", "Home");
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
await _userManager.AddToRoleAsync(user,"ROleName");
var callbackUrl = Url.Page(
"/Account/ConfirmEmail",
pageHandler: null,
values: new { userId = user.Id, code = code },
protocol: Request.Scheme);
await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
$"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
return LocalRedirect(returnUrl);
}
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
}
// If we got this far, something failed, redisplay form
return Page();
}
and this should send your email. it works for me and it should for you too.
I'm trying to implement SendGrid in Angular project template with ASP.NET Core.
I'm using this example: Account confirmation and password recovery in ASP.NET Core
Unfortunately I always get this error (status: 500):
System.InvalidOperationException: Unable to resolve service for type 'MyWebApp.Extensions.EmailSender' while attempting to activate 'MyWebApp.Controllers.AuthenticationController'
Here is the Controller:
[HttpPost]
public async Task<IActionResult> Register([FromBody]RegistrationViewModel model)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var userIdentity = _mapper.Map<AppUser>(model);
var result = await _userManager.CreateAsync(userIdentity, model.Password);
if (!result.Succeeded)
return new BadRequestObjectResult(Errors.AddErrorsToModelState(result, ModelState));
//Enable account confirmation
var code = await _userManager.GenerateEmailConfirmationTokenAsync(userIdentity);
var callbackUrl = Url.EmailConfirmationLink(userIdentity.Id, code, Request.Scheme);
await _emailSender.SendEmailConfirmationAsync(model.Email, callbackUrl);
//await _appDbContext.SaveChangesAsync();
return new OkObjectResult("Account created");
}
And the EmailSender:
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(<email-address>, <name>),
Subject = subject,
PlainTextContent = message,
HtmlContent = message
};
msg.AddTo(new EmailAddress(email));
return client.SendEmailAsync(msg);
}
}
I tried to write the apiKey directly in the code, but same result.
I've already successfully added a user in the database, but without SendGrid.
Make sure that the service and its abstraction is registered with the service collection so that the service provider is aware of how to resolve them
Startup.ConfigureServices
services.AddSingleton<IEmailSender, EmailSender>();
services.Configure<AuthMessageSenderOptions>(Configuration);
I have to do the exception handling in asp.net core I have read so many articles and I have implemented it on my startup.cs file here is the code
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IServiceProvider svp)
{
app.UseExceptionHandler(errorApp =>
{
errorApp.Run(async context =>
{
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; ; // or another Status accordingly to Exception Type
context.Response.ContentType = "application/json";
var error = context.Features.Get<IExceptionHandlerFeature>();
if (error != null)
{
var ex = error.Error;
await context.Response.WriteAsync(new ErrorDto()
{
Code = 1,
Message = ex.Message // or your custom message
// other custom data
}.ToString(), Encoding.UTF8);
}
});
app.UseMvc();
I am having a problem that how to call this code when there is exception occur in my controller.
I will be very thankfullk to you.
Here is the controller code-:
[HttpPost]
[AllowAnonymous]
public async Task<JsonResult> Register([FromBody] RegisterViewModel model)
{
int count = 1;
int output = count / 0;
var user = new ApplicationUser { UserName = model.Email, Email = model.Email, FirstName = model.FirstName, LastName = model.LastName, UserType = model.UserType };
user.FirstName = user.UserType.Equals(Models.Entity.Constant.RECOVERY_CENTER) ? model.Name : model.FirstName;
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 http://go.microsoft.com/fwlink/?LinkID=532713
// Send an email with this link
//var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
//var callbackUrl = Url.Action("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.");
user = await _userManager.FindByEmailAsync(user.Email);
var InsertR = await RecoveryGuidance.Models.Entity.CenterGateWay.AddNewRecoveryCenter(new Models.Entity.Center { Rec_Email = user.Email, Rec_Name = user.FirstName, Rec_UserId = user.Id });
}
AddErrors(result);
return Json(result);
}
You don't need to call it. UseExceptionHandler is an extension method which uses ExceptionHandlerMiddleware. See middleware source code:
public async Task Invoke(HttpContext context)
{
try
{
await _next(context);// action execution occurs in try block
}
catch (Exception ex)
{
// if any middleware has an exception(includes mvc action) handle it
}
}
I am use my own EF database using long as the Id field. I have created all the entities and the relevant custom stores.
However when registering a new user, it is trying to call SetPasswordHashAsync before it has called CreateAsync, which means that it is trying to write to a database record that doesn't exist yet and of course it is throwing an error.
It looks like the system is expecting to know the Id field before it has been written to the DB but until then it cannot know the Id as it is set to be an auto-incremental field.
What am I missing? Is this even possible?
Here is the code from the AccountController (taken from the MVC project template)
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new User { UserName = model.Email, Email = model.Email };
var result = await userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
// We do not want the user to be logged in as soon as they register. We want them have to go through the login process.
//await SignInManager.SignInAsync(user, isPersistent:false, rememberBrowser:false);
// 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, "Confirm your account", "Please confirm your account by clicking here");
return View("DisplayEmail");
}
AddErrors(result);
}
// If we got this far, something failed, redisplay form
return View(model);
}
and here is the code from UserManager (from Identity.core) that is called
public virtual async Task<IdentityResult> CreateAsync(TUser user, string password)
{
ThrowIfDisposed();
var passwordStore = GetPasswordStore();
if (user == null)
{
throw new ArgumentNullException("user");
}
if (password == null)
{
throw new ArgumentNullException("password");
}
var result = await UpdatePasswordInternal(passwordStore, user, password).WithCurrentCulture();
if (!result.Succeeded)
{
return result;
}
return await CreateAsync(user).WithCurrentCulture();
}
So you can see that updating the password before calling createUser is the intended process, but how is that meant to work? That just seems wrong to me.