0

I'm learning ASP .NET and I'd want to make a simple registration/login page.

I've created my public class AccountController : Controller in this way:

public class AccountController : Controller
{
    private readonly UserManager<ApplicationUser> _userManager;
    private readonly SignInManager<ApplicationUser> _signInManager;
    private readonly IEmailSender _emailSender;
    private readonly ILogger _logger;

    public AccountController(
        UserManager<ApplicationUser> userManager,
        SignInManager<ApplicationUser> signInManager,
        IEmailSender emailSender,
        ILogger<AccountController> logger)
    {
        _userManager = userManager;
        _signInManager = signInManager;
        _emailSender = emailSender;
        _logger = logger;
    }
    
    [HttpPost]
    [AllowAnonymous]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
    {
        ViewData["ReturnUrl"] = returnUrl;
        if (ModelState.IsValid)
        {
            // This doesn't count login failures towards account lockout
            // To enable password failures to trigger account lockout, set lockoutOnFailure: true
            var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false);
            if (result.Succeeded)
            {
                _logger.LogInformation("Utente loggato.");
                return RedirectToLocal(returnUrl);
            }
            if (result.RequiresTwoFactor)
            {
                return RedirectToAction(nameof(LoginWith2fa), new { returnUrl, model.RememberMe });
            }
            if (result.IsLockedOut)
            {
                _logger.LogWarning("User bloccato.");
                return RedirectToAction(nameof(Lockout));
            }
            else
            {
                ModelState.AddModelError(string.Empty, "Tentativo di login fallito.");
                return View(model);
            }
        }

        // If we got this far, something failed, redisplay form
        return View(model);
    }
    
    public IActionResult Register(string returnUrl = null)
    {
        ViewData["ReturnUrl"] = returnUrl;
        return View();
    }

    [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)
            {
                _logger.LogInformation("L'utente ha creato un nuovo account con una nuova password.");

                var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
                var callbackUrl = Url.EmailConfirmationLink(user.Id, code, Request.Scheme);
                await _emailSender.SendEmailConfirmationAsync(model.Email, callbackUrl);

                //diallowed signin for self registration, email should be confirmed first
                //await _signInManager.SignInAsync(user, isPersistent: false);
                _logger.LogInformation("L'utente ha creato un nuovo account con una nuova password.");
                return RedirectToConfirmEmailNotification();
            }
            AddErrors(result);
        }
        return View(model);
    }
    
    public async Task<IActionResult> ConfirmEmail(string userId, string code)
    {
        if (userId == null || code == null)
        {
            return RedirectToAction(nameof(HomeController.Index), "Home");
        }
        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);
        return View(result.Succeeded ? "ConfirmEmail" : "Error");
    }
}

Then I created Startup class and I've written the following method:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseBrowserLink();
        app.UseDeveloperExceptionPage();
        app.UseDatabaseErrorPage();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
    }

    app.UseStaticFiles();

    app.UseAuthentication();

    app.UseMvc(routes =>
    {
        routes.MapRoute(
            name: "default",
            template: "{controller=Landing}/{action=Index}/{id?}");
    });
}

When I register a new user I can see it into the database but I don't receive any mail and I can't log in with the new user if I try.

I've watched some tutorials on YouTube about it and read the Microsoft documentation. To me it seems correct compared with what I've done, but surely I have to modify something and I don't notice it.

EDIT: this is what I've done for EmailSender and NetcoreService class:

public class EmailSender : IEmailSender
{        
    private SendGridOptions _sendGridOptions { get; }
    private INetcoreService _netcoreService { get; }
    private SmtpOptions _smtpOptions { get; }

    public EmailSender(IOptions<SendGridOptions> sendGridOptions,
        INetcoreService netcoreService,
        IOptions<SmtpOptions> smtpOptions)
    {
        _sendGridOptions = sendGridOptions.Value;
        _netcoreService = netcoreService;
        _smtpOptions = smtpOptions.Value;
    }


    public Task SendEmailAsync(string email, string subject, string message)
    {
        //send email using sendgrid via netcoreService
        _netcoreService.SendEmailBySendGridAsync(_sendGridOptions.SendGridKey,
            _sendGridOptions.FromEmail,
            _sendGridOptions.FromFullName,
            subject,
            message,
            email).Wait();
            
        return Task.CompletedTask;
    }
}

NetcoreService class:

public async Task SendEmailBySendGridAsync(string apiKey, string fromEmail, string fromFullName, string subject, string message, string email)
{
    var client = new SendGridClient(apiKey);
    var msg = new SendGridMessage()
    {
        From = new EmailAddress(fromEmail, fromFullName),
        Subject = subject,
        PlainTextContent = message,
        HtmlContent = message
    };
    msg.AddTo(new EmailAddress(email, email));
    await client.SendEmailAsync(msg);

}
  • What's your `_emailSender` implementation? You left that out of your question, you need that for a [MCVE]. – mason Dec 02 '20 at 14:38
  • When you debug and step through your code, do the registration steps run? Are you seeing any errors? Did you implement the IEmailSender interface? – Adam Dec 02 '20 at 14:38
  • @AdamG yes, I can complete the registration. When I insert my email and pwd the program continues with the permissions assignment – Frankie Macca Dec 02 '20 at 14:59
  • @mason I edited the post with the EMailSender implementation – Frankie Macca Dec 02 '20 at 14:59
  • I suggest that you get rid of the `.Wait()` when you send email and instead put `async` in front of `Task SendEmailAsync` and put `await` in front of the call to `_netcoreService.SendEmailBySendGridAsync`. Finally, get rid of the return statement. (You can also not use `async` and `await` and return the task directly.) While I don't think any exceptions thrown while sending are lost when you call `Wait` it's not the right thing to do and to solve you problem you have to make sure that this method is called and that it succeeds. – Martin Liversage Dec 02 '20 at 15:04
  • @MartinLiversage I've just tested the modifies but the code still doesn't work. When I debug the code I've got the correct value for the email and I pass through `SendEmailAsync`. What else can I check into the code that I did wrong? – Frankie Macca Dec 02 '20 at 15:39
  • I'm not familiar with `SendEmailBySendGridAsync` - does it return a status code indicating success or failure? Is there some configuration you need to do within SendGrid itself? Since you're not getting an exception, it seems like the email is getting into SendGrid properly and so that's where the problem may lay. – mason Dec 02 '20 at 17:05
  • @mason yes, I implemented a method into NetcoreService class. I edited again my post :) – Frankie Macca Dec 03 '20 at 08:19
  • Why did you do .Wait() on your async method call instead of using await like you did elsewhere? – mason Dec 03 '20 at 12:55
  • @mason because I thought was more correct. Anyway, I've just edited the code, not the post, as suggested by Martin Liversage but it still doesn't work. I debugged several times and I arrive at the end of the code with the right value for each variable. I don't know what I did wrong, I can't understand – Frankie Macca Dec 03 '20 at 15:42
  • Using `.Wait()` is almost never correct. You should almost always be `await`ing your async calls. – mason Dec 03 '20 at 16:50

0 Answers0