Sending Email in Razor Pages

4 (4 votes)

The Razor Pages framework is not the only thing to be introduced as part of .NET Core 2.0. A lot more of the existing full framework class libraries are also being ported across to .NET Core including System.Net.Mail. That means there is no longer any need to rely on third party libraries or services for email functionality in your .NET Core applications. So I thought I'd take the opportunity to illustrate sending email in a Razor Pages application.

I'm going to show two approaches to this task. The first will adopt the approach that is more likely to be seen in Web Forms or Web Pages applications. The second approach is much more structured and will involve the use of a "service", and is intended to show how Razor Pages supports clean separation of concerns - especially as Microsoft are now recommending Razor Pages as the Go To option for server-side generation of HTML on .NET Core. Yes - that's right: Microsoft believe that Razor Pages is a "vastly superior way of doing server-side HTML generation" [compared to MVC] and will be encouraging people to use it by default in 2.0. The next update to Visual Studio tooling will have the Razor Pages template as the default "Web" option.

Peparation

Back to emailing.... both approaches that I am going to show feature the Contact page that comes with the standard Razor Pages template. It comes with a PageModel "code-behind" file, which is the recommended way to work with Razor Pages. First, I am going to create a class that represents an email message:

public class Message 
{
    public string[] To { get; set; }
    public string From { get; set; }
    public string Subject { get; set; }
    public string Body { get; set; }
    public bool IsHtml { get; set; }
}

This is added as a property named Message to the ContactModel page model class, and the existing Message property (a string) is renamed "Welcome":

public class ContactModel : PageModel
{
    public string Welcome { get; set; }
    [BindProperty]
    public Message Message { get; set; }
    public void OnGet()
    {
        Welcome = "Your contact page.";
    }
}

The Message property is decorated with the BindProperty attribute, which ensures that it takes part in model binding, which I will explore in a future post. In the meantime, for those not familiar with model minding, it is a process whereby values associated with a request, particularly form values are assigned by the framework to properties that have been designated as targets for binding. It saves a lot of boiler-plate simple assignment code that would otherwise be required.

Next, I add a form to the Contact.cshtml content page, which makes use of taghelpers:

<form method="post">
    <label asp-for="Message.From"></label> <input type="text" asp-for="Message.From"/><br>
    <label asp-for="Message.To" ></label> <input type="text" asp-for="Message.To" /><br>
    <label asp-for="Message.Subject" ></label> <input type="text" asp-for="Message.Subject" /><br>
    <label asp-for="Message.Body"></label> <textarea asp-for="Message.Body"></textarea><br>
    <input type="submit"/>
</form>

Finally, I update the content of the <h3> heading in Contact.cshtml to reflect the new name for the string property:

<h3>@Model.Welcome</h3>

First Approach

The first approach includes the email generation and sending code in the page handler method for the POST request. Accordingly, it requires the addition of using System.Net.Mail; at the top of the page model file. Then all that is required is an OnPost handler method with familiar email generation code:

public async Task OnPost()
{
    using (var smtp = new SmtpClient())
    {
        smtp.DeliveryMethod = SmtpDeliveryMethod.SpecifiedPickupDirectory;
        smtp.PickupDirectoryLocation = @"c:\maildump";
        var message = new MailMessage
        {
            Body = Message.Body,
            Subject = Message.Subject,
            From = new MailAddress(Message.From)
        };
        message.To.Add(Message.To);
        await smtp.SendMailAsync(message);
    }
}

For the purposes of this example, I have used the SpecifiedPickupDirectory option for the delivery method, ensuring that the resulting email is created in a local folder. That way, it's easy to test that everything worked by simply looking in the specified folder. If you want to test with GMail or similar, you should use the settings recommended in my one of my previous posts covering emailing from ASP.NET.

Second Approach

The second approach sees the email generation and sending code moved out of the page handler and into a separate class representing a mail service. The page model class should only be responsible for processing input and returning an HTML response. It should not know about specific emailing libraries. In that way, it is very similar to a controller in MVC.

I create a folder named Services in the root of the site, and add a class file named IMailService.cs to it with the following content:

using System.Threading.Tasks;

namespace RazorPages.Services
{
    public interface IMailService
    {
        Task Send(Message message);
    }
}

Now I add another class file named SmtpMailService.cs that implements the interface and provides simple functionality to send emails via SMTP:

using System.Net.Mail;
using System.Threading.Tasks;

namespace RazorPages.Services
{
    public class SmtpMailService : IMailService
    {
        public async Task Send(Message message)
        {
            using (var smtp = new SmtpClient())
            {
                smtp.DeliveryMethod = SmtpDeliveryMethod.SpecifiedPickupDirectory;
                smtp.PickupDirectoryLocation = @"c:\maildump";
                var msg = new MailMessage
                {
                    Body = message.Body,
                    Subject = message.Subject,
                    From = new MailAddress(message.From)
                };
                msg.To.Add(message.To);
                await smtp.SendMailAsync(msg);
            }
        }
    }
}

If you compare the body of the Send method to the body of the OnPost handler method in the first approach, you can see that they are more or less identical. But now you've got emailing code that can be tested independently of the PageModel class, and that can be used elsewhere in the application without the need to copy and paste.

The mail service needs to be registered, which happens in the ConfigureServices method in the StartUp class (Startup.cs):

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
    services.AddTransient<IMailService, SmtpMailService>();
}

Finally, here is the revised page model class file content in its entirety:

using Microsoft.AspNetCore.Mvc.RazorPages;
using RazorPages.Services;
using System.Threading.Tasks;

namespace RazorPages.Pages
{
    public class ContactModel : PageModel
    {
        private readonly IMailService _mailSender;

        public ContactModel(IMailService mailSender)
        {
            _mailSender = mailSender;
        }

        public string Welcome { get; set; }
        [BindProperty]
        public Message Message { get; set; }

        public void OnGet()
        {
            Welcome = "Your contact page.";
        }

        public async Task OnPost()
        {
            await _mailSender.Send(Message);
        }
    }
}

The mail service is injected into the page model class via its constructor and is then assigned to a private field for later use in the OnPost handler method. All the page model class now knows is that something conforming to the IMailer interface will be provided at runtime via its constructor. It doesn't know what the actual implementation will be and it doesn't care. It no longer has a dependency on any specific mailing library. The using statement for System.Net.Mail has gone. If you decided at a later date to change the method of email generation to use e.g. Exchange Web Services, you can simply change the registration of the service in the ConfigureServices method to resolve to your alternative email library, so long as it conforms to the IMailer interface. The Contact page will continue to work without alteration.

Summary

The purpose of this post is, I suppose, three-fold. The first motivation was to highlght the fact that System.Net.Mail will be available in .NET Core 2.0, along with Razor Pages, and that there is no longer a need to hunt Nuget to satisfy your .Net Core emailing needs. The second purpose was to call attention to Damian Edwards remarks in the lastest ASP.NET Community Standup (about 25 minutes in) about Razor Pages being preferred for server-side HTML generation (web) applications. Finally, I wanted to take another opportunity to illustrate how easy it is to create a properly separated, testable, cross-platform web application with Razor Pages.

Date Posted:
Last Updated:
Posted by:
Total Views to date: 3483

5 Comments

- Gfw

Question... Does System.Net.Mail support SSL?

- Satyabrata Mohapatra

Thanks for sharing...learned a lot

- Mike

@Gfw

Yes. Set EnableSsl to true. In the example above, you enable it like this:

smtp.EnableSsl = true;

- Satyabrata Mohapatra

Bit off topic, but congratulation sir for your MVP award. You deserve it !!!

- Pam

Mike,
RazorPages sound like a nice choice for somebody still working in ASP classic who wants to move to something new. It almost looks like they are designed on purpose for that. I have tried more than once to look into MVC but the initial hill to climb always kept me wondering if I have to climb it at first place.

Recent Comments

Thomas 05/03/2018 00:59
In response to I'm Not Writing A Book On Razor Pages
There's a typo on this page: = true)] should be [BindProperty(SupportsGet = true)]...

Rolf Herbert 04/03/2018 19:25
In response to I'm Not Writing A Book On Razor Pages
So is MS deprecating razor Web Pages..? Is it dead..? I wish they would stop killing things so its...

Borut 17/02/2018 12:59
In response to I'm Not Writing A Book On Razor Pages
Mike, is it possible that Web Pages and Razor Pages "live" together in one web application? I a I...

hrboyce 09/02/2018 04:44
In response to I'm Not Writing A Book On Razor Pages
Mike, First thanks for doing this but I have to ask, any chance you would consider converting one of...

aziz sallam 07/02/2018 10:18
In response to I'm Not Writing A Book On Razor Pages
u are a great man...

Satyabrata Mohapatra 31/01/2018 11:36
In response to I'm Not Writing A Book On Razor Pages
This is a great news!!!! Thanks...

tangdf 30/01/2018 07:25
In response to I'm Not Writing A Book On Razor Pages
=> { o.ConfigureFilter(new IgnoreAntiforgeryTokenAttribute()); }); The extension method does...

Obinna Okafor 30/01/2018 04:02
In response to I'm Not Writing A Book On Razor Pages
Thank you very much. I would like to see a project built from scratch using Razor Pages. And it show...

rachida Dukes 31/10/2017 13:52
In response to Customising Identity in Razor Pages
Thanks again for this wonderful tutorial. I followed all the steps in this section called: Adding...

Rachida 31/10/2017 12:06
In response to Customising Identity in Razor Pages
Thanks very much for this wonderful tutorial, it helped a lot....