<feed xmlns="http://www.w3.org/2005/Atom"><title type="text">blog.smarx.com - cloud development blog</title><subtitle type="text">blog.smarx.com - cloud development blog</subtitle><id>uuid:cff305ce-44ca-4f37-bc8a-cd4f7ca863b4;id=1954</id><updated>2010-07-30T07:27:33Z</updated><link rel="alternate" href="http://blog.smarx.com/"/><link rel="next" href="?continuation=1!8!c21hcng-/1!80!MjUyMTMwNDIzNjM2NjgxMzkxNyBjYWxsLWRpYWdub3N0aWNtb25pdG9yLXN0YXJ0LW9ubHktb25jZQ--"/><entry><id>http://blog.smarx.com/atompub.svc/blog/posts/smooth-streaming-with-windows-azure-blobs-and-cdn</id><title type="text">Adaptive Streaming with Windows Azure Blobs and CDN</title><published>2010-07-29T22:22:40Z</published><updated>2010-07-29T22:22:40Z</updated><author><name>Steve Marx</name><uri>http://smarx.com</uri><email>steve.marx@microsoft.com</email></author><link rel="alternate" href="http://blog.smarx.com/posts/smooth-streaming-with-windows-azure-blobs-and-cdn"/><link rel="edit" href="smooth-streaming-with-windows-azure-blobs-and-cdn"/><content type="html">&lt;p&gt;In this post, I’ll show you how to use Windows Azure Blobs and the Windows Azure CDN to deliver adaptive streaming video content to your users in a format compatible with Silverlight’s Smooth Streaming player. For those who just want to try it out, head over to the&lt;font color="#800000"&gt; &lt;a href="http://code.msdn.com/streamingazure"&gt;Adaptive Streaming with Windows Azure Blobs Uploader project on Code Gallery&lt;/a&gt;&lt;/font&gt;. The instructions there will get you going.&lt;/p&gt;  &lt;h2&gt;Understanding Smooth Streaming&lt;/h2&gt;  &lt;p&gt;Before we get into the details of how adaptive streaming works on top of Windows Azure Blobs, it’s necessary to understand what Smooth Streaming is and how it works.&lt;/p&gt;  &lt;p&gt;Smooth Streaming is Microsoft’s HTTP-based adaptive streaming protocol. As Alex Zambelli’s wrote in his excellent “&lt;a href="http://learn.iis.net/page.aspx/626/smooth-streaming-technical-overview/"&gt;Smooth Streaming Technical Overview&lt;/a&gt;”:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;Adaptive streaming is a hybrid delivery method that acts like streaming but is based on HTTP progressive download. It's an advanced concept that uses HTTP rather than a new protocol.&lt;/p&gt;    &lt;p&gt;…&lt;/p&gt;    &lt;p&gt;In a typical adaptive streaming implementation, the video/audio source is cut into many short segments (&amp;quot;chunks&amp;quot;) and encoded to the desired delivery format… The encoded chunks are hosted on a HTTP Web server. A client requests the chunks from the Web server in a linear fashion and downloads them using plain HTTP progressive download.&lt;/p&gt;    &lt;p&gt;…&lt;/p&gt;    &lt;p&gt;The &amp;quot;adaptive&amp;quot; part of the solution comes into play when the video/audio source is encoded at multiple bit rates, generating multiple chunks of various sizes for each 2-to-4-seconds of video. The client can now choose between chunks of different sizes. Because Web servers usually deliver data as fast as network bandwidth allows them to, the client can easily estimate user bandwidth and decide to download larger or smaller chunks ahead of time. The size of the playback/download buffer is fully customizable.&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;In other words, HTTP-based adaptive streaming is about taking a source video, encoding it into lots of small chunks at various bitrates, and then letting the client play back the most appropriate chunks (based on available bandwidth).&lt;/p&gt;  &lt;p&gt;If you’ve ever looked at IIS Smooth Streaming content, though, you’ll notice that there aren’t lots of tiny chunks. There are a few, fairly large video files. Alex explains this too:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;IIS Smooth Streaming uses the MPEG-4 Part 14 (ISO/IEC 14496-12) file format as its disk (storage) and wire (transport) format. Specifically, the Smooth Streaming specification defines each chunk/GOP as an MPEG-4 Movie Fragment and stores it within a contiguous MP4 file for easy random access. One MP4 file is expected for each bit rate. When a client requests a specific source time segment from the IIS Web server, the server dynamically finds the appropriate Movie Fragment box within the contiguous MP4 file and sends it over the wire as a standalone file, thus ensuring full cacheability downstream.&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Smooth Streaming files are quite literally all those little chunks concatenated together. I encourage you to read Alex’s &lt;a href="http://learn.iis.net/page.aspx/626/smooth-streaming-technical-overview/"&gt;entire article&lt;/a&gt; to understand the exact file format and wire format.&lt;/p&gt;  &lt;p&gt;The key insight for our purpose is that &lt;em&gt;to the client, Smooth Streaming content is just many small video chunks.&lt;/em&gt; The beauty of this model is that Smooth Streaming works great with CDNs and caches in between the client and the server. To the client, all that matters is that small chunks of video are being served from the appropriate URLs.&lt;/p&gt;  &lt;h2&gt;Using Windows Azure Blobs as an Adaptive Streaming Host&lt;/h2&gt;  &lt;p&gt;Windows Azure Blobs can serve specific content at configurable URLs, which as we’ve seen is the only requirement to provide clients with an adaptive streaming experience. To sweeten the deal, there’s &lt;a href="http://blog.smarx.com/posts/using-the-new-windows-azure-cdn-with-a-custom-domain"&gt;built-in integration between Windows Azure Blobs and the Windows Azure CDN&lt;/a&gt;.&lt;/p&gt;  &lt;p&gt;All that’s left for us to do is to figure out the set of URLs a Smooth Streaming client might request and store the appropriate video chunks at those URLs. There are two files that will help us do that:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;&lt;a href="http://msdn.microsoft.com/en-us/library/ee230810(VS.90).aspx"&gt;The server manifest&lt;/a&gt; (*.ism) – This is a &lt;a href="http://en.wikipedia.org/wiki/Synchronized_Multimedia_Integration_Language"&gt;SMIL&lt;/a&gt; file that maps video and audio tracks to the file that contains them and the bitrates at which they were encoded. &lt;/li&gt;    &lt;li&gt;&lt;a href="http://msdn.microsoft.com/en-us/library/ee673438(VS.90).aspx"&gt;The client manifest&lt;/a&gt; (*.ismc) – This is an XML file that specifies to the client which bitrates and timestamps are available. It also specifies the URL template clients should use to request chunks. &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;The combination of these two files tells us everything we need to know to extract the video chunks and store them in Widows Azure Blobs.&lt;/p&gt;  &lt;p&gt;The &lt;a href="http://code.msdn.com/streamingazure"&gt;Adaptive Streaming with Windows Azure Blobs Uploader&lt;/a&gt; code first reads the server manifest and keeps track of the mapping of bitrate and content type (video or audio) to tracks within files. Then it reads the client manifest and generates all the permutations of bitrate, content type, and timestamp. For each of these, it looks up the appropriate track of the appropriate file, extracts that chunk from the file, and stores it in blob storage according to the URL template in the client manifest.&lt;/p&gt;  &lt;p&gt;The code’s not too complicated, and you can find it &lt;a href="http://code.msdn.com/smoothstreamingazure"&gt;in the Code Gallery project&lt;/a&gt; in &lt;code&gt;SmoothStreamingAzure.cs&lt;/code&gt;.&lt;/p&gt;  &lt;h2&gt;Prior Work&lt;/h2&gt;  &lt;p&gt;After I patted myself on the back for coming up with this brilliant scheme, it was pointed out to me that &lt;a href="http://aldenml.com/blog/2009/12/11/how-to-smooth-streaming-and-windows-azure-storage/"&gt;Alden Torres blogged about this back in December 2009&lt;/a&gt;. He used a tool on Codeplex called &lt;a href="http://mp4explorer.codeplex.com/"&gt;MP4 Explorer&lt;/a&gt;, which has a feature that allows uploading to blob storage. That tool reads the source MP4 files themselves and derives the chunks from there (as opposed to my approach, which reads the client manifest).&lt;/p&gt;  &lt;p&gt;The two big reasons I decided to write my own code for this were that I wanted a command-line tool and that I wanted to upload the blobs in parallel. I was able to cut down the upload time for the &lt;a href="http://www.microsoft.com/downloads/details.aspx?FamilyID=e44b0a2d-2e0c-48ff-bf57-3d05a20e2f6a&amp;amp;displaylang=en"&gt;Big Buck Bunny video&lt;/a&gt; from around three hours (as Alden mentions in his post) to around thirty minutes simply by doing the uploads in parallel.&lt;/p&gt;  &lt;h2&gt;Shortcomings of This Approach&lt;/h2&gt;  &lt;p&gt;To the client doing simple playback, there’s no difference between IIS Smooth Streaming (hosted by IIS Media Services) or Adaptive Streaming with Windows Azure Blobs. However, to the content owner and to the server, there are significant differences:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;With IIS on the server, scenarios that require server intelligence are possible (like real-time transcoding or encryption). &lt;/li&gt;    &lt;li&gt;There are fewer files to manage with IIS (since it keeps all the content in a small number of files). This makes copying files around and renaming them much simpler. &lt;/li&gt;    &lt;li&gt;As future features (like fast-forward and new targets like the iPad) come out, all you need to do is update IIS Media Services to get the new functionality. With a solution like the one described in this post, you’ll need to reprocess existing content. &lt;/li&gt;    &lt;li&gt;Because the manifest formats for IIS Smooth Streaming are actively evolving, there’s no guarantee that my code will work correctly with future Smooth Streaming clients and content. &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;Specifically, there are a few features of IIS Smooth Streaming that my code doesn’t handle today:&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;Trick play (fast-forward and rewind). This is supported under IIS by extracting keyframes from the video. My code doesn’t support extracting these keyframes. &lt;/li&gt;    &lt;li&gt;Live Smooth Streaming. Handling a live event (where the manifest is changing and the chunks include extra hints about future chunks) isn’t supported in my code. &lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;The Windows Azure team is still committed to running full IIS Media Services within Windows Azure web roles in the future.&lt;/p&gt;  &lt;h2&gt;Get the Tool&lt;/h2&gt;  &lt;p&gt;If you’d like to host Smooth Streaming content in Windows Azure Blobs, please check out the &lt;a href="http://code.msdn.com/smoothstreamingazure"&gt;Adaptive Streaming with Windows Azure Blobs Uploader&lt;/a&gt; project on Code Gallery, where you can download the command-line tool as well as the full source code.&lt;/p&gt;  &lt;h2&gt;Sample&lt;/h2&gt;  &lt;p&gt;Here's Big Buck Bunny served from cdn.blog.smarx.com:&lt;/p&gt; &lt;iframe style="width: 800px; height: 480px" src="http://cdn.blog.smarx.com/bbb/embed.html"&gt;&lt;/iframe&gt;  &lt;p&gt;&amp;#160;&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;[UPDATE 11:29pm] &lt;/strong&gt;The name of the tool has been changed to “Adaptive Streaming with Windows Azure Blobs Uploader”.&lt;/p&gt;  </content></entry><entry><id>http://blog.smarx.com/atompub.svc/blog/posts/deleting-windows-azure-queue-messages-handling-exceptions</id><title type="text">Deleting Windows Azure Queue Messages: Handling Exceptions</title><published>2010-07-02T04:11:53Z</published><updated>2010-07-02T04:11:53Z</updated><author><name>Steve Marx</name><uri>http://smarx.com</uri><email>steve.marx@microsoft.com</email></author><link rel="alternate" href="http://blog.smarx.com/posts/deleting-windows-azure-queue-messages-handling-exceptions"/><link rel="edit" href="deleting-windows-azure-queue-messages-handling-exceptions"/><content type="html">&lt;p&gt;This week’s episode of &lt;a href="http://channel9.msdn.com/shows/Cloud+Cover"&gt;Cloud Cover&lt;/a&gt; (scheduled to go live tomorrow morning) is all about Windows Azure queues. It’s a bit of a long episode, but there’s a lot of interesting technical content in there. During the show, a detail came up as &lt;a href="http://dunnry.com"&gt;Ryan&lt;/a&gt; and I were discussing queues and concurrency. At the time, I wasn’t sure exactly what guidance to give, so I committed to following up before the show went live.&lt;/p&gt;  &lt;p&gt;To understand the situation, remember that Windows Azure employs reliable queueing, meaning that it guarantees no message is lost without being handled by a consumer. That means that consuming queue messages is a two step process. First, the consumer &lt;a href="http://msdn.microsoft.com/en-us/library/dd179474.aspx"&gt;dequeues&lt;/a&gt; the message, specifying a &lt;em&gt;visibility timeout&lt;/em&gt;. At this point, the message is invisible and can’t be retrieved by other consumers. When the consumer is finished with the message, it &lt;a href="http://msdn.microsoft.com/en-us/library/dd179347.aspx"&gt;deletes&lt;/a&gt; it. If, however, the consumer is unable to finish processing the message, the visibility timeout will expire, and the message will reappear on the queue. This is what guarantees the message will eventually be handled.&lt;/p&gt;  &lt;p&gt;This leads us to the situation Ryan and I were discussing on the show. The scenario is as follows:&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;Instance #1 dequeues a message and starts working on it.&lt;/li&gt;    &lt;li&gt;The message’s visibility timeout expires, making it visible again on the queue.&lt;/li&gt;    &lt;li&gt;Instance #2 dequeues the message and starts working on it.&lt;/li&gt;    &lt;li&gt;Instance #1 finishes working on the message and tries to delete it.&lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;At that last step, an error will be returned from the queue service. This is because the first instance no longer “owns” the message; it’s already been delivered to another instance. (The underlying mechanism is a &lt;em&gt;pop receipt &lt;/em&gt;which is invalidated by the second dequeueing of the message.)&lt;/p&gt;  &lt;p&gt;The question I couldn’t answer on the fly during the show was how to accurately detect this error and handle it in code. After some discussion with the storage team, this is the .NET code I’m recommending people use to identify this error:&lt;/p&gt;  &lt;blockquote&gt;   &lt;pre class="code"&gt;&lt;span style="color: blue"&gt;try
&lt;/span&gt;{
    q.DeleteMessage(msg);
}
&lt;span style="color: blue"&gt;catch &lt;/span&gt;(&lt;span style="color: #2b91af"&gt;StorageClientException &lt;/span&gt;ex)
{
    &lt;span style="color: blue"&gt;if &lt;/span&gt;(ex.ExtendedErrorInformation.ErrorCode == &lt;span style="color: #a31515"&gt;&amp;quot;MessageNotFound&amp;quot;&lt;/span&gt;)
    {
        &lt;span style="color: green"&gt;// pop receipt must be invalid
        // ignore or log (so we can tune the visibility timeout)
    &lt;/span&gt;}
    &lt;span style="color: blue"&gt;else
    &lt;/span&gt;{
        &lt;span style="color: green"&gt;// not the error we were expecting
        &lt;/span&gt;&lt;span style="color: blue"&gt;throw&lt;/span&gt;;
    }
}&lt;/pre&gt;
&lt;/blockquote&gt;

&lt;p&gt;It would be nice if the storage client library included a constant for “MessageNotFound,” as it does for a number of other common error codes, but we can be confident that’s the right string by consulting the documentation on &lt;a href="http://msdn.microsoft.com/en-us/library/dd179446.aspx"&gt;Queue Service Error Codes&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Note that I’m not just checking for the HTTP 404 status code, because that could mean some other things (like an incorrect queue name). Looking for the “MessageNotFound” error code is more specific and thus better to use.&lt;/p&gt;

&lt;p&gt;Now go watch the latest &lt;a href="http://channel9.msdn.com/shows/Cloud+Cover"&gt;Cloud Cover&lt;/a&gt; episode!&lt;/p&gt;  </content></entry><entry><id>http://blog.smarx.com/atompub.svc/blog/posts/pivot-odata-and-windows-azure-visual-netflix-browsing</id><title type="text">Pivot, OData, and Windows Azure: Visual Netflix Browsing</title><published>2010-06-29T17:19:42Z</published><updated>2010-06-29T17:19:42Z</updated><author><name>Steve Marx</name><uri>http://smarx.com</uri><email>steve.marx@microsoft.com</email></author><link rel="alternate" href="http://blog.smarx.com/posts/pivot-odata-and-windows-azure-visual-netflix-browsing"/><link rel="edit" href="pivot-odata-and-windows-azure-visual-netflix-browsing"/><content type="html">&lt;p&gt;&lt;a href="http://cdn.blog.smarx.com/images/image%5B14%5D"&gt;&lt;img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; margin-left: 0px; border-left-width: 0px; margin-right: 0px" title="netflixpivot.cloudapp.net screenshot" border="0" alt="netflixpivot.cloudapp.net screenshot" align="right" src="http://cdn.blog.smarx.com/images/image_thumb%5B11%5D" width="340" height="260" /&gt;&lt;/a&gt; The &lt;a href="http://www.silverlight.net/learn/pivotviewer/"&gt;PivotViewer Silverlight control&lt;/a&gt; shipped this morning, which means you can now embed a &lt;a href="http://getpivot.com"&gt;Pivot&lt;/a&gt; collection (with great UI) directly in a web page. Pivot is fantastic for sorting, filtering, and browsing large numbers of items.&lt;/p&gt;  &lt;p&gt;I’ve put together my own example of using the new PivotViewer control at &lt;a href="http://netflixpivot.cloudapp.net"&gt;http://netflixpivot.cloudapp.net&lt;/a&gt;. It lets you browse the top ~3,000 movies that Netflix has available to stream online. I really encourage you to click through to the demo… it’s a fantastic way to find a movie to watch.&lt;/p&gt;  &lt;h2&gt;Technical Overview&lt;/h2&gt;  &lt;p&gt;The demo is built on Windows Azure and consists of a web role (which serves the web page itself), a worker role (which creates the Pivot collection once every hour or so), and blob storage, which hosts the collection and the Silverlight control (all behind the &lt;a href="http://msdn.microsoft.com/en-us/library/ee795176.aspx"&gt;Windows Azure CDN&lt;/a&gt;). The data comes from &lt;a href="http://developer.netflix.com/docs/oData_Catalog"&gt;Netflix’s OData feed&lt;/a&gt;.&lt;/p&gt;  &lt;p&gt;I only had to write about &lt;strong&gt;500 lines of code&lt;/strong&gt; to make this all happen, and I suspect that number would go down if I used the &lt;a href="http://pauthor.codeplex.com/"&gt;Pauthor library&lt;/a&gt; (which I didn’t have access to when I wrote this demo).&lt;/p&gt;  &lt;h2&gt;Creating the Pivot Collection&lt;/h2&gt;  &lt;p&gt;The Pivot collection is created by a worker role that only has a single instance. It takes more than an hour to process the latest Netflix feed into the form needed for Pivot. I could have parallelized some of this and spread the load across multiple instances, but the feed changes infrequently, so I’m not in any particular rush to get the work done. Using a single instance makes the code very simple, because everything happens locally on a single disk, but I have also built Pivot collections in the past using a large number of instances.&lt;/p&gt;  &lt;p&gt;The collection is created on the local disk in &lt;code&gt;NetflixPivotCreator.cs&lt;/code&gt;. The first step is loading all the available titles from the OData feed. To generate the &lt;code&gt;NetflixCatalog&lt;/code&gt; class, I just right-clicked on the worker role’s references and added a service reference to &lt;a href="http://odata.netflix.com/Catalog"&gt;http://odata.netflix.com/Catalog&lt;/a&gt;.&lt;/p&gt;  &lt;blockquote&gt;   &lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;context = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;NetflixCatalog&lt;/span&gt;(&lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Uri&lt;/span&gt;(&lt;span style="color: #a31515"&gt;&amp;quot;http://odata.netflix.com/Catalog&amp;quot;&lt;/span&gt;));
&lt;span style="color: #2b91af"&gt;DataServiceQueryContinuation&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Title&lt;/span&gt;&amp;gt; token = &lt;span style="color: blue"&gt;null&lt;/span&gt;;
&lt;span style="color: blue"&gt;var &lt;/span&gt;response = ((&lt;span style="color: blue"&gt;from &lt;/span&gt;title &lt;span style="color: blue"&gt;in &lt;/span&gt;context.Titles 
                 &lt;span style="color: blue"&gt;where &lt;/span&gt;title.Instant.Available &amp;amp;&amp;amp; title.Type == &lt;span style="color: #a31515"&gt;&amp;quot;Movie&amp;quot;
                 &lt;/span&gt;&lt;span style="color: blue"&gt;orderby &lt;/span&gt;title.AverageRating &lt;span style="color: blue"&gt;descending select &lt;/span&gt;title)
                &lt;span style="color: blue"&gt;as &lt;/span&gt;&lt;span style="color: #2b91af"&gt;DataServiceQuery&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Title&lt;/span&gt;&amp;gt;)
               .Expand(&lt;span style="color: #a31515"&gt;&amp;quot;Genres,Cast,Directors&amp;quot;&lt;/span&gt;)
               .Execute() &lt;span style="color: blue"&gt;as &lt;/span&gt;&lt;span style="color: #2b91af"&gt;QueryOperationResponse&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;Title&lt;/span&gt;&amp;gt;;
&lt;span style="color: blue"&gt;int &lt;/span&gt;count = 0;
&lt;span style="color: blue"&gt;var &lt;/span&gt;ids = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;HashSet&lt;/span&gt;&amp;lt;&lt;span style="color: blue"&gt;string&lt;/span&gt;&amp;gt;();
&lt;span style="color: blue"&gt;do
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;if &lt;/span&gt;(token != &lt;span style="color: blue"&gt;null&lt;/span&gt;)
    {
        response = context.Execute&amp;lt;&lt;span style="color: #2b91af"&gt;Title&lt;/span&gt;&amp;gt;(token);
    }
    &lt;span style="color: blue"&gt;foreach &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;title &lt;span style="color: blue"&gt;in &lt;/span&gt;response)
    {
        &lt;span style="color: blue"&gt;if &lt;/span&gt;(ids.Add(title.Id))
        {
            &lt;span style="color: blue"&gt;if &lt;/span&gt;(count &amp;lt; howMany)
            {
                &lt;span style="color: blue"&gt;yield return &lt;/span&gt;title;
            }
            count++;
        }
    }
    token = response.GetContinuation();
}
&lt;span style="color: blue"&gt;while &lt;/span&gt;(token != &lt;span style="color: blue"&gt;null &lt;/span&gt;&amp;amp;&amp;amp; count &amp;lt; howMany);&lt;/pre&gt;
&lt;/blockquote&gt;

&lt;p&gt;The next step is to download each title’s box art and create a Deep Zoom images out of it. This is a simplified version of that code:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;pre class="code"&gt;&lt;span style="color: #2b91af"&gt;Parallel&lt;/span&gt;.ForEach(GetTopInstantWatchTitles(3000),
    &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ParallelOptions &lt;/span&gt;{ MaxDegreeOfParallelism = 16 },
    (title) =&amp;gt;
{
    &lt;span style="color: blue"&gt;var &lt;/span&gt;boxArtUrl = title.BoxArt.HighDefinitionUrl ?? title.BoxArt.LargeUrl;
    &lt;span style="color: blue"&gt;var &lt;/span&gt;imagePath = &lt;span style="color: blue"&gt;string&lt;/span&gt;.Format(&lt;span style="color: #a31515"&gt;@&amp;quot;{0}\images\{1}.jpg&amp;quot;&lt;/span&gt;, outputDirectory, title.Id.ToHex());
    &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;WebClient&lt;/span&gt;().DownloadFile(boxArtUrl, imagePath);
    &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ImageCreator&lt;/span&gt;().Create(imagePath, &lt;span style="color: blue"&gt;string&lt;/span&gt;.Format(&lt;span style="color: #a31515"&gt;@&amp;quot;{0}\output\{1}.xml&amp;quot;&lt;/span&gt;, outputDirectory, title.Id));
});&lt;/pre&gt;
&lt;/blockquote&gt;

&lt;p&gt;Note the use of the &lt;a href="http://msdn.microsoft.com/en-us/library/dd460717.aspx"&gt;Task Parallel Library&lt;/a&gt;, which is an awesome way to make multi-threaded programming easy.&lt;/p&gt;

&lt;p&gt;From there, there are just one more line to create the full Deep Zoom collection:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;pre class="code"&gt;&lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;CollectionCreator&lt;/span&gt;().Create(
    titles.Select(t =&amp;gt; &lt;span style="color: blue"&gt;string&lt;/span&gt;.Format(&lt;span style="color: #a31515"&gt;@&amp;quot;{0}\output\{1}.xml&amp;quot;&lt;/span&gt;, outputDirectory, t.Id.ToHex())).ToList(),
    &lt;span style="color: blue"&gt;string&lt;/span&gt;.Format(&lt;span style="color: #a31515"&gt;@&amp;quot;{0}\output\collection-{1}.dzc&amp;quot;&lt;/span&gt;, outputDirectory, suffix));&lt;/pre&gt;
&lt;/blockquote&gt;

&lt;p&gt;At this point, I’m ready to create the actual Pivot collection (a &lt;code&gt;.cxml&lt;/code&gt; file that contains all the details about the movies). Check out the source code in the method &lt;code&gt;CreateCxml&lt;/code&gt; to see how this is done. (It’s just XML generation, probably made much simpler if I use the &lt;a href="http://pauthor.codeplex.com/"&gt;Pauthor library&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;Storing the Collection in Blob Storage&lt;/h2&gt;

&lt;p&gt;Once the collection has been created, the worker role uploads it to blob storage, using some rather mundane code. I’m including it here because it demonstrates a few important details: parallelizing uploads for performance, setting the correct content type on blobs, and setting the cache control header when using the CDN. Note also that the main &lt;code&gt;.cxml&lt;/code&gt; file is uploaded last, to ensure that it’s not served to users before all the supporting files have been uploaded.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;pre class="code"&gt;&lt;span style="color: blue"&gt;private void &lt;/span&gt;UploadDirectoryRecursive(&lt;span style="color: blue"&gt;string &lt;/span&gt;path, &lt;span style="color: #2b91af"&gt;CloudBlobContainer &lt;/span&gt;container)
{
    &lt;span style="color: blue"&gt;string &lt;/span&gt;cxmlPath = &lt;span style="color: blue"&gt;null&lt;/span&gt;;

    &lt;span style="color: green"&gt;// use 16 threads to upload
    &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Parallel&lt;/span&gt;.ForEach(EnumerateDirectoryRecursive(path),
        &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ParallelOptions &lt;/span&gt;{ MaxDegreeOfParallelism = 16 },
        (file) =&amp;gt;
    {
        &lt;span style="color: green"&gt;// save collection-#####.cxml for last
        &lt;/span&gt;&lt;span style="color: blue"&gt;if &lt;/span&gt;(&lt;span style="color: #2b91af"&gt;Path&lt;/span&gt;.GetFileName(file).StartsWith(&lt;span style="color: #a31515"&gt;&amp;quot;collection-&amp;quot;&lt;/span&gt;) &amp;amp;&amp;amp; &lt;span style="color: #2b91af"&gt;Path&lt;/span&gt;.GetExtension(file) == &lt;span style="color: #a31515"&gt;&amp;quot;.cxml&amp;quot;&lt;/span&gt;)
        {
            cxmlPath = file;
        }
        &lt;span style="color: blue"&gt;else
        &lt;/span&gt;{
            &lt;span style="color: green"&gt;// upload each file, using the relative path as a blob name
            &lt;/span&gt;UploadFile(file, container.GetBlobReference(&lt;span style="color: #2b91af"&gt;Path&lt;/span&gt;.GetFullPath(file).Substring(path.Length)));
        }
    });

    &lt;span style="color: green"&gt;// finish up with the cxml itself
    &lt;/span&gt;&lt;span style="color: blue"&gt;if &lt;/span&gt;(cxmlPath != &lt;span style="color: blue"&gt;null&lt;/span&gt;)
    {
        UploadFile(cxmlPath, container.GetBlobReference(&lt;span style="color: #2b91af"&gt;Path&lt;/span&gt;.GetFullPath(cxmlPath).Substring(path.Length)));
    }
}

&lt;span style="color: blue"&gt;private &lt;/span&gt;&lt;span style="color: #2b91af"&gt;IEnumerable&lt;/span&gt;&amp;lt;&lt;span style="color: blue"&gt;string&lt;/span&gt;&amp;gt; EnumerateDirectoryRecursive(&lt;span style="color: blue"&gt;string &lt;/span&gt;root)
{
    &lt;span style="color: blue"&gt;foreach &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;file &lt;span style="color: blue"&gt;in &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Directory&lt;/span&gt;.GetFiles(root))
        &lt;span style="color: blue"&gt;yield return &lt;/span&gt;file;
    &lt;span style="color: blue"&gt;foreach &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;subdir &lt;span style="color: blue"&gt;in &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Directory&lt;/span&gt;.GetDirectories(root))
        &lt;span style="color: blue"&gt;foreach &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;file &lt;span style="color: blue"&gt;in &lt;/span&gt;EnumerateDirectoryRecursive(subdir))
            &lt;span style="color: blue"&gt;yield return &lt;/span&gt;file;
}

&lt;span style="color: blue"&gt;private void &lt;/span&gt;UploadFile(&lt;span style="color: blue"&gt;string &lt;/span&gt;filename, &lt;span style="color: #2b91af"&gt;CloudBlob &lt;/span&gt;blob)
{
    &lt;span style="color: blue"&gt;var &lt;/span&gt;extension = &lt;span style="color: #2b91af"&gt;Path&lt;/span&gt;.GetExtension(filename).ToLower();
    &lt;span style="color: blue"&gt;if &lt;/span&gt;(extension == &lt;span style="color: #a31515"&gt;&amp;quot;.cxml&amp;quot;&lt;/span&gt;)
    {
        &lt;span style="color: green"&gt;// cache CXML for 30 minutes
        &lt;/span&gt;blob.Properties.CacheControl = &lt;span style="color: #a31515"&gt;&amp;quot;max-age=1800&amp;quot;&lt;/span&gt;;
    }
    &lt;span style="color: blue"&gt;else
    &lt;/span&gt;{
        &lt;span style="color: green"&gt;// cache everything else (images) for 2 hours
        &lt;/span&gt;blob.Properties.CacheControl = &lt;span style="color: #a31515"&gt;&amp;quot;max-age=7200&amp;quot;&lt;/span&gt;;
    }
    &lt;span style="color: blue"&gt;switch &lt;/span&gt;(extension)
        {
            &lt;span style="color: blue"&gt;case &lt;/span&gt;&lt;span style="color: #a31515"&gt;&amp;quot;.xml&amp;quot;&lt;/span&gt;:
            &lt;span style="color: blue"&gt;case &lt;/span&gt;&lt;span style="color: #a31515"&gt;&amp;quot;.cxml&amp;quot;&lt;/span&gt;:
            &lt;span style="color: blue"&gt;case &lt;/span&gt;&lt;span style="color: #a31515"&gt;&amp;quot;.dzc&amp;quot;&lt;/span&gt;:
                blob.Properties.ContentType = &lt;span style="color: #a31515"&gt;&amp;quot;application/xml&amp;quot;&lt;/span&gt;;
                &lt;span style="color: blue"&gt;break&lt;/span&gt;;
            &lt;span style="color: blue"&gt;case &lt;/span&gt;&lt;span style="color: #a31515"&gt;&amp;quot;.jpg&amp;quot;&lt;/span&gt;:
                blob.Properties.ContentType = &lt;span style="color: #a31515"&gt;&amp;quot;image/jpeg&amp;quot;&lt;/span&gt;;
                &lt;span style="color: blue"&gt;break&lt;/span&gt;;
        }
    blob.UploadFile(filename);
}&lt;/pre&gt;
&lt;/blockquote&gt;

&lt;h2&gt;Serving the Collection&lt;/h2&gt;

&lt;p&gt;Once the collection is done, there’s very little left to do. I subclassed PivotViewer to handle users clicking through to the movie listing on Netflix (either by clicking “View on Netflix” or by double-clicking an item).&lt;/p&gt;

&lt;blockquote&gt;
  &lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public class &lt;/span&gt;&lt;span style="color: #2b91af"&gt;NetflixPivotControl &lt;/span&gt;: &lt;span style="color: #2b91af"&gt;PivotViewer
&lt;/span&gt;{
    &lt;span style="color: blue"&gt;public &lt;/span&gt;NetflixPivotControl()
    {
        ItemActionExecuted += &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;EventHandler&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;ItemActionEventArgs&lt;/span&gt;&amp;gt;(NetflixPivotViewer_ItemActionExecuted);
        ItemDoubleClicked += &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;EventHandler&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;ItemEventArgs&lt;/span&gt;&amp;gt;(NetflixPivotViewer_ItemDoubleClicked);
    }

    &lt;span style="color: blue"&gt;private void &lt;/span&gt;BrowseTo(&lt;span style="color: blue"&gt;string &lt;/span&gt;itemId)
    {
        &lt;span style="color: #2b91af"&gt;HtmlPage&lt;/span&gt;.Window.Navigate(&lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Uri&lt;/span&gt;(GetItem(itemId).Href));
    }

    &lt;span style="color: blue"&gt;private void &lt;/span&gt;NetflixPivotViewer_ItemDoubleClicked(&lt;span style="color: blue"&gt;object &lt;/span&gt;sender, &lt;span style="color: #2b91af"&gt;ItemEventArgs &lt;/span&gt;e)
    {
        BrowseTo(e.ItemId);
    }

    &lt;span style="color: blue"&gt;private void &lt;/span&gt;NetflixPivotViewer_ItemActionExecuted(&lt;span style="color: blue"&gt;object &lt;/span&gt;sender, &lt;span style="color: #2b91af"&gt;ItemActionEventArgs &lt;/span&gt;e)
    {
        BrowseTo(e.ItemId);
    }

    &lt;span style="color: blue"&gt;protected override &lt;/span&gt;&lt;span style="color: #2b91af"&gt;List&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;CustomAction&lt;/span&gt;&amp;gt; GetCustomActionsForItem(&lt;span style="color: blue"&gt;string &lt;/span&gt;itemId)
    {
        &lt;span style="color: blue"&gt;var &lt;/span&gt;list = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;List&lt;/span&gt;&amp;lt;&lt;span style="color: #2b91af"&gt;CustomAction&lt;/span&gt;&amp;gt;();
        list.Add(&lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;CustomAction&lt;/span&gt;(&lt;span style="color: #a31515"&gt;&amp;quot;View on Netflix&amp;quot;&lt;/span&gt;, &lt;span style="color: blue"&gt;null&lt;/span&gt;, &lt;span style="color: #a31515"&gt;&amp;quot;View this movie at Netflix&amp;quot;&lt;/span&gt;, &lt;span style="color: #a31515"&gt;&amp;quot;view&amp;quot;&lt;/span&gt;));
        &lt;span style="color: blue"&gt;return &lt;/span&gt;list;
    }
}&lt;/pre&gt;
&lt;/blockquote&gt;

&lt;p&gt;Finally, I wrote an ASP.NET MVC web role that serves up a web page with the Silverlight control embedded. The actual Silverlight application (&lt;code&gt;.xap&lt;/code&gt; file) is stored in blob storage just like the rest of the content, so the interesting part of the controller is constructing the proper URL to the CDN version of the blob.&lt;/p&gt;

&lt;p&gt;We have to be careful not to get a mismatched collection due to CDN caching (for example, an updated collection with new movies mismatched with cached Deep Zoom images). To avoid this situation, every time a new collection is created, all files involved are suffixed with a reversed timestamp. The ASP.NET MVC controller below references the latest &lt;code&gt;.cxml&lt;/code&gt; file (which in turn references the matching Deep Zoom images).&lt;/p&gt;

&lt;blockquote&gt;
  &lt;pre class="code"&gt;&lt;span style="color: blue"&gt;private &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Uri &lt;/span&gt;GetBlobOrCdnUri(&lt;span style="color: #2b91af"&gt;CloudBlob &lt;/span&gt;blob, &lt;span style="color: blue"&gt;string &lt;/span&gt;cdnHost)
{
    &lt;span style="color: green"&gt;// always use HTTP to avoid Silverlight cross-protocol issues
    &lt;/span&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;ub = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;UriBuilder&lt;/span&gt;(blob.Uri)
    {
        Scheme = &lt;span style="color: #a31515"&gt;&amp;quot;http&amp;quot;&lt;/span&gt;,
        Port = 80
    };
    &lt;span style="color: blue"&gt;if &lt;/span&gt;(!&lt;span style="color: blue"&gt;string&lt;/span&gt;.IsNullOrEmpty(cdnHost))
    {
        ub.Host = cdnHost;
    }
    &lt;span style="color: blue"&gt;return &lt;/span&gt;ub.Uri;
}

&lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ActionResult &lt;/span&gt;Index()
{
    &lt;span style="color: blue"&gt;var &lt;/span&gt;blobs = &lt;span style="color: #2b91af"&gt;CloudStorageAccount&lt;/span&gt;.Parse(&lt;span style="color: #2b91af"&gt;RoleEnvironment&lt;/span&gt;.GetConfigurationSettingValue(&lt;span style="color: #a31515"&gt;&amp;quot;DataConnectionString&amp;quot;&lt;/span&gt;))
        .CreateCloudBlobClient();
    &lt;span style="color: blue"&gt;var &lt;/span&gt;cdnHost = &lt;span style="color: #2b91af"&gt;RoleEnvironment&lt;/span&gt;.GetConfigurationSettingValue(&lt;span style="color: #a31515"&gt;&amp;quot;CdnHost&amp;quot;&lt;/span&gt;);

    &lt;span style="color: blue"&gt;var &lt;/span&gt;controlBlob = blobs.GetBlobReference(&lt;span style="color: #a31515"&gt;&amp;quot;control/NetflixPivotViewer.xap&amp;quot;&lt;/span&gt;);
    &lt;span style="color: blue"&gt;var &lt;/span&gt;collectionBlob = blobs.ListBlobsWithPrefix(&lt;span style="color: #a31515"&gt;&amp;quot;collection/collection-&amp;quot;&lt;/span&gt;).OfType&amp;lt;&lt;span style="color: #2b91af"&gt;CloudBlob&lt;/span&gt;&amp;gt;()
        .Where(b =&amp;gt; b.Uri.AbsolutePath.EndsWith(&lt;span style="color: #a31515"&gt;&amp;quot;.cxml&amp;quot;&lt;/span&gt;)).First();

    ViewData[&lt;span style="color: #a31515"&gt;&amp;quot;xapUrl&amp;quot;&lt;/span&gt;] = GetBlobOrCdnUri(controlBlob, cdnHost).AbsoluteUri;
    ViewData[&lt;span style="color: #a31515"&gt;&amp;quot;collectionUrl&amp;quot;&lt;/span&gt;] = GetBlobOrCdnUri(collectionBlob, cdnHost).AbsoluteUri;
    &lt;span style="color: blue"&gt;return &lt;/span&gt;View();
}&lt;/pre&gt;
&lt;/blockquote&gt;

&lt;h2&gt;Download the Code&lt;/h2&gt;

&lt;p&gt;You’ve now seen nearly all of the code involved, but you can download the full Visual Studio 2010 solution at &lt;a href="http://cdn.blog.smarx.com/files/NetflixPivot_source_updated2.zip"&gt;http://cdn.blog.smarx.com/files/NetflixPivot_source_updated2.zip&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you want to run it, you’ll also need:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="http://www.silverlight.net/learn/pivotviewer/"&gt;PivotView Silverlight control&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;&lt;a href="http://seadragon.com/developer/creating-content/deep-zoom-tools/"&gt;Deep Zoom Tools&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And note that the collection takes quite some time to create, so expect to run this for at least an hour before you can see anything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[UPDATE 2:58pm] &lt;/strong&gt;The first revision of this code had a couple bugs, notably around the use of the CDN. (I originally didn’t create new blob names for each update to the collection, so mismatches due to caching were possible.) I’ve updated a couple code snippets in the text of this post, and I’ve posted a new version of the source code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[UPDATE 6/30/2010 4:28pm] &lt;/strong&gt;I made another small revision to the code to make sure the &lt;code&gt;.cxml&lt;/code&gt; file is uploaded last. I updated one code snippet above, and I’ve posted a new version of the source code.&lt;/p&gt;  </content></entry><entry><id>http://blog.smarx.com/atompub.svc/blog/posts/computing-the-total-size-of-your-blobs</id><title type="text">Computing the Total Size of Your Blobs</title><published>2010-06-01T23:48:41Z</published><updated>2010-06-01T23:48:41Z</updated><author><name>Steve Marx</name><uri>http://smarx.com</uri><email>steve.marx@microsoft.com</email></author><link rel="alternate" href="http://blog.smarx.com/posts/computing-the-total-size-of-your-blobs"/><link rel="edit" href="computing-the-total-size-of-your-blobs"/><content type="html">&lt;p&gt;A question came up on the &lt;a href="http://social.msdn.microsoft.com/forums/en-us/windowsazure/threads"&gt;Windows Azure MSDN forum&lt;/a&gt; recently about how to find the total number of bytes used by a blobs in a particular container. There’s no API that retrieves that information at the container level, but you can compute it by enumerating the blobs, as in the following one-liner:&lt;/p&gt;  &lt;blockquote&gt;   &lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;totalBytes = (&lt;span style="color: blue"&gt;from &lt;/span&gt;&lt;span style="color: #2b91af"&gt;CloudBlob &lt;/span&gt;blob &lt;span style="color: blue"&gt;in
                  &lt;/span&gt;container.ListBlobs(&lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;BlobRequestOptions&lt;/span&gt;() { UseFlatBlobListing = &lt;span style="color: blue"&gt;true &lt;/span&gt;})
                  &lt;span style="color: blue"&gt;select &lt;/span&gt;blob.Properties.Length
                 ).Sum();&lt;/pre&gt;
&lt;/blockquote&gt;

&lt;p&gt;To go one step further, we can enumerate all the containers too. Here’s how to sum the sizes of all the blobs in a particular account (still a one-liner):&lt;/p&gt;

&lt;blockquote&gt;
  &lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;totalBytes = (&lt;span style="color: blue"&gt;from &lt;/span&gt;container &lt;span style="color: blue"&gt;in &lt;/span&gt;blobClient.ListContainers()
                  &lt;span style="color: blue"&gt;select
                  &lt;/span&gt;(&lt;span style="color: blue"&gt;from &lt;/span&gt;&lt;span style="color: #2b91af"&gt;CloudBlob &lt;/span&gt;blob &lt;span style="color: blue"&gt;in
                   &lt;/span&gt;container.ListBlobs(&lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;BlobRequestOptions&lt;/span&gt;() { UseFlatBlobListing = &lt;span style="color: blue"&gt;true &lt;/span&gt;})
                   &lt;span style="color: blue"&gt;select &lt;/span&gt;blob.Properties.Length
                  ).Sum()
                 ).Sum();&lt;/pre&gt;
&lt;/blockquote&gt;

&lt;p&gt;Note that this does &lt;em&gt;not&lt;/em&gt; reflect the number of bytes you’re billed for. Things like empty pages in page blobs, uncommitted blocks in block blobs, snapshots, metadata, etc. all affect the total storage used in your account. The code snippets above simply sums the “sizes” (if you were to download them, for example) of all the blobs.&lt;/p&gt;  </content></entry><entry><id>http://blog.smarx.com/atompub.svc/blog/posts/making-songs-swing-with-windows-azure-python-and-the-echo-nest-api</id><title type="text">Making Songs Swing with Windows Azure, Python, and the Echo Nest API</title><published>2010-05-27T23:01:21Z</published><updated>2010-05-27T23:01:21Z</updated><author><name>Steve Marx</name><uri>http://smarx.com</uri><email>steve.marx@microsoft.com</email></author><link rel="alternate" href="http://blog.smarx.com/posts/making-songs-swing-with-windows-azure-python-and-the-echo-nest-api"/><link rel="edit" href="making-songs-swing-with-windows-azure-python-and-the-echo-nest-api"/><content type="html">&lt;p&gt;&lt;a href="http://swingify.cloudapp.net"&gt;&lt;img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; margin-left: 0px; border-left-width: 0px; margin-right: 0px" title="image" border="0" alt="image" align="right" src="http://cdn.blog.smarx.com/images/image%5B10%5D" width="367" height="155" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;I’ve put together a sample application at &lt;a href="http://swingify.cloudapp.net"&gt;http://swingify.cloudapp.net&lt;/a&gt; that lets you upload a song as an MP3 and produces a “swing time” version of it. It’s easier to explain by example, so here’s &lt;a href="http://swingify.cloudapp.net/Result/6b6ef003-d731-4996-9c67-463bff87a792"&gt;the Tetris theme song as converted by Swingify&lt;/a&gt;.&lt;/p&gt;  &lt;h2&gt;Background&lt;/h2&gt;  &lt;p&gt;The app makes use of the &lt;a href="http://developer.echonest.com/"&gt;Echo Nest API&lt;/a&gt; and a sample developed by &lt;a href="http://web.media.mit.edu/~tristan/"&gt;Tristan Jehan&lt;/a&gt; that converts any straight-time song to swing time by extended the first half of each beat and compressing the second half. I first saw the story over on the &lt;a href="http://musicmachinery.com/2010/05/21/the-swinger/"&gt;Music Machinery blog&lt;/a&gt; and then later in the week on &lt;a href="http://www.engadget.com/2010/05/26/the-swinger-hacks-any-song-to-swing/"&gt;Engadget&lt;/a&gt;.&lt;/p&gt;  &lt;p&gt;I immediately wanted to try this with some songs of my own, and I thought others would want to do the same, so I thought I’d create a Windows Azure application to do this in the cloud.&lt;/p&gt;  &lt;h2&gt;How it Works&lt;/h2&gt;  &lt;p&gt;We covered this application on the latest episode of &lt;a href="http://channel9.msdn.com/shows/cloud+cover"&gt;the Cloud Cover show on Channel 9&lt;/a&gt; (to go live tomorrow morning – watch the &lt;a href="http://www.twitvid.com/PXA7R"&gt;teaser&lt;/a&gt; now). In short, the application consists of an ASP.NET MVC web role and a worker role that is mostly a thin wrapper around a Python script.&lt;/p&gt;  &lt;p&gt;The ASP.NET MVC web role accepts an MP3 upload, stores the file in blob storage, and enqueues the name of the blob:&lt;/p&gt;  &lt;blockquote&gt;   &lt;pre class="code"&gt;[&lt;span style="color: #2b91af"&gt;HttpPost&lt;/span&gt;]
&lt;span style="color: blue"&gt;public &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ActionResult &lt;/span&gt;Create()
{
    &lt;span style="color: blue"&gt;var &lt;/span&gt;guid = &lt;span style="color: #2b91af"&gt;Guid&lt;/span&gt;.NewGuid().ToString();
    &lt;span style="color: blue"&gt;var &lt;/span&gt;file = Request.Files[0];
    &lt;span style="color: blue"&gt;var &lt;/span&gt;account = &lt;span style="color: #2b91af"&gt;CloudStorageAccount&lt;/span&gt;.FromConfigurationSetting(&lt;span style="color: #a31515"&gt;&amp;quot;DataConnectionString&amp;quot;&lt;/span&gt;);
    &lt;span style="color: blue"&gt;var &lt;/span&gt;blob = account.CreateCloudBlobClient().GetContainerReference(&lt;span style="color: #a31515"&gt;&amp;quot;incoming&amp;quot;&lt;/span&gt;).GetBlobReference(guid);
    blob.UploadFromStream(file.InputStream);
    account.CreateCloudQueueClient().GetQueueReference(&lt;span style="color: #a31515"&gt;&amp;quot;incoming&amp;quot;&lt;/span&gt;).AddMessage(&lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;CloudQueueMessage&lt;/span&gt;(guid));
    &lt;span style="color: blue"&gt;return &lt;/span&gt;RedirectToAction(&lt;span style="color: #a31515"&gt;&amp;quot;Result&amp;quot;&lt;/span&gt;, &lt;span style="color: blue"&gt;new &lt;/span&gt;{ id = guid });
}&lt;/pre&gt;
&lt;/blockquote&gt;

&lt;p&gt;The worker role mounts a Windows Azure drive in &lt;code&gt;OnStart()&lt;/code&gt;. Here I used the same tools and initialization code as I developed for my blog post “&lt;a href="http://blog.smarx.com/posts/serving-your-website-from-a-windows-azure-drive"&gt;Serving Your Website From a Windows Azure Drive&lt;/a&gt;.” In &lt;code&gt;OnStart()&lt;/code&gt;:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;cache = &lt;span style="color: #2b91af"&gt;RoleEnvironment&lt;/span&gt;.GetLocalResource(&lt;span style="color: #a31515"&gt;&amp;quot;DriveCache&amp;quot;&lt;/span&gt;);
&lt;span style="color: #2b91af"&gt;CloudDrive&lt;/span&gt;.InitializeCache(cache.RootPath.TrimEnd(&lt;span style="color: #a31515"&gt;'\\'&lt;/span&gt;), cache.MaximumSizeInMegabytes);

drive = &lt;span style="color: #2b91af"&gt;CloudStorageAccount&lt;/span&gt;.FromConfigurationSetting(&lt;span style="color: #a31515"&gt;&amp;quot;DataConnectionString&amp;quot;&lt;/span&gt;)
    .CreateCloudDrive(&lt;span style="color: #2b91af"&gt;RoleEnvironment&lt;/span&gt;.GetConfigurationSettingValue(&lt;span style="color: #a31515"&gt;&amp;quot;DriveSnapshotUrl&amp;quot;&lt;/span&gt;));
drive.Mount(cache.MaximumSizeInMegabytes, &lt;span style="color: #2b91af"&gt;DriveMountOptions&lt;/span&gt;.None);&lt;/pre&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then there’s a simple loop in &lt;code&gt;Run()&lt;/code&gt;:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;pre class="code"&gt;&lt;span style="color: blue"&gt;while &lt;/span&gt;(&lt;span style="color: blue"&gt;true&lt;/span&gt;)
{
    &lt;span style="color: blue"&gt;var &lt;/span&gt;msg = q.GetMessage(&lt;span style="color: #2b91af"&gt;TimeSpan&lt;/span&gt;.FromMinutes(5));
    &lt;span style="color: blue"&gt;if &lt;/span&gt;(msg != &lt;span style="color: blue"&gt;null&lt;/span&gt;)
    {
        SwingifyBlob(msg.AsString);
        q.DeleteMessage(msg);
    }
    &lt;span style="color: blue"&gt;else
    &lt;/span&gt;{
        &lt;span style="color: #2b91af"&gt;Thread&lt;/span&gt;.Sleep(&lt;span style="color: #2b91af"&gt;TimeSpan&lt;/span&gt;.FromSeconds(5));
    }
}&lt;/pre&gt;
&lt;/blockquote&gt;

&lt;p&gt;The meat of the application is in the implementation of &lt;code&gt;SwingifyBlob()&lt;/code&gt;, which calls out to &lt;code&gt;python.exe&lt;/code&gt; on the mounted Windows Azure drive:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public void &lt;/span&gt;SwingifyBlob(&lt;span style="color: blue"&gt;string &lt;/span&gt;guid)
{
    &lt;span style="color: blue"&gt;var &lt;/span&gt;account = &lt;span style="color: #2b91af"&gt;CloudStorageAccount&lt;/span&gt;.FromConfigurationSetting(&lt;span style="color: #a31515"&gt;&amp;quot;DataConnectionString&amp;quot;&lt;/span&gt;);
    &lt;span style="color: blue"&gt;var &lt;/span&gt;blobs = account.CreateCloudBlobClient();
    &lt;span style="color: blue"&gt;var &lt;/span&gt;blob = blobs.GetContainerReference(&lt;span style="color: #a31515"&gt;&amp;quot;incoming&amp;quot;&lt;/span&gt;).GetBlobReference(guid);

    &lt;span style="color: blue"&gt;var &lt;/span&gt;tempdir = &lt;span style="color: #2b91af"&gt;RoleEnvironment&lt;/span&gt;.GetLocalResource(&lt;span style="color: #a31515"&gt;&amp;quot;temp&amp;quot;&lt;/span&gt;).RootPath;
    &lt;span style="color: blue"&gt;string &lt;/span&gt;filepath = &lt;span style="color: #2b91af"&gt;Path&lt;/span&gt;.Combine(tempdir, guid + &lt;span style="color: #a31515"&gt;&amp;quot;.mp3&amp;quot;&lt;/span&gt;);
    blob.DownloadToFile(filepath);

    &lt;span style="color: blue"&gt;var &lt;/span&gt;localPath = drive.LocalPath + &lt;span style="color: #a31515"&gt;@&amp;quot;\&amp;quot;&lt;/span&gt;;

    &lt;span style="color: blue"&gt;var &lt;/span&gt;process = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Process&lt;/span&gt;()
    {
        StartInfo = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ProcessStartInfo&lt;/span&gt;(
            &lt;span style="color: #2b91af"&gt;Path&lt;/span&gt;.Combine(localPath, &lt;span style="color: #a31515"&gt;@&amp;quot;python\python.exe&amp;quot;&lt;/span&gt;),
            &lt;span style="color: blue"&gt;string&lt;/span&gt;.Format(&lt;span style="color: #a31515"&gt;@&amp;quot;swinger.py &amp;quot;&amp;quot;{0}&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;, filepath))
        {
            RedirectStandardOutput = &lt;span style="color: blue"&gt;true&lt;/span&gt;,
            RedirectStandardError = &lt;span style="color: blue"&gt;true&lt;/span&gt;,
            UseShellExecute = &lt;span style="color: blue"&gt;false&lt;/span&gt;,
            WorkingDirectory = localPath
        }
    };

    process.StartInfo.EnvironmentVariables[&lt;span style="color: #a31515"&gt;&amp;quot;PATH&amp;quot;&lt;/span&gt;] = &lt;span style="color: #2b91af"&gt;Path&lt;/span&gt;.Combine(localPath, &lt;span style="color: #a31515"&gt;@&amp;quot;python&amp;quot;&lt;/span&gt;);
    process.StartInfo.EnvironmentVariables[&lt;span style="color: #a31515"&gt;&amp;quot;TEMP&amp;quot;&lt;/span&gt;] = tempdir;
    process.StartInfo.EnvironmentVariables[&lt;span style="color: #a31515"&gt;&amp;quot;ECHO_NEST_API_KEY&amp;quot;&lt;/span&gt;] =
        &lt;span style="color: #2b91af"&gt;RoleEnvironment&lt;/span&gt;.GetConfigurationSettingValue(&lt;span style="color: #a31515"&gt;&amp;quot;EchoNestApiKey&amp;quot;&lt;/span&gt;);

    &lt;span style="color: #2b91af"&gt;DataReceivedEventHandler &lt;/span&gt;logit = (s, e) =&amp;gt;
        {
            &lt;span style="color: blue"&gt;if &lt;/span&gt;(e.Data != &lt;span style="color: blue"&gt;null&lt;/span&gt;)
            {
                &lt;span style="color: #2b91af"&gt;Debug&lt;/span&gt;.WriteLine(e.Data);
            }
        };

    process.OutputDataReceived += logit;
    process.ErrorDataReceived += logit;

    process.Start();
    process.BeginOutputReadLine();
    process.BeginErrorReadLine();
    process.WaitForExit();

    &lt;span style="color: blue"&gt;var &lt;/span&gt;outfilepath = &lt;span style="color: #2b91af"&gt;Path&lt;/span&gt;.Combine(tempdir, guid + &lt;span style="color: #a31515"&gt;&amp;quot;_swing+33.mp3&amp;quot;&lt;/span&gt;);
    &lt;span style="color: blue"&gt;var &lt;/span&gt;outblob = blobs.GetContainerReference(&lt;span style="color: #a31515"&gt;&amp;quot;output&amp;quot;&lt;/span&gt;).GetBlobReference(guid);
    &lt;span style="color: blue"&gt;if &lt;/span&gt;(&lt;span style="color: #2b91af"&gt;File&lt;/span&gt;.Exists(outfilepath))
    {
        outblob.Properties.ContentType = &lt;span style="color: #a31515"&gt;&amp;quot;audio/mp3&amp;quot;&lt;/span&gt;;
        &lt;span style="color: blue"&gt;using &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;reader = &lt;span style="color: #2b91af"&gt;File&lt;/span&gt;.OpenText(&lt;span style="color: #2b91af"&gt;Path&lt;/span&gt;.Combine(tempdir, guid + &lt;span style="color: #a31515"&gt;&amp;quot;_metadata.txt&amp;quot;&lt;/span&gt;)))
        {
            &lt;span style="color: blue"&gt;var &lt;/span&gt;title = reader.ReadLine().Trim();
            &lt;span style="color: blue"&gt;if &lt;/span&gt;(!&lt;span style="color: blue"&gt;string&lt;/span&gt;.IsNullOrEmpty(title))
            {
                outblob.Metadata[&lt;span style="color: #a31515"&gt;&amp;quot;title&amp;quot;&lt;/span&gt;] = title;
            }
            &lt;span style="color: blue"&gt;var &lt;/span&gt;artist = reader.ReadLine().Trim();
            &lt;span style="color: blue"&gt;if &lt;/span&gt;(!&lt;span style="color: blue"&gt;string&lt;/span&gt;.IsNullOrEmpty(artist))
            {
                outblob.Metadata[&lt;span style="color: #a31515"&gt;&amp;quot;artist&amp;quot;&lt;/span&gt;] = artist;
            }
        }
        outblob.UploadFile(outfilepath);
    }
    &lt;span style="color: blue"&gt;else
    &lt;/span&gt;{
        outblob.Properties.ContentType = &lt;span style="color: #a31515"&gt;&amp;quot;text/plain&amp;quot;&lt;/span&gt;;
        outblob.UploadText(&lt;span style="color: #a31515"&gt;&amp;quot;Sorry... we failed to swingify this song.  Try a different one?&amp;quot;&lt;/span&gt;);
    }

    &lt;span style="color: blue"&gt;foreach &lt;/span&gt;(&lt;span style="color: blue"&gt;var &lt;/span&gt;filename &lt;span style="color: blue"&gt;in &lt;/span&gt;&lt;span style="color: #2b91af"&gt;Directory&lt;/span&gt;.GetFiles(tempdir))
    {
        &lt;span style="color: #2b91af"&gt;File&lt;/span&gt;.Delete(filename);
    }
}&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;

&lt;p&gt;The first part of this code sets up the call to the &lt;code&gt;python.exe&lt;/code&gt; process by downloading the blob to local storage, making sure the path environment variable is correct, the Echo Nest API key is set, and that the working directory is right.&lt;/p&gt;

&lt;p&gt;The second part of the method uploads the processed MP3 and parses out the title and artist written to disk by the Python script.&lt;/p&gt;

&lt;h2&gt;Running Python in Windows Azure&lt;/h2&gt;

&lt;p&gt;For the most part, all I need to do to run Python in Windows Azure was copy my local Python directory (&lt;code&gt;c:\python26&lt;/code&gt;) to a Windows Azure drive and mount that in my worker role. There was one gotcha, however, which stumped me for almost a day. Python is actually &lt;em&gt;not&lt;/em&gt; copy-deployable. I needed to copy &lt;code&gt;python26.dll&lt;/code&gt; onto the drive as well (in the Python folder, which you’ll note I added to the path). On my local machine, this DLL was in &lt;code&gt;%windir%\system32&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You might also check out the &lt;a href="http://portablepython.com"&gt;Portable Python&lt;/a&gt; project, which seems like an easier (and better supported) way to make sure your Python distribution can actually run in Windows Azure.&lt;/p&gt;

&lt;h2&gt;Other Swingified Songs&lt;/h2&gt;

&lt;p&gt;If you wanted more examples of what you can make with &lt;a href="http://swingify.cloudapp.net"&gt;http://swingify.cloudapp.net&lt;/a&gt;, here are some that I’ve made:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="http://swingify.cloudapp.net/Result/7b35a98d-5e99-475d-88f7-f61ccdc7556e"&gt;Peter Gunn Theme&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;&lt;a href="http://swingify.cloudapp.net/Result/636fd37b-7feb-4015-82e7-80a4faf0f9ef"&gt;This Love&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;&lt;a href="http://swingify.cloudapp.net/Result/75fd44b6-594d-4194-b71a-06073f1350b4"&gt;Never There&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;&lt;a href="http://swingify.cloudapp.net/Result/c1eb5dbe-168b-4b40-8007-f010dfcc40c6"&gt;Beautiful Day&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;&lt;a href="http://swingify.cloudapp.net/Result/1f3bb70d-558a-4b16-9daa-22467579d29b"&gt;Billie Jean&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;&lt;a href="http://swingify.cloudapp.net/Result/05ae018a-6547-4bb4-837b-3b877d21e888"&gt;Bad Romance&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;&lt;a href="http://swingify.cloudapp.net/Result/19fa3577-0334-4cb4-93b4-dd410087e8e3"&gt;Stacy's Mom&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;&lt;a href="http://swingify.cloudapp.net/Result/760e30a8-085f-4f34-a233-30d3213cc793"&gt;3&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;&lt;a href="http://swingify.cloudapp.net/Result/166189f7-2716-4bbd-9738-168b34a819b0"&gt;Chocolate Rain&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;&lt;a href="http://swingify.cloudapp.net/Result/8bc57cef-71b9-4367-80be-841f4be0451a"&gt;South Park - Poker Face&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;&lt;a href="http://swingify.cloudapp.net/Result/cbe0226f-1428-4a7e-83a7-47164fefe128"&gt;James Bond Theme&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;&lt;a href="http://swingify.cloudapp.net/Result/5aa811c8-7742-4157-af28-13f4fef1e899"&gt;The Element Song&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;&lt;a href="http://swingify.cloudapp.net/Result/6b6ef003-d731-4996-9c67-463bff87a792"&gt;Tetris Theme&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;&lt;a href="http://swingify.cloudapp.net/Result/ce50445e-17cf-40a2-95c6-a16172751409"&gt;Rocky Theme&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;&lt;a href="http://swingify.cloudapp.net/Result/b7ae3574-e08c-4f74-adde-a51daadc7ea6"&gt;I Want it That Way&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;&lt;a href="http://swingify.cloudapp.net/Result/3fe04e36-3057-495c-9529-5653983b80cb"&gt;Make Me Lose Control&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;&lt;a href="http://swingify.cloudapp.net/Result/0eb5a317-00ed-43eb-ade7-191469179fcb"&gt;Lose Yourself&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;Also be sure to check out &lt;a href="http://musicmachinery.com/2010/05/21/the-swinger/"&gt;the original post on the Music Machinery blog&lt;/a&gt; for a few more examples.&lt;/p&gt;

&lt;h2&gt;Make Some Music&lt;/h2&gt;

&lt;p&gt;Go to &lt;a href="http://swingify.cloudapp.net"&gt;http://swingify.cloudapp.net&lt;/a&gt; and make the songs in your own MP3 collection swing, and &lt;em&gt;please &lt;/em&gt;send me a tweet (&lt;a href="http://twitter.com/smarx"&gt;@smarx&lt;/a&gt;) with the good ones you come up with!&lt;/p&gt;  </content></entry><entry><id>http://blog.smarx.com/atompub.svc/blog/posts/emailtheinternet-com-sending-and-receiving-email-in-windows-azure</id><title type="text">EmailTheInternet.com: Sending and Receiving Email in Windows Azure</title><published>2010-05-20T17:33:19Z</published><updated>2010-05-20T17:33:19Z</updated><author><name>Steve Marx</name><uri>http://smarx.com</uri><email>steve.marx@microsoft.com</email></author><link rel="alternate" href="http://blog.smarx.com/posts/emailtheinternet-com-sending-and-receiving-email-in-windows-azure"/><link rel="edit" href="emailtheinternet-com-sending-and-receiving-email-in-windows-azure"/><content type="html">&lt;p&gt;&lt;a href="http://emailtheinternet.com/"&gt;&lt;img style="border-bottom: 0px; border-left: 0px; display: inline; margin-left: 0px; border-top: 0px; margin-right: 0px; border-right: 0px" title="image" border="0" alt="image" align="right" src="http://cdn.blog.smarx.com/images/image_thumb%5B8%5D" width="260" height="208" /&gt;&lt;/a&gt; Running right now at &lt;a href="http://emailtheinternet.com"&gt;http://emailtheinternet.com&lt;/a&gt; is my latest demo, which lets you send anything via email and posts it to a public URL on the web.&lt;/p&gt;  &lt;p&gt;This sample, which you can download &lt;a href="http://cdn.blog.smarx.com/files/EmailTheInternet_source.zip"&gt;here&lt;/a&gt;, does three things of technical interest:&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;It uses a third party service (&lt;a href="http://sendgrid.com"&gt;SendGrid&lt;/a&gt;) to send email from inside Windows Azure. &lt;/li&gt;    &lt;li&gt;It uses a &lt;a href="http://blog.smarx.com/posts/using-other-web-servers-on-windows-azure"&gt;worker role with an input endpoint&lt;/a&gt; to listen for SMTP traffic on port 25. &lt;/li&gt;    &lt;li&gt;It uses &lt;a href="http://blog.smarx.com/posts/using-the-new-windows-azure-cdn-with-a-custom-domain"&gt;a custom domain name on a CDN endpoint&lt;/a&gt; to cache blobs. &lt;/li&gt; &lt;/ol&gt;  &lt;h2&gt;Background: email and Windows Azure&lt;/h2&gt;  &lt;p&gt;Sending email is more complicated than you might think. Jeff Atwood has a blog post called “&lt;a href="http://www.codinghorror.com/blog/2010/04/so-youd-like-to-send-some-email-through-code.html"&gt;So You’d Like to Send Some Email (Through Code)&lt;/a&gt;” that sums up some of the complexities.&lt;/p&gt;  &lt;p&gt;Sending email directly from a cloud like Windows Azure presents further challenges, because you don’t have a dedicated IP address, and it’s quite likely that spammers will use Windows Azure (if they haven’t already) to send truckloads of spam. Once that happens, spam blacklists will quickly flag the IP range of Windows Azure data centers as sources of spam. That means your legitimate email will stop getting through.&lt;/p&gt;  &lt;p&gt;The best solution to these challenges is to &lt;em&gt;not &lt;/em&gt;send email dircetly from Windows Azure. Instead, relay all email through a third-party SMTP service (like &lt;a href="http://sendgrid.com/"&gt;SendGrid&lt;/a&gt; or &lt;a href="http://authsmtp.com/"&gt;AuthSMTP&lt;/a&gt;) with strict anti-spam rules and perhaps dedicated IP addresses.&lt;/p&gt;  &lt;p&gt;Note that &lt;em&gt;receiving&lt;/em&gt; email is a completely different story. As long as people are willing to send email to your domain, you can receive it in Windows Azure by just listening on port 25 for SMTP traffic.&lt;/p&gt;  &lt;h2&gt;Sending email using a third-party service&lt;/h2&gt;  &lt;p&gt;The first thing I did was sign up for a free account with &lt;a href="http://sendgrid.com"&gt;SendGrid&lt;/a&gt;. The free tier allows me to send up to 200 emails per day, but it doesn’t give me access to some of the advanced features. I’d recommend using at least the Silver tier if you’re serious about your email being delivered and looking correct for all users.&lt;/p&gt;  &lt;p&gt;&lt;a href="http://cdn.blog.smarx.com/images/sendgrid%5B4%5D"&gt;&lt;img style="border-right-width: 0px; display: block; float: none; border-top-width: 0px; border-bottom-width: 0px; margin-left: auto; border-left-width: 0px; margin-right: auto" title="sendgrid" border="0" alt="sendgrid" src="http://cdn.blog.smarx.com/images/sendgrid_thumb%5B2%5D" width="644" height="399" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;Once I completed the quick signup, I took down all the details, which I added as configuration settings to my project:&lt;/p&gt;  &lt;p&gt;&amp;#160;&lt;a href="http://cdn.blog.smarx.com/images/smtpinfo%5B4%5D"&gt;&lt;img style="border-right-width: 0px; display: block; float: none; border-top-width: 0px; border-bottom-width: 0px; margin-left: auto; border-left-width: 0px; margin-right: auto" title="smtpinfo" border="0" alt="smtpinfo" src="http://cdn.blog.smarx.com/images/smtpinfo_thumb%5B2%5D" width="644" height="263" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;Actually sending email is incredibly easy using the &lt;code&gt;System.Net.Mail&lt;/code&gt; namespace. Here’s the code that sends email replies for &lt;a href="http://emailtheinternet.com"&gt;EmailTheInternet.com&lt;/a&gt;:&lt;/p&gt;  &lt;blockquote&gt;   &lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;reply = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;MailMessage&lt;/span&gt;(&lt;span style="color: #2b91af"&gt;RoleEnvironment&lt;/span&gt;.GetConfigurationSettingValue(&lt;span style="color: #a31515"&gt;&amp;quot;EmailAddress&amp;quot;&lt;/span&gt;),
    msg.FromAddress)
{
    Subject = msg.Subject.StartsWith(&lt;span style="color: #a31515"&gt;&amp;quot;RE:&amp;quot;&lt;/span&gt;, &lt;span style="color: #2b91af"&gt;StringComparison&lt;/span&gt;.InvariantCultureIgnoreCase)
                                            ? msg.Subject : &lt;span style="color: blue"&gt;string&lt;/span&gt;.Format(&lt;span style="color: #a31515"&gt;&amp;quot;RE: &amp;quot; &lt;/span&gt;+ msg.Subject),
    Body = body,
    IsBodyHtml = msg.HasHtmlBody &lt;span style="color: green"&gt;// send HTML if we got HTML
&lt;/span&gt;};
&lt;span style="color: blue"&gt;if &lt;/span&gt;(!reply.IsBodyHtml) reply.BodyEncoding = &lt;span style="color: #2b91af"&gt;Encoding&lt;/span&gt;.UTF8;
&lt;span style="color: green"&gt;// make it a proper reply
&lt;/span&gt;reply.Headers[&lt;span style="color: #a31515"&gt;&amp;quot;References&amp;quot;&lt;/span&gt;] = msg.MessageID;
reply.Headers[&lt;span style="color: #a31515"&gt;&amp;quot;In-Reply-To&amp;quot;&lt;/span&gt;] = msg.MessageID;
&lt;span style="color: green"&gt;// use our SMTP server, port, username, and password to send the mail
&lt;/span&gt;(&lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;SmtpClient&lt;/span&gt;(&lt;span style="color: #2b91af"&gt;RoleEnvironment&lt;/span&gt;.GetConfigurationSettingValue(&lt;span style="color: #a31515"&gt;&amp;quot;SmtpServer&amp;quot;&lt;/span&gt;),
    &lt;span style="color: blue"&gt;int&lt;/span&gt;.Parse(&lt;span style="color: #2b91af"&gt;RoleEnvironment&lt;/span&gt;.GetConfigurationSettingValue(&lt;span style="color: #a31515"&gt;&amp;quot;SmtpPort&amp;quot;&lt;/span&gt;)))
{
    Credentials = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;NetworkCredential&lt;/span&gt;(&lt;span style="color: #2b91af"&gt;RoleEnvironment&lt;/span&gt;.GetConfigurationSettingValue(&lt;span style="color: #a31515"&gt;&amp;quot;SmtpUsername&amp;quot;&lt;/span&gt;),
        &lt;span style="color: #2b91af"&gt;RoleEnvironment&lt;/span&gt;.GetConfigurationSettingValue(&lt;span style="color: #a31515"&gt;&amp;quot;SmtpPassword&amp;quot;&lt;/span&gt;))
}).Send(reply);&lt;/pre&gt;
&lt;/blockquote&gt;

&lt;p&gt;Yes, it’s really that easy. If you take a look at &lt;a href="http://cdn.blog.smarx.com/files/EmailTheInternet_source.zip"&gt;the code&lt;/a&gt;, you’ll see that there’s much more work involved in constructing the HTML body of the email than in sending it via SMTP.&lt;/p&gt;

&lt;p&gt;Note that for &lt;a href="http://emailtheinternet.com"&gt;EmailTheInternet.com&lt;/a&gt;, I’m using &lt;a href="http://sendgrid.com"&gt;SendGrid&lt;/a&gt;’s free pricing tier, which means I get a limited number of emails per day, and I don’t get a dedicated IP address or &lt;a href="http://wiki.sendgrid.com/doku.php?id=whitelabel"&gt;whitelabeling&lt;/a&gt;. Because of this, my emails may not all make it through spam filters, and some email clients will show the emails as coming from sendgrid “on behalf of &lt;a href="mailto:post@emailtheinternet.com"&gt;post@emailtheinternet.com&lt;/a&gt;.” This is simply because I haven’t paid for those services, not because of some limitation of this approach.&lt;/p&gt;

&lt;h2&gt;Receiving email using a worker role&lt;/h2&gt;

&lt;p&gt;I was surprised how hard it was to find good, free libraries for receiving email in C#. I settled on &lt;a href="http://www.ericdaugherty.com/dev/cses/developers.html"&gt;Eric Daugherty’s C# Email Server (CSES)&lt;/a&gt; to receive the email, and then I added &lt;a href="http://anmar.eu.org/projects/sharpmimetools/"&gt;SharpMimeTools&lt;/a&gt; to handle the complex task of decoding MIME emails with attachments.&lt;/p&gt;

&lt;p&gt;You can read the code for the gory details, but I essentially do two things:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Start a &lt;code&gt;TcpListener&lt;/code&gt; in &lt;code&gt;OnStart()&lt;/code&gt; to listen on the appropriate port. &lt;/li&gt;

  &lt;li&gt;Start a loop in &lt;code&gt;Run()&lt;/code&gt; that handles each incoming email by saving everything to blobs and replying via email. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here’s the code that initializes our SMTP handler (part of CSES) and starts a &lt;code&gt;TcpListener&lt;/code&gt; listening on the right port (called from &lt;code&gt;OnStart()&lt;/code&gt;):&lt;/p&gt;

&lt;blockquote&gt;
  &lt;pre class="code"&gt;listener = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;TcpListener&lt;/span&gt;(&lt;span style="color: #2b91af"&gt;IPAddress&lt;/span&gt;.Any,
    &lt;span style="color: #2b91af"&gt;RoleEnvironment&lt;/span&gt;.CurrentRoleInstance.InstanceEndpoints[&lt;span style="color: #a31515"&gt;&amp;quot;SmtpIn&amp;quot;&lt;/span&gt;].IPEndpoint.Port);
processor = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;SMTPProcessor&lt;/span&gt;(&lt;span style="color: #2b91af"&gt;RoleEnvironment&lt;/span&gt;.GetConfigurationSettingValue(&lt;span style="color: #a31515"&gt;&amp;quot;DomainName&amp;quot;&lt;/span&gt;),
    &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;RecipientFilter&lt;/span&gt;(), &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;MessageSpool&lt;/span&gt;());
listener.Start();&lt;/pre&gt;
&lt;/blockquote&gt;

&lt;p&gt;Note that I’m using the service runtime API to determine the correct port.&lt;/p&gt;

&lt;p&gt;Here’s the simple asynchronous handling of incoming TCP connections (called from &lt;code&gt;Run()&lt;/code&gt;):&lt;/p&gt;

&lt;blockquote&gt;
  &lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;mutex = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;ManualResetEvent&lt;/span&gt;(&lt;span style="color: blue"&gt;false&lt;/span&gt;);
&lt;span style="color: blue"&gt;while &lt;/span&gt;(&lt;span style="color: blue"&gt;true&lt;/span&gt;)
{
    mutex.Reset();
    listener.BeginAcceptSocket((ar) =&amp;gt;
        {
            mutex.Set();
            processor.ProcessConnection(listener.EndAcceptSocket(ar));
        }, &lt;span style="color: blue"&gt;null&lt;/span&gt;);
    mutex.WaitOne();
}&lt;/pre&gt;
&lt;/blockquote&gt;

&lt;p&gt;Finally, here’s the code that handles an incoming email:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;pre class="code"&gt;&lt;span style="color: green"&gt;// make a container, with public access to blobs
&lt;/span&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;id = &lt;span style="color: #2b91af"&gt;Guid&lt;/span&gt;.NewGuid().ToString().Replace(&lt;span style="color: #a31515"&gt;&amp;quot;-&amp;quot;&lt;/span&gt;, &lt;span style="color: blue"&gt;null&lt;/span&gt;);
&lt;span style="color: blue"&gt;var &lt;/span&gt;container = account.CreateCloudBlobClient().GetContainerReference(id);
container.Create();
container.SetPermissions(&lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;BlobContainerPermissions&lt;/span&gt;() { PublicAccess=&lt;span style="color: #2b91af"&gt;BlobContainerPublicAccessType&lt;/span&gt;.Blob });

&lt;span style="color: green"&gt;// parse the message
&lt;/span&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;msg = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;SharpMessage&lt;/span&gt;(&lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;MemoryStream&lt;/span&gt;(&lt;span style="color: #2b91af"&gt;Encoding&lt;/span&gt;.ASCII.GetBytes(message.Data)),
    &lt;span style="color: #2b91af"&gt;SharpDecodeOptions&lt;/span&gt;.AllowAttachments | &lt;span style="color: #2b91af"&gt;SharpDecodeOptions&lt;/span&gt;.AllowHtml | &lt;span style="color: #2b91af"&gt;SharpDecodeOptions&lt;/span&gt;.DecodeTnef);

&lt;span style="color: green"&gt;// create a permalink-style name for the blob
&lt;/span&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;permalink = &lt;span style="color: #2b91af"&gt;Regex&lt;/span&gt;.Replace(&lt;span style="color: #2b91af"&gt;Regex&lt;/span&gt;.Replace(msg.Subject.ToLower(), &lt;span style="color: #a31515"&gt;@&amp;quot;[^a-z0-9]&amp;quot;&lt;/span&gt;, &lt;span style="color: #a31515"&gt;&amp;quot;-&amp;quot;&lt;/span&gt;), &lt;span style="color: #a31515"&gt;&amp;quot;--+&amp;quot;&lt;/span&gt;, &lt;span style="color: #a31515"&gt;&amp;quot;-&amp;quot;&lt;/span&gt;).Trim(&lt;span style="color: #a31515"&gt;'-'&lt;/span&gt;);
&lt;span style="color: blue"&gt;if &lt;/span&gt;(&lt;span style="color: blue"&gt;string&lt;/span&gt;.IsNullOrEmpty(permalink))
{
    &lt;span style="color: green"&gt;// in case there's no subject
    &lt;/span&gt;permalink = &lt;span style="color: #a31515"&gt;&amp;quot;message&amp;quot;&lt;/span&gt;;
}
&lt;span style="color: blue"&gt;var &lt;/span&gt;bodyBlob = container.GetBlobReference(permalink);
&lt;span style="color: green"&gt;// set the CDN to cache the object for 2 hours
&lt;/span&gt;bodyBlob.Properties.CacheControl = &lt;span style="color: #a31515"&gt;&amp;quot;max-age=7200&amp;quot;&lt;/span&gt;;

&lt;span style="color: green"&gt;// replaces references to attachments with the URL of where we'll put them
&lt;/span&gt;msg.SetUrlBase(&lt;span style="color: #2b91af"&gt;Utility&lt;/span&gt;.GetCdnUrlForUri(bodyBlob.Uri) + &lt;span style="color: #a31515"&gt;&amp;quot;/[Name]&amp;quot;&lt;/span&gt;);

&lt;span style="color: green"&gt;// save each attachment in a blob, setting the appropriate content type
&lt;/span&gt;&lt;span style="color: blue"&gt;foreach &lt;/span&gt;(&lt;span style="color: #2b91af"&gt;SharpAttachment &lt;/span&gt;attachment &lt;span style="color: blue"&gt;in &lt;/span&gt;msg.Attachments)
{
    &lt;span style="color: blue"&gt;var &lt;/span&gt;blob = container.GetBlobReference(permalink + &lt;span style="color: #a31515"&gt;&amp;quot;/&amp;quot; &lt;/span&gt;+ attachment.Name);
    blob.Properties.ContentType = attachment.MimeTopLevelMediaType + &lt;span style="color: #a31515"&gt;&amp;quot;/&amp;quot; &lt;/span&gt;+ attachment.MimeMediaSubType;
    blob.Properties.CacheControl = &lt;span style="color: #a31515"&gt;&amp;quot;max-age=7200&amp;quot;&lt;/span&gt;;
    attachment.Stream.Position = 0;
    blob.UploadFromStream(attachment.Stream);
}
&lt;span style="color: green"&gt;// add the footer and save the body to the blob
&lt;/span&gt;SaveBody(msg, bodyBlob, message, container, permalink);&lt;/pre&gt;
&lt;/blockquote&gt;

&lt;p&gt;From there, it’s just a matter of sending the reply, which we’ve already seen.&lt;/p&gt;

&lt;h2&gt;Custom domains and CDN&lt;/h2&gt;

&lt;p&gt;Using a custom domain name is a &lt;em&gt;must &lt;/em&gt;for your email-enabled application if you want to take advantage of email verification mechanisms like SendGrid’s &lt;a href="http://wiki.sendgrid.com/doku.php?id=whitelabel"&gt;whitelabel&lt;/a&gt; features (including &lt;a href="http://wiki.sendgrid.com/doku.php?id=domain_keys"&gt;Domain Keys&lt;/a&gt;). This is because those features require that you’re able to change DNS settings for your domain. I’m not taking advantage of those features, but I decided to use a custom domain name anyway.&lt;/p&gt;

&lt;p&gt;I’ve &lt;a href="http://blog.smarx.com/posts/using-the-new-windows-azure-cdn-with-a-custom-domain"&gt;covered the Windows Azure CDN before&lt;/a&gt;, but I thought it’s worth going over again. The Windows Azure CDN is perfect for an application where this, where (in my imagination) things that get published this way will potentially be viewed by many people around the world. I decided to turn on the CDN for this demo’s blob storage account. I also decided to use custom domain names for the CDN endpoint (&lt;a href="http://content.emailtheinternet.com"&gt;http://content.emailtheinternet.com&lt;/a&gt;).&lt;/p&gt;

&lt;h3&gt;Step 1: Set up the CNAME record for &lt;a href="http://emailtheinternet.com/"&gt;EmailTheInternet.com&lt;/a&gt;&lt;/h3&gt;

&lt;p&gt;&lt;a href="http://blog.smarx.com/posts/custom-domain-names-in-windows-azure"&gt;Setting up a custom domain with a Windows Azure application&lt;/a&gt; is easy. I just went to my domain registrar (&lt;a href="http://www.godaddy.com"&gt;GoDaddy&lt;/a&gt;) and set up domain forwarding from &lt;a href="http://emailtheinternet.com/"&gt;emailtheinternet.com&lt;/a&gt; to &lt;a href="http://www.emailtheinternet.com"&gt;www.emailtheinternet.com&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="http://cdn.blog.smarx.com/images/forwarding%5B4%5D"&gt;&lt;img style="border-right-width: 0px; display: block; float: none; border-top-width: 0px; border-bottom-width: 0px; margin-left: auto; border-left-width: 0px; margin-right: auto" title="forwarding" border="0" alt="forwarding" src="http://cdn.blog.smarx.com/images/forwarding_thumb%5B2%5D" width="644" height="370" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then I added a CNAME record that maps “www” to &lt;a href="emailtheinternet.cloudapp.net"&gt;emailtheinternet.cloudapp.net&lt;/a&gt; (the domain I got from Windows Azure):&lt;/p&gt;

&lt;p&gt;&lt;a href="http://cdn.blog.smarx.com/images/CNAME%5B4%5D"&gt;&lt;img style="border-right-width: 0px; display: block; float: none; border-top-width: 0px; border-bottom-width: 0px; margin-left: auto; border-left-width: 0px; margin-right: auto" title="CNAME" border="0" alt="CNAME" src="http://cdn.blog.smarx.com/images/CNAME_thumb%5B2%5D" width="644" height="231" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, because I want email to get routed correctly, I needed to add an MX record also maping to &lt;a href="http://emailtheinternet.com/"&gt;emailtheinternet.cloudapp.net&lt;/a&gt;:&lt;/p&gt;

&lt;h3&gt;&lt;a href="http://cdn.blog.smarx.com/images/MX%5B4%5D"&gt;&lt;img style="border-right-width: 0px; display: block; float: none; border-top-width: 0px; border-bottom-width: 0px; margin-left: auto; border-left-width: 0px; margin-right: auto" title="MX" border="0" alt="MX" src="http://cdn.blog.smarx.com/images/MX_thumb%5B2%5D" width="644" height="231" /&gt;&lt;/a&gt;&lt;/h3&gt;

&lt;h3&gt;Step 2: Enable the CDN for my storage account&lt;/h3&gt;

&lt;p&gt;Adding a CDN endpoint to your storage account is as easy as clicking one button. Once I clicked “Enable CDN,” I got a generic CDN endpoint (az2919.vo.smecnd.net):&lt;/p&gt;

&lt;p&gt;&lt;a href="http://cdn.blog.smarx.com/images/CDN%20enabled%5B4%5D"&gt;&lt;img style="border-right-width: 0px; display: block; float: none; border-top-width: 0px; border-bottom-width: 0px; margin-left: auto; border-left-width: 0px; margin-right: auto" title="CDN enabled" border="0" alt="CDN enabled" src="http://cdn.blog.smarx.com/images/CDN%20enabled_thumb%5B2%5D" width="644" height="190" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;Step 3: Add a custom domain name for the CDN endpoint&lt;/h3&gt;

&lt;p&gt;Adding a custom domain name for a storage account or CDN endpoint consists of the same process. First I clicked “Manage” next to the CDN endpoint and entered a custom domain name:&lt;/p&gt;

&lt;p&gt;&lt;a href="http://cdn.blog.smarx.com/images/CDN%20custom%20domain%5B5%5D"&gt;&lt;img style="border-right-width: 0px; display: block; float: none; border-top-width: 0px; border-bottom-width: 0px; margin-left: auto; border-left-width: 0px; margin-right: auto" title="CDN custom domain" border="0" alt="CDN custom domain" src="http://cdn.blog.smarx.com/images/CDN%20custom%20domain_thumb%5B3%5D" width="644" height="125" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I then clicked the “Generate Key” button, which is the first step towards validating that I own that custom domain:&lt;/p&gt;

&lt;p&gt;&lt;a href="http://cdn.blog.smarx.com/images/CDN%20custom%20domain%20key%5B4%5D"&gt;&lt;img style="border-right-width: 0px; display: block; float: none; border-top-width: 0px; border-bottom-width: 0px; margin-left: auto; border-left-width: 0px; margin-right: auto" title="CDN custom domain key" border="0" alt="CDN custom domain key" src="http://cdn.blog.smarx.com/images/CDN%20custom%20domain%20key_thumb%5B2%5D" width="644" height="205" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Following the instructions, I added a CNAME record mapping the generated name to verify.azure.com.&lt;/p&gt;

&lt;p&gt;&lt;a href="http://cdn.blog.smarx.com/images/CDN%20custom%20domain%20verification%5B4%5D"&gt;&lt;img style="border-right-width: 0px; display: block; float: none; border-top-width: 0px; border-bottom-width: 0px; margin-left: auto; border-left-width: 0px; margin-right: auto" title="CDN custom domain verification" border="0" alt="CDN custom domain verification" src="http://cdn.blog.smarx.com/images/CDN%20custom%20domain%20verification_thumb%5B2%5D" width="644" height="231" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then I want back to the portal and clicked through to validate the domain. Here’s the screen showing the custom domain name validated.&lt;/p&gt;

&lt;p&gt;&lt;a href="http://cdn.blog.smarx.com/images/CDN%20custom%20domain%20validated%5B4%5D"&gt;&lt;img style="border-right-width: 0px; display: block; float: none; border-top-width: 0px; border-bottom-width: 0px; margin-left: auto; border-left-width: 0px; margin-right: auto" title="CDN custom domain validated" border="0" alt="CDN custom domain validated" src="http://cdn.blog.smarx.com/images/CDN%20custom%20domain%20validated_thumb%5B2%5D" width="644" height="191" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once the domain name was validated, I went back to my domain registrar and mapped the “content” CNAME to the CDN endpoint:&lt;a href="http://cdn.blog.smarx.com/images/CDN%20CNAME%5B4%5D"&gt;&lt;img style="border-right-width: 0px; display: block; float: none; border-top-width: 0px; border-bottom-width: 0px; margin-left: auto; border-left-width: 0px; margin-right: auto" title="CDN CNAME" border="0" alt="CDN CNAME" src="http://cdn.blog.smarx.com/images/CDN%20CNAME_thumb%5B2%5D" width="644" height="231" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The whole process took only a few minutes.&lt;/p&gt;

&lt;p&gt;You may have noticed in the code snippets above that I’m calling a method called &lt;code&gt;Utility.GetCdnUrlForUri()&lt;/code&gt;. This method transforms blob URIs to use the CDN host, which I store as a configuration setting:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;pre class="code"&gt;&lt;span style="color: blue"&gt;public static string &lt;/span&gt;GetCdnUrlForUri(&lt;span style="color: #2b91af"&gt;Uri &lt;/span&gt;uri)
{
    &lt;span style="color: blue"&gt;var &lt;/span&gt;builder = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;UriBuilder&lt;/span&gt;(uri);
    builder.Host = &lt;span style="color: #2b91af"&gt;RoleEnvironment&lt;/span&gt;.GetConfigurationSettingValue(&lt;span style="color: #a31515"&gt;&amp;quot;CdnHostName&amp;quot;&lt;/span&gt;);
    &lt;span style="color: blue"&gt;return &lt;/span&gt;builder.Uri.AbsoluteUri;
}&lt;/pre&gt;
&lt;/blockquote&gt;

&lt;h2&gt;Download the code&lt;/h2&gt;

&lt;p&gt;You can &lt;a href="http://cdn.blog.smarx.com/files/EmailTheInternet_source.zip"&gt;download the Visual Studio 2010 solution here&lt;/a&gt;, but note that I have not included the dependencies. Here’s where you can download them:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="http://www.ericdaugherty.com/dev/cses/developers.html"&gt;Eric Daugherty’s C# Email Server (CSES)&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;&lt;a href="http://anmar.eu.org/projects/sharpmimetools/"&gt;SharpMimeTools&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Thanks, Twitter followers!&lt;/h2&gt;

&lt;p&gt;A special thanks to my followers on Twitter who helped me test this (and fix a few bugs) before I released the code here. I really appreciate the help!&lt;/p&gt;  </content></entry><entry><id>http://blog.smarx.com/atompub.svc/blog/posts/serving-your-website-from-a-windows-azure-drive</id><title type="text">Serving Your Website From a Windows Azure Drive</title><published>2010-05-14T20:23:59Z</published><updated>2010-05-14T20:23:59Z</updated><author><name>Steve Marx</name><uri>http://smarx.com</uri><email>steve.marx@microsoft.com</email></author><link rel="alternate" href="http://blog.smarx.com/posts/serving-your-website-from-a-windows-azure-drive"/><link rel="edit" href="serving-your-website-from-a-windows-azure-drive"/><content type="html">&lt;p&gt;For &lt;a href="http://channel9.msdn.com/shows/Cloud+Cover/Cloud-Cover-Episode-11-Drives-and-IIS-Hostable-Worker-Core/"&gt;this week’s episode of Cloud Cover&lt;/a&gt;, &lt;a href="http://dunnry.com"&gt;Ryan&lt;/a&gt; and I showed how to mount a &lt;a href="http://go.microsoft.com/?linkid=9710117"&gt;Windows Azure drive&lt;/a&gt; and serve your website from it using my &lt;a href="http://code.msdn.microsoft.com/hwcworker"&gt;Hosted Web Core Worker Role&lt;/a&gt;. I’ve released a new Visual Studio 2010 solution called &lt;code&gt;HWCWorker_Drive_source.zip&lt;/code&gt; that mounts a snapshot of a Windows Azure Drive and points Hosted Web Core at it. You can download it over on the &lt;a href="http://code.msdn.microsoft.com/hwcworker"&gt;Hosted Web Core Worker Role project on Code Gallery&lt;/a&gt;.&lt;/p&gt;  &lt;p&gt;To use the project, you need to store your web content in a VHD, upload it to the cloud, take a snapshot of the VHD, and then configure the project to use that snapshot. To make changes, just upload the new version of the VHD, take a new snapshot, and change the configuration setting in the project.&lt;/p&gt;  &lt;p&gt;In the rest of this post, I’ll walk through how the various steps work.&lt;/p&gt;  &lt;h2&gt;Creating a VHD on Windows 7 or Windows Server&lt;/h2&gt;  &lt;p&gt;Yeah, you could &lt;a href="http://windows7news.com/2009/08/25/how-to-create-and-configure-a-vhd-in-windows-7/"&gt;be a wuss and use the GUI&lt;/a&gt;, but for those of you who love the command-line (as I do), I’ve included three scripts along with the code that create, mount, and unmount VHDs.&lt;/p&gt;  &lt;p&gt;The first script, &lt;code&gt;createvhd.cmd&lt;/code&gt;, creates a VHD of the specified size and assigns it a drive letter:&lt;/p&gt;  &lt;blockquote&gt;   &lt;pre class="code"&gt;&lt;span style="background: white; color: magenta"&gt;@&lt;/span&gt;&lt;span style="background: white; color: blue"&gt;echo&lt;/span&gt;&lt;span style="background: white"&gt; off
&lt;/span&gt;&lt;span style="background: white; color: blue"&gt;if&lt;/span&gt;&lt;span style="background: white"&gt; &amp;quot;&lt;/span&gt;&lt;span style="background: #fcfff0; color: #ff8000"&gt;%2&lt;/span&gt;&lt;span style="background: white"&gt;&amp;quot;&lt;/span&gt;&lt;span style="background: white; color: red"&gt;==&lt;/span&gt;&lt;span style="background: white"&gt;&amp;quot;&amp;quot; &lt;/span&gt;&lt;span style="background: white; color: blue"&gt;goto&lt;/span&gt;&lt;span style="background: white"&gt; usage
&lt;/span&gt;&lt;span style="background: white; color: blue"&gt;set&lt;/span&gt;&lt;span style="background: white"&gt; driveletter&lt;/span&gt;&lt;span style="background: white; color: red"&gt;=&lt;/span&gt;&lt;span style="background: #fcfff0; color: #ff8000"&gt;%3
&lt;/span&gt;&lt;span style="background: white"&gt;&lt;/span&gt;&lt;span style="background: white; color: blue"&gt;if&lt;/span&gt;&lt;span style="background: white"&gt; driveletter&lt;/span&gt;&lt;span style="background: white; color: red"&gt;==&lt;/span&gt;&lt;span style="background: white"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span style="background: white; color: blue"&gt; set&lt;/span&gt;&lt;span style="background: white"&gt; driveletter&lt;/span&gt;&lt;span style="background: white; color: red"&gt;=&lt;/span&gt;&lt;span style="background: white"&gt;v
&lt;/span&gt;&lt;span style="background: white; color: blue"&gt;echo&lt;/span&gt;&lt;span style="background: white"&gt; create vdisk file&lt;/span&gt;&lt;span style="background: white; color: red"&gt;=&lt;/span&gt;&lt;span style="background: white"&gt;&amp;quot;&lt;/span&gt;&lt;span style="background: #fcfff0; color: #ff8000"&gt;%~f1&lt;/span&gt;&lt;span style="background: white"&gt;&amp;quot; maximum&lt;/span&gt;&lt;span style="background: white; color: red"&gt;=&lt;/span&gt;&lt;span style="background: #fcfff0; color: #ff8000"&gt;%2&lt;/span&gt;&lt;span style="background: white"&gt; type&lt;/span&gt;&lt;span style="background: white; color: red"&gt;=&lt;/span&gt;&lt;span style="background: white"&gt;fixed &lt;/span&gt;&lt;span style="background: white; color: red"&gt;&amp;gt;&lt;/span&gt;&lt;span style="background: white"&gt; &amp;quot;&lt;/span&gt;&lt;span style="background: #fcfff0; color: #ff8000"&gt;%TEMP%&lt;/span&gt;&lt;span style="background: white"&gt;\script.txt&amp;quot;
&lt;/span&gt;&lt;span style="background: white; color: blue"&gt;echo&lt;/span&gt;&lt;span style="background: white"&gt; select vdisk file&lt;/span&gt;&lt;span style="background: white; color: red"&gt;=&lt;/span&gt;&lt;span style="background: white"&gt;&amp;quot;&lt;/span&gt;&lt;span style="background: #fcfff0; color: #ff8000"&gt;%~f1&lt;/span&gt;&lt;span style="background: white"&gt;&amp;quot; &lt;/span&gt;&lt;span style="background: white; color: red"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style="background: white"&gt; &amp;quot;&lt;/span&gt;&lt;span style="background: #fcfff0; color: #ff8000"&gt;%TEMP%&lt;/span&gt;&lt;span style="background: white"&gt;\script.txt&amp;quot;
&lt;/span&gt;&lt;span style="background: white; color: blue"&gt;echo&lt;/span&gt;&lt;span style="background: white"&gt; attach vdisk &lt;/span&gt;&lt;span style="background: white; color: red"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style="background: white"&gt; &amp;quot;&lt;/span&gt;&lt;span style="background: #fcfff0; color: #ff8000"&gt;%TEMP%&lt;/span&gt;&lt;span style="background: white"&gt;\script.txt&amp;quot;
&lt;/span&gt;&lt;span style="background: white; color: blue"&gt;echo&lt;/span&gt;&lt;span style="background: white"&gt; create partition primary &lt;/span&gt;&lt;span style="background: white; color: red"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style="background: white"&gt; &amp;quot;&lt;/span&gt;&lt;span style="background: #fcfff0; color: #ff8000"&gt;%TEMP%&lt;/span&gt;&lt;span style="background: white"&gt;\script.txt&amp;quot;
&lt;/span&gt;&lt;span style="background: white; color: blue"&gt;echo&lt;/span&gt;&lt;span style="background: white"&gt; assign letter&lt;/span&gt;&lt;span style="background: white; color: red"&gt;=&lt;/span&gt;&lt;span style="background: #fcfff0; color: #ff8000"&gt;%driveletter%&lt;/span&gt;&lt;span style="background: white"&gt; &lt;/span&gt;&lt;span style="background: white; color: red"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style="background: white"&gt; &amp;quot;&lt;/span&gt;&lt;span style="background: #fcfff0; color: #ff8000"&gt;%TEMP%&lt;/span&gt;&lt;span style="background: white"&gt;\script.txt&amp;quot;
&lt;/span&gt;&lt;span style="background: white; color: blue"&gt;echo&lt;/span&gt;&lt;span style="background: white"&gt; format fs&lt;/span&gt;&lt;span style="background: white; color: red"&gt;=&lt;/span&gt;&lt;span style="background: white"&gt;ntfs label&lt;/span&gt;&lt;span style="background: white; color: red"&gt;=&lt;/span&gt;&lt;span style="background: white"&gt;vhd quick &lt;/span&gt;&lt;span style="background: white; color: red"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style="background: white"&gt; &amp;quot;&lt;/span&gt;&lt;span style="background: #fcfff0; color: #ff8000"&gt;%TEMP%&lt;/span&gt;&lt;span style="background: white"&gt;\script.txt&amp;quot;
&lt;/span&gt;&lt;span style="background: white; color: #0080ff"&gt;diskpart&lt;/span&gt;&lt;span style="background: white"&gt; -s &lt;/span&gt;&lt;span style="background: #fcfff0; color: #ff8000"&gt;%TEMP%&lt;/span&gt;&lt;span style="background: white"&gt;\script.txt
&lt;/span&gt;&lt;span style="background: white; color: blue"&gt;echo&lt;/span&gt;&lt;span style="background: white"&gt; VHD should be available shortly at &amp;quot;&lt;/span&gt;&lt;span style="background: #fcfff0; color: #ff8000"&gt;%driveletter%&lt;/span&gt;&lt;span style="background: white"&gt;:&amp;quot;
&lt;/span&gt;&lt;span style="background: white; color: blue"&gt;goto&lt;/span&gt;&lt;span style="background: white"&gt; exit
&lt;/span&gt;&lt;span style="background: #ffff80; color: red"&gt;:usage
&lt;/span&gt;&lt;span style="background: white; color: blue"&gt;echo&lt;/span&gt;&lt;span style="background: white"&gt; USAGE: createvhd.cmd &lt;/span&gt;&lt;span style="background: white; color: red"&gt;&amp;lt;&lt;/span&gt;&lt;span style="background: white"&gt;path-to-vhd&lt;/span&gt;&lt;span style="background: white; color: red"&gt;&amp;gt;&lt;/span&gt;&lt;span style="background: white"&gt; &lt;/span&gt;&lt;span style="background: white; color: red"&gt;&amp;lt;&lt;/span&gt;&lt;span style="background: white"&gt;size-in-megabytes&lt;/span&gt;&lt;span style="background: white; color: red"&gt;&amp;gt;
&lt;/span&gt;&lt;span style="background: white"&gt;&lt;/span&gt;&lt;span style="background: #ffff80; color: red"&gt;:exit&lt;/span&gt;&lt;/pre&gt;
&lt;/blockquote&gt;

&lt;p&gt;This also mounts the drive, so you can go copy in your web content. To unmount the drive (which you must do before uploading it to the cloud), you can use &lt;code&gt;unmountvhd.cmd&lt;/code&gt;:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;pre class="code"&gt;&lt;span style="background: white; color: magenta"&gt;@&lt;/span&gt;&lt;span style="background: white; color: blue"&gt;echo&lt;/span&gt;&lt;span style="background: white"&gt; off
&lt;/span&gt;&lt;span style="background: white; color: blue"&gt;if&lt;/span&gt;&lt;span style="background: white"&gt; &amp;quot;&lt;/span&gt;&lt;span style="background: #fcfff0; color: #ff8000"&gt;%1&lt;/span&gt;&lt;span style="background: white"&gt;&amp;quot;&lt;/span&gt;&lt;span style="background: white; color: red"&gt;==&lt;/span&gt;&lt;span style="background: white"&gt;&amp;quot;&amp;quot; &lt;/span&gt;&lt;span style="background: white; color: blue"&gt;goto&lt;/span&gt;&lt;span style="background: white"&gt; usage
&lt;/span&gt;&lt;span style="background: white; color: blue"&gt;echo&lt;/span&gt;&lt;span style="background: white"&gt; select vdisk file&lt;/span&gt;&lt;span style="background: white; color: red"&gt;=&lt;/span&gt;&lt;span style="background: white"&gt;&amp;quot;&lt;/span&gt;&lt;span style="background: #fcfff0; color: #ff8000"&gt;%~f1&lt;/span&gt;&lt;span style="background: white"&gt;&amp;quot; &lt;/span&gt;&lt;span style="background: white; color: red"&gt;&amp;gt;&lt;/span&gt;&lt;span style="background: white"&gt; &amp;quot;&lt;/span&gt;&lt;span style="background: #fcfff0; color: #ff8000"&gt;%TEMP%&lt;/span&gt;&lt;span style="background: white"&gt;\script.txt&amp;quot;
&lt;/span&gt;&lt;span style="background: white; color: blue"&gt;echo&lt;/span&gt;&lt;span style="background: white"&gt; detach vdisk &lt;/span&gt;&lt;span style="background: white; color: red"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style="background: white"&gt; &amp;quot;&lt;/span&gt;&lt;span style="background: #fcfff0; color: #ff8000"&gt;%TEMP%&lt;/span&gt;&lt;span style="background: white"&gt;\script.txt&amp;quot;
&lt;/span&gt;&lt;span style="background: white; color: #0080ff"&gt;diskpart&lt;/span&gt;&lt;span style="background: white"&gt; -s &lt;/span&gt;&lt;span style="background: #fcfff0; color: #ff8000"&gt;%TEMP%&lt;/span&gt;&lt;span style="background: white"&gt;\script.txt
&lt;/span&gt;&lt;span style="background: white; color: blue"&gt;goto&lt;/span&gt;&lt;span style="background: white"&gt; exit
&lt;/span&gt;&lt;span style="background: #ffff80; color: red"&gt;:usage
&lt;/span&gt;&lt;span style="background: white; color: blue"&gt;echo&lt;/span&gt;&lt;span style="background: white"&gt; USAGE: unmountvhd.cmd &lt;/span&gt;&lt;span style="background: white; color: red"&gt;&amp;lt;&lt;/span&gt;&lt;span style="background: white"&gt;path-to-vhd&lt;/span&gt;&lt;span style="background: white; color: red"&gt;&amp;gt;
&lt;/span&gt;&lt;span style="background: white"&gt;&lt;/span&gt;&lt;span style="background: #ffff80; color: red"&gt;:exit&lt;/span&gt;&lt;/pre&gt;
&lt;/blockquote&gt;

&lt;p&gt;And finally, to mount it again (to make changes), you can use &lt;code&gt;mountvhd.cmd&lt;/code&gt;:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;pre class="code"&gt;&lt;span style="background: white; color: magenta"&gt;@&lt;/span&gt;&lt;span style="background: white; color: blue"&gt;echo&lt;/span&gt;&lt;span style="background: white"&gt; off
&lt;/span&gt;&lt;span style="background: white; color: blue"&gt;if&lt;/span&gt;&lt;span style="background: white"&gt; &amp;quot;&lt;/span&gt;&lt;span style="background: #fcfff0; color: #ff8000"&gt;%1&lt;/span&gt;&lt;span style="background: white"&gt;&amp;quot;&lt;/span&gt;&lt;span style="background: white; color: red"&gt;==&lt;/span&gt;&lt;span style="background: white"&gt;&amp;quot;&amp;quot; &lt;/span&gt;&lt;span style="background: white; color: blue"&gt;goto&lt;/span&gt;&lt;span style="background: white"&gt; usage
&lt;/span&gt;&lt;span style="background: white; color: blue"&gt;echo&lt;/span&gt;&lt;span style="background: white"&gt; select vdisk file&lt;/span&gt;&lt;span style="background: white; color: red"&gt;=&lt;/span&gt;&lt;span style="background: white"&gt;&amp;quot;&lt;/span&gt;&lt;span style="background: #fcfff0; color: #ff8000"&gt;%~f1&lt;/span&gt;&lt;span style="background: white"&gt;&amp;quot; &lt;/span&gt;&lt;span style="background: white; color: red"&gt;&amp;gt;&lt;/span&gt;&lt;span style="background: white"&gt; &amp;quot;&lt;/span&gt;&lt;span style="background: #fcfff0; color: #ff8000"&gt;%TEMP%&lt;/span&gt;&lt;span style="background: white"&gt;\script.txt&amp;quot;
&lt;/span&gt;&lt;span style="background: white; color: blue"&gt;echo&lt;/span&gt;&lt;span style="background: white"&gt; attach vdisk &lt;/span&gt;&lt;span style="background: white; color: red"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style="background: white"&gt; &amp;quot;&lt;/span&gt;&lt;span style="background: #fcfff0; color: #ff8000"&gt;%TEMP%&lt;/span&gt;&lt;span style="background: white"&gt;\script.txt&amp;quot;
&lt;/span&gt;&lt;span style="background: white; color: #0080ff"&gt;diskpart&lt;/span&gt;&lt;span style="background: white"&gt; -s &lt;/span&gt;&lt;span style="background: #fcfff0; color: #ff8000"&gt;%TEMP%&lt;/span&gt;&lt;span style="background: white"&gt;\script.txt
&lt;/span&gt;&lt;span style="background: white; color: blue"&gt;echo&lt;/span&gt;&lt;span style="background: white"&gt; VHD should be available shortly at the drive letter you assigned when you created it.
&lt;/span&gt;&lt;span style="background: white; color: blue"&gt;goto&lt;/span&gt;&lt;span style="background: white"&gt; exit
&lt;/span&gt;&lt;span style="background: #ffff80; color: red"&gt;:usage
&lt;/span&gt;&lt;span style="background: white; color: blue"&gt;echo&lt;/span&gt;&lt;span style="background: white"&gt; USAGE: mountvhd.cmd &lt;/span&gt;&lt;span style="background: white; color: red"&gt;&amp;lt;&lt;/span&gt;&lt;span style="background: white"&gt;path-to-vhd&lt;/span&gt;&lt;span style="background: white; color: red"&gt;&amp;gt;
&lt;/span&gt;&lt;span style="background: white"&gt;&lt;/span&gt;&lt;span style="background: #ffff80; color: red"&gt;:exit&lt;/span&gt;&lt;/pre&gt;
&lt;/blockquote&gt;

&lt;h2&gt;Uploading and Snapshotting the VHD&lt;/h2&gt;

&lt;p&gt;One of the projects included in the &lt;code&gt;HWCWorker_Drive_source.zip&lt;/code&gt; solution is a command-line tool called &lt;code&gt;UploadAndSnapshot.exe&lt;/code&gt;. It does exactly what the name suggests: it uploads a VHD (or really any file) as a page blob in Windows Azure storage, takes a snapshot of that blob, and then returns the full URL to the snapshot. You can take the output of this command and put it directly into the &lt;code&gt;DriveSnapshotUrl&lt;/code&gt; configuration setting of the cloud project. This is the snapshot that the application will mount and serve your website from.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;UploadAndSnapshot&lt;/code&gt; uses the &lt;a href="http://blogs.msdn.com/windowsazurestorage/archive/2010/04/11/using-windows-azure-page-blobs-and-how-to-efficiently-upload-and-download-page-blobs.aspx"&gt;technique described on the Windows Azure storage team blog&lt;/a&gt; to efficiently upload the VHD by including only the non-zero pages. This is important both for efficiency reasons (less bytes to upload) and for cost reasons (only charged for the pages uploaded).&lt;/p&gt;

&lt;p&gt;The usage of &lt;code&gt;UploadAndSnapshot&lt;/code&gt; is simple. Run it without any parameters to see the usage:&lt;/p&gt;
&lt;code&gt;
  &lt;blockquote&gt;
    &lt;pre&gt;Usage UploadAndSnapshot.exe &amp;lt;connectionstring&amp;gt; &amp;lt;path-to-local-file&amp;gt; &amp;lt;url-to-uploaded-blob&amp;gt;
e.g. UploadAndSnapshot.exe UseDefaultEndpointsProtocol=http;AccountName=myaccount;AccountKey=1F84D...
    c:\mydrive.vhd drives/mydrive.vhd&lt;/pre&gt;
  &lt;/blockquote&gt;
&lt;/code&gt;

&lt;h2&gt;Mounting and Using the VHD&lt;/h2&gt;

&lt;p&gt;To use a Windows Azure drive, you just need to create a local cache and then mount the drive. The following code handles this in &lt;code&gt;HWCWorker_Drive&lt;/code&gt; in &lt;code&gt;OnStart()&lt;/code&gt;:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;localCache = &lt;span style="color: #2b91af"&gt;RoleEnvironment&lt;/span&gt;.GetLocalResource(&lt;span style="color: #a31515"&gt;&amp;quot;DriveCache&amp;quot;&lt;/span&gt;);
&lt;span style="color: #2b91af"&gt;CloudDrive&lt;/span&gt;.InitializeCache(localCache.RootPath.TrimEnd(&lt;span style="color: #a31515"&gt;'\\'&lt;/span&gt;), localCache.MaximumSizeInMegabytes);

&lt;span style="color: blue"&gt;var &lt;/span&gt;cloudAccount = &lt;span style="color: #2b91af"&gt;CloudStorageAccount&lt;/span&gt;.Parse(
    &lt;span style="color: #2b91af"&gt;RoleEnvironment&lt;/span&gt;.GetConfigurationSettingValue(&lt;span style="color: #a31515"&gt;&amp;quot;DriveConnectionString&amp;quot;&lt;/span&gt;));
drive = cloudAccount.CreateCloudDrive(&lt;span style="color: #2b91af"&gt;RoleEnvironment&lt;/span&gt;.GetConfigurationSettingValue(&lt;span style="color: #a31515"&gt;&amp;quot;DriveSnapshotUrl&amp;quot;&lt;/span&gt;));

&lt;span style="color: blue"&gt;var &lt;/span&gt;drivePath = drive.Mount(localCache.MaximumSizeInMegabytes, &lt;span style="color: #2b91af"&gt;DriveMountOptions&lt;/span&gt;.None);&lt;/pre&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then in &lt;code&gt;OnStop()&lt;/code&gt; I call &lt;code&gt;drive.Unmount()&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;Using the Development Fabric&lt;/h2&gt;

&lt;p&gt;Windows Azure drives work completely differently in the development fabric. Instead of mounting an actual VHD, development storage simulates it by creating a directory on the local filesystem, and it &lt;em&gt;can not&lt;/em&gt; be used to mount a VHD located in the cloud. I haven’t played around with doing this on development storage enough to talk through it yet, so I’ll probably follow up with a post in the future about how to do this.&lt;/p&gt;

&lt;p&gt;For now, my solution only works in the cloud.&lt;/p&gt;

&lt;h2&gt;&lt;strike&gt;Known Issues&lt;/strike&gt;&lt;/h2&gt;

&lt;p&gt;&lt;strike&gt;After deploying this solution to the cloud, changing the snapshot configuration setting seems to work inconsistently. Sometimes it works fine, but sometimes a role instance will cycle, continually restarting. You can, of course, deploy the application again with the new snapshot to staging, and then swap it into production.&lt;/strike&gt;&lt;/p&gt;

&lt;p&gt;&lt;strike&gt;I need to add some logging to debug this further. If I figure out the mystery, I’ll update this post, but I wanted to get the code out now.&lt;/strike&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[UPDATE 5/14/2010] &lt;/strong&gt;After a number of attempts to reproduce the error, I think it may have simply been caused by a simple error on my part specifying a bogus snapshot. If someone sees this problem, please let me know so I can investigate.&lt;/p&gt;

&lt;h2&gt;More Information&lt;/h2&gt;

&lt;p&gt;If you’re interested in learning more about Windows Azure drives, I recommend reading the following blog posts on the &lt;a href="http://blogs.msdn.com/windowsazurestorage"&gt;Windows Azure storage team blog&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="http://blogs.msdn.com/windowsazurestorage/archive/2010/03/29/windows-azure-drive-demo-at-mix-2010.aspx"&gt;Windows Azure Drive Demo at MIX 2010&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;&lt;a href="http://blogs.msdn.com/windowsazurestorage/archive/2010/04/11/using-windows-azure-page-blobs-and-how-to-efficiently-upload-and-download-page-blobs.aspx"&gt;Using Windows Azure Page Blobs and How to Efficiently Upload and Download Page Blobs&lt;/a&gt; &lt;/li&gt;

  &lt;li&gt;&lt;a href="http://blogs.msdn.com/windowsazurestorage/archive/2010/05/10/windows-azure-storage-abstractions-and-their-scalability-targets.aspx"&gt;Windows Azure Storage Abstractions and their Scalability Targets&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You should also read the &lt;a href="http://go.microsoft.com/?linkid=9710117"&gt;Windows Azure Drives whitepaper&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;Get the Code&lt;/h2&gt;

&lt;p&gt;You can download the &lt;code&gt;HWCWorker_drive_source.zip&lt;/code&gt; package over on the &lt;a href="http://code.msdn.microsoft.com/hwcworker"&gt;Hosted Web Core Worker Role project on Code Gallery&lt;/a&gt;.&lt;/p&gt;  </content></entry><entry><id>http://blog.smarx.com/atompub.svc/blog/posts/shared-access-signatures-are-easy-these-days</id><title type="text">Shared Access Signatures Are Easy These Days</title><published>2010-05-02T16:22:34Z</published><updated>2010-05-02T16:22:34Z</updated><author><name>Steve Marx</name><uri>http://smarx.com</uri><email>steve.marx@microsoft.com</email></author><link rel="alternate" href="http://blog.smarx.com/posts/shared-access-signatures-are-easy-these-days"/><link rel="edit" href="shared-access-signatures-are-easy-these-days"/><content type="html">&lt;p&gt;I wrote a blog post back when Shared Access Signatures were first released called “&lt;a href="http://blog.smarx.com/posts/new-storage-feature-signed-access-signatures"&gt;New Storage Feature: Shared Access Signatures&lt;/a&gt;,” which gave some sample code to use what was then a brand new feature in Windows Azure storage (and not supported by the storage client library).&lt;/p&gt;  &lt;p&gt;These days, using Shared Access Signatures is much simpler. I just wrote some .NET code that uses the &lt;code&gt;Microsoft.WindowsAzure.StorageClient&lt;/code&gt; library to do the following:&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;Create a blob. &lt;/li&gt;    &lt;li&gt;Generate a Shared Access Signature (SAS) for that blob that allows read and write access. &lt;/li&gt;    &lt;li&gt;Display a working URL to the blob. &lt;/li&gt;    &lt;li&gt;Modify and read back the blob using only the SAS for authorization. &lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;Here’s the code:&lt;/p&gt;  &lt;blockquote&gt;   &lt;pre class="code"&gt;&lt;span style="color: green"&gt;// regular old blob storage
&lt;/span&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;account = &lt;span style="color: #2b91af"&gt;CloudStorageAccount&lt;/span&gt;.DevelopmentStorageAccount; &lt;span style="color: green"&gt;// or your cloud account
&lt;/span&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;container = account
    .CreateCloudBlobClient()
    .GetContainerReference(&lt;span style="color: #a31515"&gt;&amp;quot;testcontainer&amp;quot;&lt;/span&gt;);
container.CreateIfNotExist();
&lt;span style="color: blue"&gt;var &lt;/span&gt;blob = container.GetBlobReference(&lt;span style="color: #a31515"&gt;&amp;quot;test.txt&amp;quot;&lt;/span&gt;);
blob.Properties.ContentType = &lt;span style="color: #a31515"&gt;&amp;quot;text/plain&amp;quot;&lt;/span&gt;;
blob.UploadText(&lt;span style="color: #a31515"&gt;&amp;quot;Hello, World!&amp;quot;&lt;/span&gt;);

&lt;span style="color: green"&gt;// create a shared access signature (looks like a query param: ?se=...)
&lt;/span&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;sas = blob.GetSharedAccessSignature(&lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;SharedAccessPolicy&lt;/span&gt;()
    {
        Permissions = &lt;span style="color: #2b91af"&gt;SharedAccessPermissions&lt;/span&gt;.Read
                        |&lt;span style="color: #2b91af"&gt;SharedAccessPermissions&lt;/span&gt;.Write,
        SharedAccessExpiryTime = &lt;span style="color: #2b91af"&gt;DateTime&lt;/span&gt;.UtcNow + &lt;span style="color: #2b91af"&gt;TimeSpan&lt;/span&gt;.FromMinutes(5)
    });
&lt;span style="color: #2b91af"&gt;Console&lt;/span&gt;.WriteLine(&lt;span style="color: #a31515"&gt;&amp;quot;This link should work for the next five minutes:&amp;quot;&lt;/span&gt;);
&lt;span style="color: #2b91af"&gt;Console&lt;/span&gt;.WriteLine(blob.Uri.AbsoluteUri + sas);

&lt;span style="color: green"&gt;// now just use the SAS to do blob operations
&lt;/span&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;sasCreds = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;StorageCredentialsSharedAccessSignature&lt;/span&gt;(sas);
&lt;span style="color: green"&gt;// new client using the same endpoint (including account name),
//   but using the SAS as the credentials
&lt;/span&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;sasBlob = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;CloudBlobClient&lt;/span&gt;(account.BlobEndpoint, sasCreds)
    .GetBlobReference(&lt;span style="color: #a31515"&gt;&amp;quot;testcontainer/test.txt&amp;quot;&lt;/span&gt;);
sasBlob.UploadText(&lt;span style="color: #a31515"&gt;&amp;quot;Hello again!&amp;quot;&lt;/span&gt;);
&lt;span style="color: #2b91af"&gt;Console&lt;/span&gt;.WriteLine(sasBlob.DownloadText());&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;a href="http://11011.net/software/vspaste"&gt;&lt;/a&gt;

&lt;p&gt;There’s nothing more to it than that! For more details about Shared Access Signatures, see “&lt;a href="http://channel9.msdn.com/shows/Cloud+Cover/Cloud-Cover-Episode-8-Shared-Access-Signatures/"&gt;Cloud Cover Episode 8: Shared Access Signatures&lt;/a&gt;” or the MSDN documentation on &lt;a href="http://msdn.microsoft.com/en-us/library/ee395415.aspx"&gt;the details of signature itself&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[UPDATE 6/4/2010]&lt;/strong&gt; I didn’t show how to use Signed Identifiers the first time around, but never fear!&amp;#160; It’s easy too. Here’s how to add an access policy to a container and use that in a Shared Access Signature:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;pre class="code"&gt;&lt;span style="color: blue"&gt;var &lt;/span&gt;permissions = container.GetPermissions();
permissions.SharedAccessPolicies.Remove(&lt;span style="color: #a31515"&gt;&amp;quot;readonly&amp;quot;&lt;/span&gt;);
permissions.SharedAccessPolicies.Add(&lt;span style="color: #a31515"&gt;&amp;quot;readonly&amp;quot;&lt;/span&gt;, &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;SharedAccessPolicy&lt;/span&gt;()
    {
        Permissions = &lt;span style="color: #2b91af"&gt;SharedAccessPermissions&lt;/span&gt;.Read
    });
container.SetPermissions(permissions, &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;BlobRequestOptions&lt;/span&gt;()
{
    &lt;span style="color: green"&gt;// fail if someone else has already changed the container before we do
    &lt;/span&gt;AccessCondition = &lt;span style="color: #2b91af"&gt;AccessCondition&lt;/span&gt;.IfMatch(container.Properties.ETag)
});

&lt;span style="color: blue"&gt;var &lt;/span&gt;sasWithIdentifier = blob.GetSharedAccessSignature(&lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;SharedAccessPolicy&lt;/span&gt;()
    {
        SharedAccessExpiryTime = &lt;span style="color: #2b91af"&gt;DateTime&lt;/span&gt;.UtcNow + &lt;span style="color: #2b91af"&gt;TimeSpan&lt;/span&gt;.FromDays(7)
    }, &lt;span style="color: #a31515"&gt;&amp;quot;readonly&amp;quot;&lt;/span&gt;);

&lt;span style="color: #2b91af"&gt;Console&lt;/span&gt;.WriteLine(&lt;span style="color: #a31515"&gt;&amp;quot;This link should work for the next seven days:&amp;quot;&lt;/span&gt;);
&lt;span style="color: #2b91af"&gt;Console&lt;/span&gt;.WriteLine(blob.Uri.AbsoluteUri + sasWithIdentifier);&lt;/pre&gt;&lt;/blockquote&gt;  </content></entry><entry><id>http://blog.smarx.com/atompub.svc/blog/posts/update-your-windows-azure-website-in-just-seconds-by-syncing-with-blob-storage</id><title type="text">Update Your Windows Azure Website in Just Seconds by Syncing with Blob Storage</title><published>2010-04-29T17:41:57Z</published><updated>2010-04-29T17:41:57Z</updated><author><name>Steve Marx</name><uri>http://smarx.com</uri><email>steve.marx@microsoft.com</email></author><link rel="alternate" href="http://blog.smarx.com/posts/update-your-windows-azure-website-in-just-seconds-by-syncing-with-blob-storage"/><link rel="edit" href="update-your-windows-azure-website-in-just-seconds-by-syncing-with-blob-storage"/><content type="html">&lt;p&gt;One of the coolest uses I’ve found for my &lt;a href="http://code.msdn.microsoft.com/hwcworker"&gt;Windows Azure Hosted Web Core Worker Role&lt;/a&gt; is to sync my website with blob storage, letting me change files at will and immediately see the results in the cloud. You can &lt;a href="http://code.msdn.microsoft.com/hwcworker"&gt;grab the code&lt;/a&gt; over on Code Gallery, or a prebuilt package that you can deploy right away.&lt;/p&gt;  &lt;p&gt;Here’s a &lt;strong&gt;&lt;a href="http://www.youtube.com/watch?v=SQpAIshEAjA&amp;amp;hd=1"&gt;30-second video&lt;/a&gt;&lt;/strong&gt; showing it in action:&lt;/p&gt;  &lt;div style="padding-bottom: 0px; margin: 0px auto; padding-left: 0px; width: 425px; padding-right: 0px; display: block; float: none; padding-top: 0px"&gt;&lt;object width="480" height="385"&gt;&lt;param name="movie" value="http://www.youtube.com/v/SQpAIshEAjA&amp;amp;hd=1&amp;amp;fs=1"&gt;&lt;/param&gt;&lt;param name="allowFullScreen" value="true"&gt;&lt;/param&gt;&lt;param name="hd" value="1"&gt;&lt;param name="allowscriptaccess" value="always"&gt;&lt;/param&gt;&lt;embed src="http://www.youtube.com/v/SQpAIshEAjA&amp;amp;hd=1&amp;amp;fs=1" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="480" height="385"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;/div&gt;  &lt;h2&gt;How It Works&lt;/h2&gt;  &lt;p&gt;In a typical Windows Azure web role, IIS is configured to host the website contained in your application package. As you may know, this content is in a read-only directory on the virtual machine. That means that to change anything in your website, you need to redeploy your application.&lt;/p&gt;  &lt;p&gt;With the &lt;a href="http://code.msdn.microsoft.com/hwcworker"&gt;Hosted Web Core Worker Role&lt;/a&gt;, I can point Hosted Web Core (HWC) at any local directory by just changing &lt;code&gt;applicationHost.config&lt;/code&gt;. This means I can have a writable local directory (using &lt;a href="http://msdn.microsoft.com/en-us/library/ee758708.aspx"&gt;local storage resources&lt;/a&gt;), and I can populate that directory with the website content I fetch from blob storage. Then it’s just a matter of continually syncing those blobs as they change.&lt;/p&gt;  &lt;p&gt;The class that does all the work is called &lt;code&gt;OneWayBlobSync&lt;/code&gt;. It’s used in &lt;code&gt;WorkerRole.cs&lt;/code&gt; like this in &lt;code&gt;OnStart()&lt;/code&gt;:&lt;/p&gt;  &lt;blockquote&gt;   &lt;pre class="code"&gt;sync = &lt;span style="color: blue"&gt;new &lt;/span&gt;&lt;span style="color: #2b91af"&gt;OneWayBlobSync&lt;/span&gt;(
    container,
    &lt;span style="color: #2b91af"&gt;RoleEnvironment&lt;/span&gt;.GetLocalResource(&lt;span style="color: #a31515"&gt;&amp;quot;Websites&amp;quot;&lt;/span&gt;).RootPath,
    &lt;span style="color: #2b91af"&gt;TimeSpan&lt;/span&gt;.FromSeconds(&lt;span style="color: blue"&gt;int&lt;/span&gt;.Parse(
        &lt;span style="color: #2b91af"&gt;RoleEnvironment&lt;/span&gt;.GetConfigurationSettingValue(&lt;span style="color: #a31515"&gt;&amp;quot;SyncIntervalInSeconds&amp;quot;&lt;/span&gt;))));&lt;/pre&gt;
&lt;/blockquote&gt;

&lt;p&gt;and then in &lt;code&gt;Run()&lt;/code&gt;:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;pre class="code"&gt;sync.Start();&lt;/pre&gt;
&lt;/blockquote&gt;

&lt;p&gt;Under the hood, this class does the following:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;List all the blobs in the source container. &lt;/li&gt;

  &lt;li&gt;Delete any local blobs that are no longer in the source container. &lt;/li&gt;

  &lt;li&gt;Compare the blobs’ ETags with the corresponding local files, and update any changed blobs. &lt;/li&gt;

  &lt;li&gt;Repeat. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That’s it!&lt;/p&gt;

&lt;h2&gt;Drawbacks&lt;/h2&gt;

&lt;p&gt;There are a few drawbacks to using this approach:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Updating the website happens at roughly the same time on all instances. No rolling upgrade means potential downtime for your application. &lt;/li&gt;

  &lt;li&gt;Copying over files that are in use might have unintended consequences (or might not work at all). For most ASP.NET content (including web.config), this seems to work fine, but I wouldn’t be surprised to learn that some files can’t be modified this way. My code doesn’t handle any sort of concurrency errors. &lt;/li&gt;

  &lt;li&gt;There are no atomic updates. During a sync, some files might be the old versions and others the new versions. There’s no way with this method to make sure everything changes at once. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The bottom line is that I’d hesitate to use this in production. But it’s &lt;em&gt;awesome&lt;/em&gt; as a development tool.&lt;/p&gt;

&lt;h2&gt;Get the Code&lt;/h2&gt;

&lt;p&gt;You can download the full source code or a prebuilt package over at the &lt;a href="http://code.msdn.microsoft.com/hwcworker"&gt;Windows Azure Hosted Web Core Worker Role&lt;/a&gt; on Code Gallery. With the prebuilt package, you can simply update the configuration file (&lt;code&gt;ServiceConfiguration.cscfg&lt;/code&gt;) and deploy. Then you can copy the website to blob storage whenever you’re ready.&lt;/p&gt;  </content></entry><entry><id>http://blog.smarx.com/atompub.svc/blog/posts/iis-compression-in-windows-azure</id><title type="text">IIS Compression in Windows Azure</title><published>2010-04-22T17:12:25Z</published><updated>2010-04-22T17:12:25Z</updated><author><name>Steve Marx</name><uri>http://smarx.com</uri><email>steve.marx@microsoft.com</email></author><link rel="alternate" href="http://blog.smarx.com/posts/iis-compression-in-windows-azure"/><link rel="edit" href="iis-compression-in-windows-azure"/><content type="html">&lt;p&gt;One change you may have noticed in &lt;a href="http://msdn.microsoft.com/en-us/library/ff436045.aspx"&gt;the latest operating system release&lt;/a&gt; in Windows Azure is that the dynamic compression module has been turned on in IIS. This means that without doing anything, you should now see the default dynamic compression settings take effect.&lt;/p&gt;  &lt;h2&gt;Changing the Defaults&lt;/h2&gt;  &lt;p&gt;Compression settings are primarily controlled by two configuration elements: &lt;code&gt;&amp;lt;urlCompression&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;httpCompression&amp;gt;&lt;/code&gt;.&lt;/p&gt;  &lt;p&gt;&lt;code&gt;&lt;a href="http://msdn.microsoft.com/en-us/library/aa347437(VS.90).aspx"&gt;&amp;lt;urlCompression&amp;gt;&lt;/a&gt;&lt;/code&gt; can be configured at the application level in &lt;code&gt;web.config&lt;/code&gt;, and it lets you turn on and off dynamic and static compression. By default, dynamic compression is turned off, so you &lt;em&gt;may &lt;/em&gt;want to add the following line to your &lt;code&gt;web.config&lt;/code&gt; file (but see the word of caution at the end of this post):&lt;/p&gt;  &lt;blockquote&gt;   &lt;pre class="code"&gt;&lt;span style="color: blue"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;urlCompression &lt;/span&gt;&lt;span style="color: red"&gt;doDynamicCompression&lt;/span&gt;&lt;span style="color: blue"&gt;=&lt;/span&gt;&amp;quot;&lt;span style="color: blue"&gt;true&lt;/span&gt;&amp;quot; &lt;span style="color: red"&gt;dynamicCompressionBeforeCache&lt;/span&gt;&lt;span style="color: blue"&gt;=&lt;/span&gt;&amp;quot;&lt;span style="color: blue"&gt;true&lt;/span&gt;&amp;quot; &lt;span style="color: blue"&gt;/&amp;gt;&lt;/span&gt;&lt;/pre&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code&gt;&lt;a href="http://msdn.microsoft.com/en-us/library/ms690689(VS.90).aspx"&gt;&amp;lt;httpCompression&amp;gt;&lt;/a&gt;&lt;/code&gt; can only be configured at the level of &lt;code&gt;applicationHost.config&lt;/code&gt;, so with today’s web role, you’ll get configuration that looks like the following (though the directory attribute will be different):&lt;/p&gt;

&lt;blockquote&gt;
  &lt;pre class="code"&gt;&lt;span style="color: blue"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;httpCompression &lt;/span&gt;&lt;span style="color: red"&gt;directory&lt;/span&gt;&lt;span style="color: blue"&gt;=&lt;/span&gt;&amp;quot;&lt;span style="color: blue"&gt;%SystemDrive%\inetpub\temp\IIS Temporary Compressed Files&lt;/span&gt;&amp;quot;
    &lt;span style="color: red"&gt;lockAttributes&lt;/span&gt;&lt;span style="color: blue"&gt;=&lt;/span&gt;&amp;quot;&lt;span style="color: blue"&gt;directory&lt;/span&gt;&amp;quot;&lt;span style="color: blue"&gt;&amp;gt;
  &amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;scheme &lt;/span&gt;&lt;span style="color: red"&gt;name&lt;/span&gt;&lt;span style="color: blue"&gt;=&lt;/span&gt;&amp;quot;&lt;span style="color: blue"&gt;gzip&lt;/span&gt;&amp;quot; &lt;span style="color: red"&gt;dll&lt;/span&gt;&lt;span style="color: blue"&gt;=&lt;/span&gt;&amp;quot;&lt;span style="color: blue"&gt;%Windir%\system32\inetsrv\gzip.dll&lt;/span&gt;&amp;quot; &lt;span style="color: blue"&gt;/&amp;gt;
  &amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;dynamicTypes&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
    &amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;add &lt;/span&gt;&lt;span style="color: red"&gt;mimeType&lt;/span&gt;&lt;span style="color: blue"&gt;=&lt;/span&gt;&amp;quot;&lt;span style="color: blue"&gt;text/*&lt;/span&gt;&amp;quot; &lt;span style="color: red"&gt;enabled&lt;/span&gt;&lt;span style="color: blue"&gt;=&lt;/span&gt;&amp;quot;&lt;span style="color: blue"&gt;true&lt;/span&gt;&amp;quot; &lt;span style="color: blue"&gt;/&amp;gt;
    &amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;add &lt;/span&gt;&lt;span style="color: red"&gt;mimeType&lt;/span&gt;&lt;span style="color: blue"&gt;=&lt;/span&gt;&amp;quot;&lt;span style="color: blue"&gt;message/*&lt;/span&gt;&amp;quot; &lt;span style="color: red"&gt;enabled&lt;/span&gt;&lt;span style="color: blue"&gt;=&lt;/span&gt;&amp;quot;&lt;span style="color: blue"&gt;true&lt;/span&gt;&amp;quot; &lt;span style="color: blue"&gt;/&amp;gt;
    &amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;add &lt;/span&gt;&lt;span style="color: red"&gt;mimeType&lt;/span&gt;&lt;span style="color: blue"&gt;=&lt;/span&gt;&amp;quot;&lt;span style="color: blue"&gt;application/x-javascript&lt;/span&gt;&amp;quot; &lt;span style="color: red"&gt;enabled&lt;/span&gt;&lt;span style="color: blue"&gt;=&lt;/span&gt;&amp;quot;&lt;span style="color: blue"&gt;true&lt;/span&gt;&amp;quot; &lt;span style="color: blue"&gt;/&amp;gt;
    &amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;add &lt;/span&gt;&lt;span style="color: red"&gt;mimeType&lt;/span&gt;&lt;span style="color: blue"&gt;=&lt;/span&gt;&amp;quot;&lt;span style="color: blue"&gt;*/*&lt;/span&gt;&amp;quot; &lt;span style="color: red"&gt;enabled&lt;/span&gt;&lt;span style="color: blue"&gt;=&lt;/span&gt;&amp;quot;&lt;span style="color: blue"&gt;false&lt;/span&gt;&amp;quot; &lt;span style="color: blue"&gt;/&amp;gt;
  &amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;dynamicTypes&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
  &amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;staticTypes&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
    &amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;add &lt;/span&gt;&lt;span style="color: red"&gt;mimeType&lt;/span&gt;&lt;span style="color: blue"&gt;=&lt;/span&gt;&amp;quot;&lt;span style="color: blue"&gt;text/*&lt;/span&gt;&amp;quot; &lt;span style="color: red"&gt;enabled&lt;/span&gt;&lt;span style="color: blue"&gt;=&lt;/span&gt;&amp;quot;&lt;span style="color: blue"&gt;true&lt;/span&gt;&amp;quot; &lt;span style="color: blue"&gt;/&amp;gt;
    &amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;add &lt;/span&gt;&lt;span style="color: red"&gt;mimeType&lt;/span&gt;&lt;span style="color: blue"&gt;=&lt;/span&gt;&amp;quot;&lt;span style="color: blue"&gt;message/*&lt;/span&gt;&amp;quot; &lt;span style="color: red"&gt;enabled&lt;/span&gt;&lt;span style="color: blue"&gt;=&lt;/span&gt;&amp;quot;&lt;span style="color: blue"&gt;true&lt;/span&gt;&amp;quot; &lt;span style="color: blue"&gt;/&amp;gt;
    &amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;add &lt;/span&gt;&lt;span style="color: red"&gt;mimeType&lt;/span&gt;&lt;span style="color: blue"&gt;=&lt;/span&gt;&amp;quot;&lt;span style="color: blue"&gt;application/javascript&lt;/span&gt;&amp;quot; &lt;span style="color: red"&gt;enabled&lt;/span&gt;&lt;span style="color: blue"&gt;=&lt;/span&gt;&amp;quot;&lt;span style="color: blue"&gt;true&lt;/span&gt;&amp;quot; &lt;span style="color: blue"&gt;/&amp;gt;
    &amp;lt;&lt;/span&gt;&lt;span style="color: #a31515"&gt;add &lt;/span&gt;&lt;span style="color: red"&gt;mimeType&lt;/span&gt;&lt;span style="color: blue"&gt;=&lt;/span&gt;&amp;quot;&lt;span style="color: blue"&gt;*/*&lt;/span&gt;&amp;quot; &lt;span style="color: red"&gt;enabled&lt;/span&gt;&lt;span style="color: blue"&gt;=&lt;/span&gt;&amp;quot;&lt;span style="color: blue"&gt;false&lt;/span&gt;&amp;quot; &lt;span style="color: blue"&gt;/&amp;gt;
  &amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;staticTypes&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;
&amp;lt;/&lt;/span&gt;&lt;span style="color: #a31515"&gt;httpCompression&lt;/span&gt;&lt;span style="color: blue"&gt;&amp;gt;&lt;/span&gt;&lt;/pre&gt;
&lt;/blockquote&gt;

&lt;p&gt;Unfortunately, you can’t change &lt;code&gt;applicationHost.config&lt;/code&gt; settings in today’s Windows Azure web role. However, you &lt;em&gt;can &lt;/em&gt;edit this section in &lt;code&gt;applicationHost.config&lt;/code&gt; using the &lt;a href="http://code.msdn.microsoft.com/hwcworker"&gt;Hosted Web Core Worker Role&lt;/a&gt; project.&lt;/p&gt;

&lt;h2&gt;A Word of Caution&lt;/h2&gt;

&lt;p&gt;Compression settings are tricky, and adding more compression will not necessarily increase the performance of your application. (Sometimes it will do the exact opposite!) Be sure to do your research first, and then test your new settings to make sure they’re having the effect you expected.&lt;/p&gt;  </content></entry></feed>