One thing that many web sites do is to verify email addresses by sending you an email to complete registration. I decided to build a Registration system for ASP.NET MVC using Windows Workflow Foundation.
When you create a new ASP.NET MVC web site, the site comes with a simple account controller that integrates with ASP.NET Membership. It provides basic one step registration and log-in support. I wanted to take this much farther and provide a simple self-contained registration verification system.
When I plan work like this, my first step is to prepare the list of scenarios I'm working on so I don't get distracted and don't miss anything important
A user registers for the site with a valid email address
The user clicks on Register
A user receives the email verification message
For my platform, I chose Visual Studio 11, .NET 4.5 and ASP.NET MVC 4. However the same concepts will work fine with .NET 4.0 and MVC 3 given a few minor modifications.
For this step I simply searched account controller for isApproved. I found that by default when users are created, isApproved is set to true. In MVC 4 there are two methods that create users they are Register and JsonRegister. The modification is shown below.
For this step I’m going to need an activity that can send email and I want to supply a nicely formatted HTML email with the username embedded and an absolute URL to the Site.css stylesheet. For this example, I decided to create a SendMail activity that uses file based email templates. This allows me to treat the body of the HTML mail as content from the site perspective. To improve performance I cache the HTML files after they are read and check to see if the source file has changed before using a cached copy.
Using SmtpClient with AsyncCodeActivity was a particular challenge because the SmtpClient class uses an event based async model (EAP) and it took me a while to work out how to use a TaskCompletionSource with AsyncCodeActivity. Take a look at the SendMail.cs file for more details.
In the body of the email, I will have to include an absolute URL to the verification page including a verificationCode which is simply the InstanceId of the workflow. Given the enormous amount of data that can apply to an email message, I decided to create a type to pass between the MVC code and the Workflow which contains everything I need.
I want the HTML email to have links which must be fully qualified. I need links to the Site.css file so I can take advantage of styling in the email and the verification URL. To do this, I created the some extension methods to the UrlHelper class
Now when I want to get the fully qualified URL it is very simple
In the HTML email I want to merge two kinds of arguments. Some are supplied by the calling code in the BodyArguments array and some are generated automatically. The automatically generated elements can be referred to by name.
To keep the SendMail activity very generic, I moved the formatting of the message and merging of the arguments into the FormatMailBody activity. As you can see, when I want to use a generated value in the email such as the stylesheet URL in line 5 I place the keyword inside of double braces. If I want to refer to one of the Body arguments that my code created, I just use the typical positional references as in line 13.
Rather than ask the MVC developer to become an expert on WorkflowApplication, I created a helper class which accepts the Workflow type that you want to use as a template parameter. This allowed me to put in place a simple strongly typed API and hide the details of Workflow. For the Workflow, I’ve created a StateMachine that does everything I need. Of course, you can make the workflow more complex if you want. I can imagine scenarios where a Human might have to approve membership or perhaps there is a membership fee that must be collected, any of these things can be provided for in the StateMachine.
And of course, I’ve added support for Debug Tracing of the Workflow as it executes using Microsoft.Activities.Extensions. In the VS Debug window when the Workflow runs you will see nicely formatted trackiing information to help you.
41: Activity [1.34] "SendMail" scheduled child activity [1.90] "Wait For Confirmation" 42: Activity [1.34] "SendMail" scheduled child activity [1.62] "Sequence" 43: Activity [1.34] "SendMail" scheduled child activity [1.49] "Wait For Resend Command" 44: Activity [1.49] "Wait For Resend Command" is Executing { Arguments Command: SendMail } 45: Activity [1.62] "Sequence" is Executing 46: Activity [1.62] "Sequence" scheduled child activity [1.76] "Delay"
I like to create an enum that declares the set of commands that I’m going to use for my Workflow. In this case, there are a few simple commands.
Then, I used the same technique that I demonstrated in the Introduction To StateMachine Hands On Lab. I created an activity which waits for a command using the enum as the bookmark name.
For this example, I have decided against using Windows Server AppFabric because I want to (eventually) run this on Windows Azure. However it is quite simple to plug in the monitoring by launching a thread from the Application_Start method.
The sample code requires
Just look for TODO in the web.config file to find things.
You will first need to create the Workflow Instance Store database. I’ve provided some batch files to make things easier
CreateInstanceStore.cmd – Drops / Creates the instance store. Close IISExpress prior to running this to close the connection.
Reset.cmd – Removes all users from the ASP.NET Membership store, removes / recreates c:\mailbox and re-creates the instance store.
The email is configured to drop messages into c:\mailbox, however you can modify the config to use hotmail or your favorite email provider if you like.
The <appSettings> group includes two values
ReminderDelay – The timespan that the workflow will wait before sending a reminder email. For testing you should make this a small value. However keep in mind that after three reminders the account will be deleted so if you are debugging you should make this value longer.
InstanceDetectionPeriod – The number of seconds that the InstanceStore will wait before polling the database for changes.
Try other variations like not confirming or trying to log in before you have confirmed etc.