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.