As a first foray into the Microsoft Azure Services Platform, I decided to try my hand at converting an application to run in the Microsoft Cloud. I chose NerdDinner as the application to convert because it was a fairly small and easy to understand application. NerdDinner was first released as a sample app for the book Professional ASP.NET MVC 1.0.
Since the ASP.NET MVC bits are not installed on Windows Azure instances, I needed to include a local copy of the DLLs with the project.
For storage, I planned on using the Windows Azure Table Service for storage. You can access the Table Service API in the following ways:
The .NET Client Library is just a layer on top of the REST API and makes things a little easier. I decided to use the .NET Client Library so therefore I needed to include a reference to the System.Data.Services.Client namespace.
I’m also going to include two sample projects into the solution. These sample projects came with the Windows Azure SDK and they are the StorageClient and AspProviders libraries.
The StorageClient is a library that sits between the .NET Client Library for ADO.NET Data Services and the NerdDinner application. Think of it as a Windows Azure Table Service-specific helper library. You can read more about the sample here.
The AspProviders project is provides sample implementations of ASP.NET providers that can run on Azure. Since NerdDinner uses an ASP.NET membership provider, I needed something equivalent. You can read more about that sample here.
Once these two projects were added to the solution, I added a project reference to each from the NerdDinner project.
I also need to tell Windows Azure a little about the application I wish to host. This is done through a new project type that gets added to Visual Studio when you install the Azure SDK. What I needed was to add a Blank Cloud Service project type to the solution:
A Cloud Service project type basically defines what your hosted service looks like to Windows Azure. Once you have this project type in your solution, you can add Web or Worker roles to it, which are basically references to other projects. See Understanding Service Architecture for more information on Web and Worker roles in Azure.
In order to add NerdDinner as a Web role, I needed to modify the .csproj file to include a <RoleType> element. For more information on this, see this blog post.
My final solution structure looked like the following:
The next step was to modify the Web.config file in NerdDinner. I needed to tell the app that I wanted to use the TableStorageMembershipProvider instead of the AspNetSqlMembershipProvider. I also needed to include some basic application settings for local development.
Initially I put these <appSettings> values in Web.config, but down the road I learned it was better to actually put these values in the local ServiceConfiguration.cscfg file. The reason for this is that if they are in the .cscfg file, you can update the configuration in the Cloud without having to redeploy your application.
Therefore, I created another .cscfg file which I added as a Solution Item that I use to configure the application when I deploy it to the Cloud. The .cscfg file within the NerdDinner_Azure Cloud Service project contains settings for local development. This is convenient because when I run locally, I can just leave those settings alone. Here is my local ServiceConfiguration.cscfg file:
<ServiceConfiguration serviceName="NerdDinner_Azure" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceConfiguration">
<Instances count="1" />
<Setting name="AccountName" value="devstoreaccount1"/>
<Setting name="AccountSharedKey" value="Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="/>
<Setting name="TableStorageEndpoint" value="http://127.0.0.1:10002"/>
<Setting name="BlobStorageEndpoint" value="http://127.0.0.1:10000/"/>
<Setting name="QueueStorageEndpoint" value="http://127.0.0.1:10001"/>
Because the relational version of SQL Data Services technology wasn’t available to me yet, I did not want to spend time learning SDS when Azure Table Storage would work just fine for my purpose. Had the new version of SQL Data Services been available, it would have made this conversion considerably easier as there would have been fewer changes to the model. Oh well…
Since we can’t use LINQ-to-SQL, I blew away any files associated with that approach and instead created the following classes:
Note that the entity classes, Dinner and RSVP, derive from TableStorageEntity and that NerdDinnerDataContext derives from TableStorageDataServiceContext. These base classes are part of the StorageClient library. From the class TableStorageEntity, the entity classes inherit the PartitionKey, RowKey and Timestamp properties.
The PartitionKey and RowKey properties are required when using the Windows Azure Table Service. These are strings and together form the unique key for an entity.
Choosing PartitionKey and RowKey values can have significant implications in scalability, which I won’t go into here.
For my purposes, I decided on the following PartitionKey and RowKey combinations for the entites:
RSVP.RsvpDate + “-“ + RSVP.AttendeeName
Since I now had to refer to entities by using PartitionKey and RowKey, I had to modify the Controller classes and Views in NerdDinner to refer to an entity using this combined key. I also had to modify the ViewDataModel classes as well as the JsonDinner class representing the JSON data structure used during the AJAX requests.
Also, because of the limitations inherent with the current version of the Windows Azure Table Service, I had to get a little creative in the data access functionality. For instance, the OrderBy LINQ operator is not supported by the Table Service. Therefore, I had to make sure that the ordering of the sequence in my LINQ queries was done in memory and not remotely. Here’s a little trick that worked nicely for me in one of the controllers:
IEnumerable<Dinner> dinners = dinnerRepository.FindUpcomingDinners().AsEnumerable().OrderBy(dinner => dinner.EventDate);
I used AsEnumerable() to hide the custom operators and instead make the standard query operators available to me. This forced the query to execute and allowed me to use an in-memory sequence for the OrderBy method call as you see in the method syntax I’m using above. I could have also called ToList<T> or ToArray<T> to force immediate execution and cache the results in memory before calling OrderyBy.
Since the Count operator is also not supported, I had to work around that as well. Who would have thought it would be that difficult to ask for the RSVP count? Well, once you know the tricks, it’s not that bad, but it sure would have made it A LOT easier if these aggregate functions were supported out-of-the-box.
Unfortunately it was getting very late by the time I got to the paging functionality, so paging through a dinner list is not supported at this point. I’ll let the reader figure that one out J