NerdDinner On Azure – Take 2

At last week’s Greater Buffalo IT/Dev Day, I did an overview of the Windows Azure Platform (the recently rebranded Azure Services Platform).  Rather than do a tired “Hello World” example, I thought it would be more interesting to talk about a more substantial application, so NerdDinner came to mind.  The fact that it’s a great application to talk about ASP.NET MVC was just a plus, and although we didn’t focus on MVC, the context of the talk highlighted the fact that an ASP.NET MVC application at some level is “just another .NET web app”.

The sample code I used is available on my SkyDrive, but I wanted to give a little bit of insight into how it was developed, as well as give credit where credit is due. 

Michael Papasevastos, a colleague at Microsoft, did most of the work!  Back in June he blogged about the steps he took to port NerdDinner to Azure, and he made the source code available as well.

To that starting point, I made a few modifications.

Rebranding

NerdBytesI rebranded the application to NerdBytes, in part to eliminate confusion among the various tweaks that folks have made to NerdDinner, and also since nerddinner.cloudapp.net was already claimed by Michael’s app!

Azure Table Storage changes

I noticed an issue in Michael’s choice of PartitionKey and RowKey for Azure Table Storage.  The scheme he proposes doesn’t handle multiple dinners at the same date and time (the RSVPs used only the date and time of the event as the foreign key).  That was easily handled via the introduction of GUIDs and minor changes to the existing code.  So, in my implementation I use:

Entity PartitionKey RowKey
Dinner GUID “Host:” + host’s user ID
RSVP GUID (from Dinner) attendee user ID

Wondering why I preface the RowKey of Dinner with the text "Host: "? When you host a dinner, the business logic is to automatically include a RSVP for yourself.  So, presume that Dinner’s RowKey were just the host’s user id.  If that were the case, we’d  now have a record in Dinner and a record in RSVP with exactly the same PartitionKey and RowKey combination. 

In development storage, it doesn’t immediately appear to be a big deal, because there Azure storage is ‘faked’ by using local relational database tables, and Dinner and RSVP are different tables.  In Azure storage though, there is no concept of schematized data, and every item is essentially a property bag uniquely identified by PartitionKey and RowKey.  Without the “Host: ” preface there would be a Dinner entity and an RSVP entity with the same key, and that is verboten, resulting in an exception (both in the Development Fabric and production).  Of course, this means that my design won’t support two dinners hosted by the same person at the same time, but since I haven’t yet figured out how to break the time-space continuum, it’s not an issue for me here.

In this particular case, I’m not focusing on scalability per se, but here’s an interesting look at how the choices of PartitionKey and RowKey can affect your application’s ability to scale.  In this case, I’m actually in lock-step with the post’s conclusions, since the GUID is the primary key.

Worker Role Addition

The original implementation of NerdDinner is a fairly compact Web application, but for my demo, I really wanted to introduce a worker role and use Azure’s queue storage to communicate between it and the existing Web role.  To that end, I added a feature that e-mails the host of the dinner whenever a new RSVP has been recorded.

To implement an e-mail agent in Azure, take a look at fellow ‘softie David Lempher’s blog article.  It’s pretty much what I stole researched for my own implementation; I used my live.com account as the SMTP relay, and included the information as part of the serviceConfiguration.cscfg file.  In code, I rely on the RoleManager.GetConfiguration setting to access the SMTP account information, and since it’s in the configuration file, I can modify the settings in production via the Windows Azure Platform portal without bringing down my application.

As I mentioned, to communicate between the Web role and worker role, I want to use Azure queue storage.  Within the ASP.NET MVC Web role code (RSVPController.cs), I added a call to a new method (line 12 below) to send an e-mail each time an RSVP is recorded.

 

    1:  public ActionResult Register(string partitionKey)
    2:  {
    3:      Dinner dinner = dinnerRepository.
    4:          GetDinner(partitionKey);
    5:   
    6:      if (!dinnerRepository.IsUserRegistered
    7:          (dinner, User.Identity.Name))
    8:      {
    9:          RSVP rsvp = new RSVP(dinner.PartitionKey, 
   10:              User.Identity.Name);
   11:   
   12:          dinnerRepository.AddRSVP(rsvp);
   13:          dinnerRepository.Save();
   14:   
   15:          SendRsvpEmail(dinner, rsvp);
   16:      }
   17:   
   18:      return Content("Thanks - we'll see you there!");
   19:  }

“Sending the e-mail” here really means putting a new message on a queue (one that my worker role is listening to).  Below is the implementation of SendRsvpEmail.  The queue name I’ve selected here is ‘rsvps’, and the code for the first 10 lines or so is more or less boilerplate to get a handle to the queue and ensure it exists before posting messages to it.

 

    1:  private void SendRsvpEmail(Dinner dinner, RSVP rsvp)
    2:  {
    3:      Boolean queueCreated = false;
    4:      Boolean queueExists = false;
    5:      QueueStorage queueStorage = QueueStorage.Create
    6:        (StorageAccountInfo.
    7:         GetDefaultQueueStorageAccountFromConfiguration());
    8:      MessageQueue queue = queueStorage.GetQueue("rsvps");
    9:   
   10:      queueCreated = queue.CreateQueue(out queueExists);
   11:      if (queueCreated || queueExists)
   12:      {
   13:          String RsvpXml = new XDocument(
   14:           new XElement("Rsvp",
   15:             new XElement("DinnerName", dinner.Title),
   16:             new XElement("DinnerDate", dinner.EventDate),
   17:             new XElement("Attendee", rsvp.AttendeeName),
   18:             new XElement("HostName", dinner.HostedBy),
   19:             new XElement("HostEmail",
   20:                membershipService.GetUserEmail(
   21:                   dinner.HostedBy))
   22:             )
   23:          ).ToString();
   24:   
   25:          queue.PutMessage(new Message(RsvpXml));
   26:          RoleManager.WriteToLog("Information", RsvpXml);
   27:     }
   28:  }

The format of the message put in the queue is XML, and I used LINQ to XML to include the name of the dinner, the date, the attendee name, host, and the email of the host in the message (lines 13-23).  The message then gets put onto the queue in line 25 and logged in the next line.

On the other end, my worker role is polling for new messages on the ‘rsvps’ queue, and when it finds one, begins processing it.  Here’s the code extracted from the polling loop of my worker role.  It’s as simple as pulling the message off the queue, reconstituting it from the XML (I declare a simple DinnerDetails data transfer object to hold the information), and then calling the method in the worker role that actually sends the e-mail via the SMTP client (SendEMail).

    1:  Message msg = queue.GetMessage();
    2:  if (msg != null)
    3:  {
    4:      // message should be an XML document
    5:      XDocument msgXml = XDocument.Parse(
    6:        msg.ContentAsString());
    7:      RoleManager.WriteToLog("Information",
    8:        msgXml.ToString());
    9:   
   10:      // parse out dinner details from XML message
   11:      DinnerDetails rsvp = 
   12:        (from m in msgXml.Descendants("Rsvp")
   13:        select new DinnerDetails()
   14:          {
   15:             DinnerName = m.Element("DinnerName").Value,
   16:             DinnerDate = m.Element("DinnerDate").Value,
   17:             Attendee = m.Element("Attendee").Value,
   18:             HostName = m.Element("HostName").Value,
   19:             HostEmail = m.Element("HostEmail").Value
   20:          }
   21:      ).FirstOrDefault();
   22:   
   23:      // send e-mail
   24:      SendEMail(rsvp);
   25:   
   26:      // delete the message from the queue
   27:      queue.DeleteMessage(msg);
   28:  }

Here’s the crux of the SendEMail method; there’s a bit more error checking in the download.

 SmtpClient client;
 MailMessage message;
  
 // set up SMTP Relay
 client = new SmtpClient(SMTPHost, SMTPPort);
 client.EnableSsl = true;
 client.Credentials = new NetworkCredential(
    SMTPAccount, SMTPPassword);
  
 // populate new message
 message = new MailMessage(
    new MailAddress("rsvp@nerdbytes.net"), 
    new MailAddress(rsvp.HostEmail));
 message.Subject = "NerdBytes RSVP";
 message.Body = rsvp.HostName + "," +
     Environment.NewLine + Environment.NewLine +
     rsvp.Attendee + " has RSVP'd for '" + 
     rsvp.DinnerName + "' on " +
     DateTime.Parse(rsvp.DinnerDate).ToShortDateString() +
     " at " +   
     DateTime.Parse(rsvp.DinnerDate).ToShortTimeString();

Now, in order to get the host's email address, I modified the Dinners controller (DinnersController.cs in the ASP.NET MVC application/Web role) as well, so that the IMembershipService was dependency-injected along with the Dinner repository service.  That necessitated a few additional code changes in the original port that Michael did.

 public class DinnersController : Controller {
  
     IDinnerRepository dinnerRepository;
     IMembershipService membershipService;
  
     //
     // Dependency Injection enabled constructors
  
     public DinnersController()
         : this(new DinnerRepository(),
                new AccountMembershipService())
     {
     }
  
     public DinnersController(IDinnerRepository repository,
        IMembershipService membership)
     {
         dinnerRepository = repository;
         membershipService = membership;
     }

And that was pretty much it.  Note, if you’re following along, the NerdDinner project on CodePlex has advanced a bit since Michael worked on his port to Azure, so code may look a bit different, but the approach is the same.

The other, and pretty major, thing to note is that all the work he did to set up Azure storage will largely be unnecessary once SQL Azure is available (late summer).  SQL Azure is essentially SQL Server in the cloud, and would allow me to store the NerdDinner data (and the ASP.NET membership database) in the cloud in the same relational format that the original authors used.  That alone would probably have cut out 2/3rds of the work (or more) for the original port to Azure.

 

Mobilizing NerdBytes

But there’s more… My other session at the Greater Buffalo IT/Dev Day was on Windows Mobile Development.  While I spent most of the time there talking about and building Compact Framework applications, I wanted to touch on mobile for the web as well – including new features in “IE6 on 6”, widgets, and Silverlight.  For the IE6 aspect, what better way to show the features than the cloud adaptation of NerdDinner.

Desktop mode As you may know, Mobile IE has a Desktop and a Mobile rendering mode option.  When using the Desktop mode, Mobile IE sends the same user-agent string as the desktop version of IE, so you get a display something like that on the right – functional but not optimal for the device’s form factor.

To improve the user experience, we need to introduce a different user-interface for those on mobile devices, one triggered by the “Mobile” mode of IE.  That’s where this post by Scott Hanselman comes in.  He’d already done the work and checked it in… alas to a build that was after what Michael had ported to Azure (which was my starting point).  Since I was committed to the Azure port, I cobbled together incorporated Scott’s work to my existing code base, so what you get in the download is not quite what he blogged about.. but close.

NerdBytes home NerdBytes list To the left are a few screens from the mobile views of my NerdBytes  ASP.NET MVC application.  Scott goes into details on the implementation, but it was cool to see how for the most part all that was affected in the implementation was the views, another testament to the separation-of-concerns philosophy espoused by ASP.NET MVC.

By the way, I wandered on to a very helpful utility when doing the work with the mobile device.  Since my application was deployed to the cloud, waiting for the deployment (which can take several minutes) to see what the site looked like wasn’t very productive.  Bayden UAPick is a very cool IE add-on that allows you to modify what user-agent string is sent by the browser.  So, for testing I could work with my Azure Development Fabric on my laptop and test with IE8, but use UAPick (which installs itself in IE as a menu item under Tools) to spoof the user-agent string sent by a mobile device (or other browsers for that matter) – like this:

IE8 with Mobile UA String

 

Well, that pretty much covers how I got to where I did.  NerdBytes is live now, so if you want to test it out while looking over the code, feel free.  Or load it up yourself in your own little spot in the cloud – Windows Azure is still in CTP, and more importantly FREE!, until PDC in November.  Also, drop me a line if you’re running into trouble, I undoubtedly left out some critical detail in my overview here!