2010年7月

Original Post: http://dotnetslackers.com/articles/aspnet/Sending-email-from-ASP-NET-MVC-through-MVC-and-MSMQ-Part1.aspx

Published: 22 Mar 2010
By: Andrew Siemer

In this article you will learn how to send emails using ASP.NET MVC and MSMQ.

Contents

  1. Introduction
  2. An example of sending mail directly to SMTP
    1. Creating the ASP.NET MVC web application
    2. Creating the email logging database
    3. Creating a quick business layer
    4. Adding LINQ to SQL support for data access
    5. Creating an email wrapper
    6. Logging to the database
    7. Hooking up our web page
    8. Ready to send an email?
  3. Summary

Sending email from ASP.NET MVC through MVC and MSMQ Part Series

Introduction

I have worked in a lot of different web environments ranging from high traffic social networking sites to high transaction ecommerce sites. One thing every site has in common regardless of the industry that it serves is that at some point in time it will need to send an email communication to someone. In the case of a community site you might send an email to a user when they comment on a friend's profile. For ecommerce sites you might send a confirmation email when someone purchases a product from your site.

Regardless of the reason that the email is being sent, sending it directly from your web sites worker process is generally a bad idea. Connecting to an SMTP server, especially one on a separate server, can be a long blocking process. Adding insult to injury, the poor guy that performed the initial button click of some kind is now stuck waiting for the next web page to return. And if you work in an environment that needs to log every communication with its customers (a common requirements these days), the customer may have to endure the wait for the sending of the email as well as a dip to an email logging database. Luckily for your customer there is a better method!

In this article we will take a look at all the normal steps required to send an email directly from an ASP.NET MVC web page. We will also discuss how to log all of our communications by serializing our email communications to a logging database with LINQ to SQL. Once we complete this article we will have a working codebase that we can then refactor to send email in a more efficient and disconnected manner through MSMQ.
An example of sending mail directly to SMTP

In the majority of consulting gigs I have been a part of I almost always come across a web page that connects directly to an SMTP server. It creates an instance of MailMessage, populates various properties of that message, spins up an instance of SmtpClient, and attempts to send the message. If the message was sent successfully then we can log it to a database. If the message doesn't send then we can log the failed attempt too.
Creating the ASP.NET MVC web application

Let's create a quick web application

that works in this fashion. To start we will create an ASP.NET MVC 2 application (in Visual Studio 2010). I am going to place mine in a source folder and call the application "Web Application" (clever eh?).

Creating the email logging database

Then we need to create the database for the logging side of our application. Right click on the App_Data folder in your MVC application and choose to add a new item. Then select a SQL Server Database (you need SQL Express installed for this to work). Name the database EmailLogging.mdf and click Add. The new database should then show up in the App_Data folder as well as under your Data Connections section of the Server Explorer.

Expand the database in the Data Connections window. Right click on the Tables folder and select add new table. Add a column named EmailLogID with an int data type and don't allow nulls. Then mark that column as a primary key (click the key above). Then scroll down in the Column Properties and set the (Is Identity) filed under the Identity Specification to Yes.

Then add a SentSuccessfully field with a data type of bit and don't allow nulls.

Next we will add a SentDate field with a data type
of date (about time we got this data type!).

And lastly we will add a Message field with a data type of varchar(500).

Now you can save your table. Name the table EmailLogs in the window that pops up.

Creating a quick business layer

Now we can add a new class library to manage our data access and business logic. Do this by right clicking on your solution and selecting to add a new project. Choose to add a new Class Library project. Name this project BusinessLayer. Then add a reference from your WebApplication project to your BusinessLayer project. This can be done by right clicking on the WebApplication project and selecting add reference. In the project tab, select the BusinessLayer project.

Adding LINQ to SQL support for data access

The quickest way to achieve data access these days is through the uses of LINQ to SQL! Add a folder to the BusinessLayer project called Domain. Then right click on that folder and choose to add a new item. Then add a LINQ to SQL Classes item and call it Logging.dbml.

NOTE

The name you choose here will be used to generate a {nameYouPick}DataContext class. If you choose AndrewsEmailLoggingContext.dbml you will end up with an AndrewsEmailLoggingContextDataContext class that you have to do all your data access through. Buyer beware!

Now you can drag the EmailLogs table we create out of the Data Connections->EmailLogging.mdf->Tables section of the Server Explorer on to the design surface of your Logging.dbml. This will generate an EmailLogging class for you. Now save your Logging.dbml file. Then build your project so that your LoggingDataContext will be created for you as we will need it in upcoming steps.

Why did you put the LINQ to SQL classes in the Domain folder?

Good question. The LINQ to SQL classes item sort of leaks it's concerns throughout your application so you have to know how to pick the lesser of two evils when deciding where to put this thing. LINQ to SQL performs (at least) two functions for you. 1) It creates the classes that represent your database objects. 2) It provides you with the tools you need to query the database with.

What this means to you from a code organization perspective is that you either have Domain objects that live in a DataAccess folder (ugly) or code that you never directly reference in your application that lives in your Domain folder. What I mean to say is that you will use a Repository class that you create and that Repository class will have a reference to Domain.LoggingDataContext. Equally ugly but you only have to touch it when building the repositories.

If this discussion is boring you then it doesn't apply to you. If this discussion has you thinking then you should probably use the Entity Framework or NHibernate! :P

Creating an email wrapper

Next we need to create a quick wrapper class that will handle sending email for us. We will do this by creating a new folder in our BusinessLayer project called Services. Inside that we can create an EmailService class. We will use this class as the entry point into sending our email directly through SMTP. Make this class public and add a method called SendMessage.

Listing 1: EmailService.cs

public void SendMessage(EmailMessage message, string username, string password, string host, int port) { }

Use ReShaper to save you some time!

If you are a big ReSharper fan you might just implement the usage of the EmailMessage directly in the SendMessage method and then use ReSharper to create the EmailMessage class for you (must faster).

Now go to the Domain folder and create a new class called EmailMessage.
Listing 2: EmailMessage.cs

namespace AndrewSiemer.BusinessLayer.Domain { public class EmailMessage { public string To { get; set; } public string From { get; set; } public string Subject { get; set; } public string Message { get; set; } } }

Back in your EmailService we can now map our EmailMessage properties into the constructor of a MailMessage instance.

Listing 3: EmailService.cs

MailMessage mm = new MailMessage(message.From, message.To, message.Subject, message.Message);

After that we can create an instance of NetworkCredential with the passed in username and password. And then we can create an instance of SmtpClient with the host and port. And then we can bring those two together by passing in the credentials to the SmtpClient.

Listing 4: EmailService.cs

public void SendMessage(EmailMessage message, string username, string password, string host, int port, bool enableSsl) { MailMessage mm = new MailMessage(message.From, message.To, message.Subject, message.Message); NetworkCredential credentials = new NetworkCredential(username, password); SmtpClient sc = new SmtpClient(host, port); sc.EnableSsl = enableSsl; sc.Credentials = credentials; }

Once this is completed we are ready to try to send our message with a call to SmtpClient.Send().

Listing 5: EmailService.cs

try { sc.Send(mm); //add to logging db } catch (Exception) { //add to logging db throw; } SmtpClient.SendAsync()

If you really just want to send email from your page "because that is how you have always done it" you should at least upgrade to .NET 4 and use the SendAssync method (http://msdn.microsoft.com/en-us/library/system.net.mail.smtpclient.aspx)

Now we can create one last method to serialize a mail message for us which we can then pass into our logging database.
Listing 6: EmailService.cs

public static string SerializeMessage(EmailMessage message) { string result = ""; StreamWriter tw = new StreamWriter(result); XmlSerializer x = new XmlSerializer(message.GetType()); x.Serialize(tw, message); return tw.ToString(); }

And now we can serialize our mail message to be logged by adding this line to our SendMessage method.

Listing 7: EmailService.cs

public void SendMessage(EmailMessage message, string username, string password, string host, int port) { string serializedMessage = SerializeMessage(message); MailMessage mm = new MailMessage(message.From, message.To, message.Subject, message.Message); }

Logging to the database

Now we are ready to build up a repository for our logging purposes. Add a new folder to the business layer called DataAccess. Then add a class called LoggingRepository. Then add a method called LogMessage. In this method we will connect to our database and insert our serialized message.

Listing 8: LoggingRepository.cs

public class LoggingRepository { public void LogMessage(string message, bool sentSuccessfully) { string connectionString = @"Data Source=.\SQLEXPRESS;AttachDbFilename={pathToDatabase}\EmailLogging.mdf;Integrated Security=True;User Instance=True"; using(LoggingDataContext dc = new LoggingDataContext(connectionString)) { EmailLog el = new EmailLog(); el.Message = message; el.SentDate = DateTime.Now; el.SentSuccessfully = sentSuccessfully; dc.EmailLogs.InsertOnSubmit(el); dc.SubmitChanges(); } } }

Now we can return to the EmailSerivce and wire up our LoggingRepository. Start by adding another parameter to our SendMessage method that takes in an instance of our new LoggingRepository. Then replace the "//add to logging db" comments with a call to the LoggingRepository.LogMessage method.

Listing 9: EmailService.cs

try { sc.Send(mm); repository.LogMessage(serializedMessage, true); } catch (Exception) { repository.LogMessage(serializedMessage, false); throw; }

Hooking up our web page

Now we can return to our web application. Open the Views/Home folder and then open the Index.aspx view page. We are going to add a form with a button that will allow us to catch a post to an action that will attempt to send an email for us.

Listing 10: Index.aspx

<% Html.BeginForm("Index", "Home"); %> <% Html.EndForm(); %>

With this in place we can then navigate to our HomeController. In the HomeController we need to add a new action to catch the form submission from the Index view. Make sure that you specify the HttpPost attribute! Without it our form post won't find this new action.

Listing 11: HomeController.cs

[HttpPost] public ActionResult Index(string id) { return View(); }

Once we have our new action in place we can then wire up our email sending code! We need to create a new instance of a MailMessage. Then we need to populate the various properties of our MailMessage. From there we can create an instance of EmailService and pass in all the various properties that your SMTP server requires.

Listing 12: HomeController.cs

[HttpPost] public ActionResult Index(string id) { EmailMessage em = new EmailMessage(); em.Subject = "test message"; em.Message = "howdy from asp.net mvc"; em.From = "[email protected]"; em.To = "[email protected]"; new EmailService().SendMessage(em, "{email account}", "{password}", "{smtp server address}", {smtp port}, {ssl or not}, new LoggingRepository()); return View(); }

Ready to send an email?

With all of these steps completed you should now be able to hit F5 and run your web application. When it pops open you will have a "SendEmail" button on your form just begging to be clicked. When you click it an email should be sent (or not depending on lots of things). And regardless of the status of your email being sent you should get a serialized EmailMessage logged into your database. If the email was sent you will probably notice that it took at least several seconds to perform its operation. If your email server was not available then you would have noticed that the server took around a minute (give or take) to tell you that it wasn't available for sending messages. Add to that the latency of logging the message into the database and you have a slow process no matter how successful it was!

Listing 13: Serialized EmailMessage from the EmailLogs table

[email protected] [email protected] test message howdy from asp.net mvc

Summary

In this article we discussed some of the issues with sending email directly out of a page in your web application. We then jumped into creating an example ASP.NET MVC application that would send email in this manner. Our example also serialized all of our communications and logged them into a logging database using LINQ to SQL.

In the next article we will take a look at the steps required to move away from this thread blocking way of sending email. We will then implement a disconnected method of sending email communications using MSMQ.