Search code examples
asp.net-mvcsendgridasp.net-identity-2

Have different version of SendEmailAsync


I am using the default ASP.NET MVC, Identity template... I want to send a confirmation email to my clients.

The default implementation which comes with a new project template, has a Register Method in AccountController.cs

 public async Task<ActionResult> Register(RegisterViewModel model)
    {
        if (ModelState.IsValid)
        {
            var user = new ApplicationUser { UserName = model.Email.Trim(), Email = model.Email.Trim(), FirstName = model.FirstName.Trim(), LastName = model.LastName.Trim() };
            var result = await UserManager.CreateAsync(user, model.Password);
            if (result.Succeeded)
            {
                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);
                string message = "Please confirm your account by clicking <a href=\"" + callbackUrl + "\">here</a>";
                await UserManager.SendEmailAsync(user.Id, "Confirm your account", HttpUtility.UrlEncode(message));

                return RedirectToAction("Index", "Home");
            }
            AddErrors(result);
        }

        // If we got this far, something failed, redisplay form
        return View(model);
    }

There is a call to UserManager.SendEmailAsync, now this method is defined in Microsoft.AspNet.Identity and I don't want to change it.

The actual send email function is in IdentityConfig.cs

public class SendGridEmailService : IIdentityMessageService
{
    public async Task SendAsync(IdentityMessage message)
    {
        var apiKey = ConfigurationManager.AppSettings["SendGridApiKey"];
        var client = new SendGridClient(apiKey);
        var msg = new SendGridMessage()
        {
            From = new EmailAddress("[email protected]", "DX Team"),
            Subject = message.Subject,
            PlainTextContent = message.Body,
            HtmlContent = message.Body
        };

        msg.TemplateId = /* I want to pass templateId here */
        msg.Personalizations[0].Substitutions.Add("confirmurl", /* I want to pass Username here */);
        msg.Personalizations[0].Substitutions.Add("confirmurl", /* I want to pass confirm url here */);

        msg.AddTo(new EmailAddress("[email protected]", "Test User"));
        var response = await client.SendEmailAsync(msg);

    }
}

Now as you see, I am using Sendgrid to send email... so I don't want a message.body to email... I have made some templates and I I want to pass teplate Id with some substituation tags, like username to be replaced in the template.

So I don't want this generic SendAsync method... I want something like

SendGridAsync(SendGridMessage message)

Is it possible to add this method, so I can choose when to call SendAsync and when to call SendGridAsync?


Solution

  • You don't need to use the built in mail service, especially when you want to do something that's a little more complicated.

    Define your own messaging service:

    public interface IMyMessageService 
    {
        Task SendConfirmationMessage(string confirmUrl, string to)
        // define methods for other message types that you want to send
    }
    
    public class MyMessageServie : IMyMessageService 
    {
        public async Task SendConfirmationMessage(string confirmUrl, string to)
        {
            var apiKey = ConfigurationManager.AppSettings["SendGridApiKey"];
            var client = new SendGridClient(apiKey);
            var msg = new SendGridMessage()
            {
                From = new EmailAddress("[email protected]", "DX Team"),
                Subject = message.Subject,
                PlainTextContent = message.Body,
                HtmlContent = message.Body
            };
    
            msg.TemplateId = /* I want to pass templateId here */
            msg.Personalizations[0].Substitutions.Add("confirmurl", confirmUrl);
    
            msg.AddTo(new EmailAddress(to, "Test User"));
            var response = await client.SendEmailAsync(msg);
    
        }
    }
    

    Register IMyMessageService in your DI framework, and inject it into the controller where the emails are being sent from (e.g. the AccountController).

    Now, your register action would look like this (assumes I've injected IMyMessageService and have an instance in _myMessageService):

    public async Task<ActionResult> Register(RegisterViewModel model)
    {
        if (ModelState.IsValid)
        {
            var user = new ApplicationUser { UserName = model.Email.Trim(), Email = model.Email.Trim(), FirstName = model.FirstName.Trim(), LastName = model.LastName.Trim() };
            var result = await UserManager.CreateAsync(user, model.Password);
            if (result.Succeeded)
            {
                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);
    
                // USE YOUR MESSAGE SERVICE
                await _myMessageService.SendConfirmationMessage(callbackUrl, user.Email);
    
                return RedirectToAction("Index", "Home");
            }
            AddErrors(result);
        }
    
        // If we got this far, something failed, redisplay form
        return View(model);
    }