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
In Solution Explorer, right click the Roles node in the GuestBook project.
Select Add and click New Worker Role Project.
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.
In the name box, enter GuestBook_WorkerRole and click Add.
Figure 7 Creating Worker Role Project
In Solution Explorer, right-click the GuestBook_WorkerRole project and select Add Reference.
Switch to the Projects tab, select the GuestBookData project and click OK.
In Solution Explorer, right-click the GuestBook_WorkerRole project, select Add Reference,
Switch to the .Net tab, select the System.Drawing component and click OK.
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.
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; } } } }
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.
In Solution Explorer, expand the Role node in the GuestBook project.
Double click GuestBook_WokerRole to open the properties for this role and select Settings tab.
Click Add Settings.
In the Name column, enter DataConnectionString.
In the Type column, from the drop-down list, select ConnectionString.
In the Value column, from the drop-down list, select Use development storage.
Figure 8 Configuring Storage Account For Worker Role
Click OK.
Press Ctrl+S to save your changes.
For related topics, see the following posts.
- Building Windows Azure Service Part1: Introduction
- Building Windows Azure Service Part2: Service Project
- Building Windows Azure Service Part3: Table Storage
- Building Windows Azure Service Part4: Web Role UI Handler
- Building Windows Azure Service Part6: Service Configuration
- Building Windows Azure Service Part7: Service Testing
Comments
- Anonymous
March 30, 2011
All the code is truncated on the right side.