Building Windows Azure Service Part5: Worker Role Background Tasks Handler

In this post you will create a worker role to read work items posted to a queue by the web role front-end. The worker role performs these tasks:

  • Extract the information about the guest book entry from the message queued by the web role in the Queue Storage.
  • Retrieve the user entry from the Table Storage.
  • Fetch the associated image from the Blob Storage and create a thumbnail and store it is as a blob.
  • Finally, update the entry to include the URL of the generated thumbnail.
To create the worker role
  1. In Solution Explorer, right click the Roles node in the GuestBook project.

  2. Select Add and click New Worker Role Project.

  3. In the Add New Role Project dialog window, select the Worker Role category and choose the Worker Role template for the language of your choice.

  4. In the name box, enter GuestBook_WorkerRole and click Add.

    image

    Figure 7 Creating Worker Role Project

  5. In Solution Explorer, right-click the GuestBook_WorkerRole project and select Add Reference.

  6. Switch to the Projects tab, select the GuestBookData project and click OK.

  7. In Solution Explorer, right-click the GuestBook_WorkerRole project, select Add Reference,

  8. Switch to the .Net tab, select the System.Drawing component and click OK.

  9. Repeat the procedure in the previous steps to add a reference to the storage client API assembly, this time choosing the Microsoft.WindowsAzure.StorageClient component instead.

  10. In the GuestBook_WorkerRole project, open the WorkerRole.cs file. Replace its content with the following code.

     using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;
    using System.Text;
    using System.Threading;
    using Microsoft.WindowsAzure.Diagnostics;
    using Microsoft.WindowsAzure.ServiceRuntime;
    using System.Drawing;
    using System.IO;
    using GuestBook_Data;
    using Microsoft.WindowsAzure;
    using Microsoft.WindowsAzure.StorageClient;
    
    namespace GuestBook_WorkerRole
    {
        public class WorkerRole : RoleEntryPoint
        {
            private CloudQueue queue;
            private CloudBlobContainer container;
    
            public override void Run()
            {
                Trace.TraceInformation("Listening for queue messages...");
    
                while (true)
                {
                    try
                    {
                        // retrieve a new message from the queue
                        CloudQueueMessage msg = queue.GetMessage();
                        if (msg != null)
                        {
                            // parse message retrieved from queue
                            var messageParts = msg.AsString.Split(new char[] { ',' });
                            var uri = messageParts[0];
                            var partitionKey = messageParts[1];
                            var rowkey = messageParts[2];
                            Trace.TraceInformation("Processing image in blob '{0}'.", uri);
    
                            // download original image from blob storage
                            CloudBlockBlob imageBlob = container.GetBlockBlobReference(uri);
                            MemoryStream image = new MemoryStream();
                            imageBlob.DownloadToStream(image);
                            image.Seek(0, SeekOrigin.Begin);
    
                            // create a thumbnail image and upload into a blob
                            string thumbnailUri = String.Concat(Path.GetFileNameWithoutExtension(uri), "_thumb.jpg");
                            CloudBlockBlob thumbnailBlob = container.GetBlockBlobReference(thumbnailUri);
                            thumbnailBlob.UploadFromStream(CreateThumbnail(image));
    
                            // update the entry in table storage to point to the thumbnail
                            var ds = new GuestBookEntryDataSource();
                            ds.UpdateImageThumbnail(partitionKey, rowkey, thumbnailBlob.Uri.AbsoluteUri);
    
                            // remove message from queue
                            queue.DeleteMessage(msg);
    
                            Trace.TraceInformation("Generated thumbnail in blob '{0}'.", thumbnailBlob.Uri);
                        }
                        else
                        {
                            System.Threading.Thread.Sleep(1000);
                        }
                    }
                    catch (StorageClientException e)
                    {
                        Trace.TraceError("Exception when processing queue item. Message: '{0}'", e.Message);
                        System.Threading.Thread.Sleep(5000);
                    }
                }
            }
    
            public override bool OnStart()
            {
                DiagnosticMonitor.Start("DiagnosticsConnectionString");
    
                // Restart the role upon all configuration changes
                RoleEnvironment.Changing += RoleEnvironmentChanging;
    
    
                // Set the global configuration setting publisher for the storage account, which 
                // will be called when the account access keys are updated in the service configuration file.
                // Calling SetConfigurationSettingPublisher in OnStart method is important otherwise the
                // system raises an exception when FromConfigurationSetting is called.
                CloudStorageAccount.SetConfigurationSettingPublisher((configName, configSetter) =>
                {
                    try
                    {
                        configSetter(RoleEnvironment.GetConfigurationSettingValue(configName));
                    }
                    catch (RoleEnvironmentException e)
                    {
    
                        Trace.TraceError(e.Message);
                        System.Threading.Thread.Sleep(5000);
                    }
                });
    
                // Create a new instance of a CloudStorageAccount object from a specified configuration setting. 
                // This method may be called only after the SetConfigurationSettingPublisher 
                // method has been called to configure the global configuration setting publisher.
                // You can call the SetConfigurationSettingPublisher method in the OnStart method
                // of the worker role before calling FromConfigurationSetting.
                // If you do not do this, the system raises an exception. 
                var storageAccount = 
                    CloudStorageAccount.FromConfigurationSetting("DataConnectionString");
    
                // initialize blob storage
                CloudBlobClient blobStorage = storageAccount.CreateCloudBlobClient();
                container = blobStorage.GetContainerReference("guestbookpics");
    
                // initialize queue storage 
                CloudQueueClient queueStorage = storageAccount.CreateCloudQueueClient();
                queue = queueStorage.GetQueueReference("guestthumbs");
    
                Trace.TraceInformation("Creating container and queue...");
    
                bool storageInitialized = false;
                while (!storageInitialized)
                {
                    try
                    {
                        // create the blob container and allow public access
                        container.CreateIfNotExist();
                        // container.CreateIfNotExist();
                        var permissions = container.GetPermissions();
                        permissions.PublicAccess = BlobContainerPublicAccessType.Container;
                        container.SetPermissions(permissions);
    
                        // create the message queue
                        queue.CreateIfNotExist();
                        storageInitialized = true;
                    }
                    catch (StorageClientException e)
                    {
                        if (e.ErrorCode == StorageErrorCode.TransportError)
                        {
                            Trace.TraceError("Storage services initialization failure. "
                              + "Check your storage account configuration settings. If running locally, "
                              + "ensure that the Development Storage service is running. Message: '{0}'", e.Message);
                            System.Threading.Thread.Sleep(5000);
                        }
                        else
                        {
                            throw;
                        }
                    }
                }
    
                return base.OnStart();
            }
    
            private void RoleEnvironmentChanging(object sender, RoleEnvironmentChangingEventArgs e)
            {
                if (e.Changes.Any(change => change is RoleEnvironmentConfigurationSettingChange))
                    e.Cancel = true;
            }
    
            private Stream CreateThumbnail(Stream input)
            {
                var orig = new Bitmap(input);
                int width;
                int height;
    
                if (orig.Width > orig.Height)
                {
                    width = 128;
                    height = 128 * orig.Height / orig.Width;
                }
                else
                {
                    height = 128;
                    width = 128 * orig.Width / orig.Height;
                }
    
                var thumb = new Bitmap(width, height);
    
                using (Graphics graphic = Graphics.FromImage(thumb))
                {
                    graphic.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
                    graphic.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
                    graphic.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
                    graphic.DrawImage(orig, 0, 0, width, height);
                    var ms = new MemoryStream();
                    thumb.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
                    ms.Seek(0, SeekOrigin.Begin);
                    return ms;
                }
            }
        }
    }
    
  11. Save and close the WorkerRole.cs file.

    To configure the storage account used for worker role

    In order for the worker role to use Windows Azure storage services, you must provide account settings as described next.

  12. In Solution Explorer, expand the Role node in the GuestBook project.

  13. Double click GuestBook_WokerRole to open the properties for this role and select Settings tab.

  14. Click Add Settings.

  15. In the Name column, enter DataConnectionString.

  16. In the Type column, from the drop-down list, select ConnectionString.

  17. In the Value column, from the drop-down list, select Use development storage.

    image

    Figure 8 Configuring Storage Account For Worker Role

  18. Click OK.

  19. Press Ctrl+S to save your changes.

For related topics, see the following posts.