Created
October 2, 2013 13:25
-
-
Save BretStateham/6793709 to your computer and use it in GitHub Desktop.
"Introduction to Cloud Services" Hands-On Lab Completed Code
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Microsoft.WindowsAzure.Storage.Table; | |
using Microsoft.WindowsAzure.Storage.Table.DataServices; | |
using System.Linq; | |
namespace GuestBook_Data | |
{ | |
public class GuestBookDataContext : TableServiceContext | |
{ | |
public GuestBookDataContext(CloudTableClient client) : base(client) | |
{ } | |
public IQueryable<GuestBookEntry> GuestBookEntry | |
{ | |
get | |
{ | |
return this.CreateQuery<GuestBookEntry>("GuestBookEntry"); | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Microsoft.WindowsAzure; | |
using Microsoft.WindowsAzure.Storage; | |
using Microsoft.WindowsAzure.Storage.Table; | |
using System; | |
using System.Collections.Generic; | |
namespace GuestBook_Data | |
{ | |
public class GuestBookDataSource | |
{ | |
private static CloudStorageAccount storageAccount; | |
private GuestBookDataContext context; | |
/// <summary> | |
/// The static constructor initializes the storage account by reading its settings from the configuration | |
/// and then uses the CreateTablesFromModel method in the CloudTableClient class to create the tables | |
/// used by the application from the model defined by the GuestBookDataContext class. | |
/// By using the static constructor, you ensure that this initialization task is executed only once. | |
/// </summary> | |
static GuestBookDataSource() | |
{ | |
storageAccount = CloudStorageAccount.Parse(CloudConfigurationManager.GetSetting("DataConnectionString")); | |
CloudTableClient cloudTableClient = storageAccount.CreateCloudTableClient(); | |
CloudTable table = cloudTableClient.GetTableReference("GuestBookEntry"); | |
table.CreateIfNotExists(); | |
} | |
/// <summary> | |
/// The default constructor initializes the GuestBookDataContext with the CloudTableClient instance | |
/// returned by the storageAccount that was itself initalized in the class' static constructor. | |
/// </summary> | |
public GuestBookDataSource() | |
{ | |
this.context = new GuestBookDataContext(storageAccount.CreateCloudTableClient()); | |
} | |
/// <summary> | |
/// The GetGuestBookEntries method retrieves today's guest book entries by creating a TableQuery operation that filters the retrieved information using the current date as the partition key value. The web role uses this method to bind to a data grid and display the guest book. | |
/// </summary> | |
/// <returns>The collection of GuestBookEntries from the partition for the current date.</returns> | |
public IEnumerable<GuestBookEntry> GetGuestBookEntries() | |
{ | |
//Shouldn't we replace the following two lines with: | |
//CloudTable table = context.ServiceClient.GetTableReference("GuestBookEntry"); | |
CloudTableClient tableClient = storageAccount.CreateCloudTableClient(); | |
CloudTable table = tableClient.GetTableReference("GuestBookEntry"); | |
TableQuery<GuestBookEntry> query = new TableQuery<GuestBookEntry>().Where(TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, DateTime.UtcNow.ToString("MMddyyyy"))); | |
return table.ExecuteQuery(query); | |
} | |
/// <summary> | |
/// This method creates a TableOperation to insert the new guest book to storage. | |
/// </summary> | |
/// <param name="newItem">The GuestBookEntry to add (insert)</param> | |
public void AddGuestBookEntry(GuestBookEntry newItem) | |
{ | |
TableOperation operation = TableOperation.Insert(newItem); | |
CloudTable table = context.ServiceClient.GetTableReference("GuestBookEntry"); | |
table.Execute(operation); | |
} | |
/// <summary> | |
/// Updates a current GuestBookEntry with the url to the thumbnail image for the entry. | |
/// This method will most likely be called by a worker role instance that is responsible | |
/// for creating thumbnail images for new GuestBookEntry instances. | |
/// </summary> | |
/// <param name="partitionKey">The Partition Key value of the entity to update</param> | |
/// <param name="rowKey">The Row Key value of the entity to update</param> | |
/// <param name="thumbUrl">The URL of the thumbnail image.</param> | |
public void UpdateImageThumbnail(string partitionKey, string rowKey, string thumbUrl) | |
{ | |
CloudTable table = context.ServiceClient.GetTableReference("GuestBookEntry"); | |
TableOperation retrieveOperation = TableOperation.Retrieve<GuestBookEntry>(partitionKey, rowKey); | |
TableResult retrievedResult = table.Execute(retrieveOperation); | |
GuestBookEntry updateEntity = (GuestBookEntry)retrievedResult.Result; | |
if (updateEntity != null) | |
{ | |
updateEntity.ThumbnailUrl = thumbUrl; | |
TableOperation replaceOperation = TableOperation.Replace(updateEntity); | |
table.Execute(replaceOperation); | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Microsoft.WindowsAzure.Storage.Table; | |
using System; | |
namespace GuestBook_Data | |
{ | |
public class GuestBookEntry : TableEntity | |
{ | |
public GuestBookEntry() | |
{ | |
//To partition the data, the GuestBook application uses the date of the entry as the PartitionKey, | |
//which means that there will be a separate partition for each day of guest book entries. | |
//In general, you choose the value of the partition key to ensure load balancing of the | |
//data across storage nodes. | |
PartitionKey = DateTime.UtcNow.ToString("MMddyyyy"); | |
//The RowKey is a reverse DateTime field with a GUID appended for uniqueness. | |
//Tables within partitions are sorted in RowKey order, so this will sort the tables into the correct | |
//order to be shown on the home page, with the newest entry shown at the top. | |
RowKey = string.Format("{0:10}_{1}", DateTime.MaxValue.Ticks - DateTime.Now.Ticks, Guid.NewGuid()); | |
} | |
public string Message { get; set; } | |
public string GuestName { get; set; } | |
public string PhotoUrl { get; set; } | |
public string ThumbnailUrl { get; set; } | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using GuestBook_Data; | |
using Microsoft.WindowsAzure; | |
using Microsoft.WindowsAzure.Storage; | |
using Microsoft.WindowsAzure.Storage.Blob; | |
using Microsoft.WindowsAzure.Storage.Queue; | |
using System; | |
using System.IO; | |
using System.Net; | |
using System.Web.UI; | |
namespace GuestBook_WebRole | |
{ | |
public partial class _Default : System.Web.UI.Page | |
{ | |
private static bool storageInitialized = false; | |
private static object gate = new object(); | |
private static CloudBlobClient blobStorage; | |
private static CloudQueueClient queueStorage; | |
protected void Page_Load(object sender, EventArgs e) | |
{ | |
if (!Page.IsPostBack) | |
{ | |
//Start Timer1 when the page is | |
//retrieved the first time. | |
//Timer1 will then refresh | |
//the data in DataList1 every | |
//15 seconds... | |
this.Timer1.Enabled = true; | |
} | |
} | |
protected void SignButton_Click(object sender, EventArgs e) | |
{ | |
if (this.FileUpload1.HasFile) | |
{ | |
this.InitializeStorage(); | |
// upload the image to blob storage | |
//Each blob will go in the "guestbookpics" "subfolder" | |
//Each blob will have a name that is comprised of a GUID and it's original extension. | |
string uniqueBlobName = string.Format("guestbookpics/image_{0}{1}", Guid.NewGuid().ToString(), Path.GetExtension(this.FileUpload1.FileName)); | |
CloudBlockBlob blob = blobStorage.GetContainerReference("guestbookpics").GetBlockBlobReference(uniqueBlobName); | |
blob.Properties.ContentType = this.FileUpload1.PostedFile.ContentType; | |
blob.UploadFromStream(this.FileUpload1.FileContent); | |
System.Diagnostics.Trace.TraceInformation("Uploaded image '{0}' to blob storage as '{1}'", this.FileUpload1.FileName, uniqueBlobName); | |
// create a new entry in table storage | |
//Create a new GuestBook entry (with the url to the Full photo blob uploaded above) | |
GuestBookEntry entry = new GuestBookEntry() { GuestName = this.NameTextBox.Text, Message = this.MessageTextBox.Text, PhotoUrl = blob.Uri.ToString(), ThumbnailUrl = blob.Uri.ToString() }; | |
//Save the new GuestBookEntry into table storage using the GuestBook_Data.GuestBookDataSource. | |
GuestBookDataSource ds = new GuestBookDataSource(); | |
ds.AddGuestBookEntry(entry); | |
System.Diagnostics.Trace.TraceInformation("Added entry {0}-{1} in table storage for guest '{2}'", entry.PartitionKey, entry.RowKey, entry.GuestName); | |
// queue a message to process the image | |
//This will write a CloudQueueMessage into the "guestthumbs" queue with all the details a worker role will need | |
//to create a thumbnail image for the new GuestBookEntry | |
var queue = queueStorage.GetQueueReference("guestthumbs"); | |
var message = new CloudQueueMessage(string.Format("{0},{1},{2}", uniqueBlobName, entry.PartitionKey, entry.RowKey)); | |
queue.AddMessage(message); | |
System.Diagnostics.Trace.TraceInformation("Queued message to process blob '{0}'", uniqueBlobName); | |
} | |
//Clear out the web form so a new entry can be made without having to delete the old values first | |
this.NameTextBox.Text = string.Empty; | |
this.MessageTextBox.Text = string.Empty; | |
//Rebind the DataList to the Data in TableStorage | |
//DataList1 is bound to ObjectDataSource1 which uses the | |
//GuestBook_Data.GuestBookDataSource.GetGuestBookEntries() method | |
//As it's SelectMethod. Calling DataBind on the DataList then will | |
//Execute the GuestBook_Data.GuestBookDataSource.GetGuestBookEntries() | |
//method and return the lastest entries (last row first) from the | |
//Table storage partion for the current date. | |
//Wow, a lot of stuff happens with this simple call huh! | |
this.DataList1.DataBind(); | |
} | |
protected void Timer1_Tick(object sender, EventArgs e) | |
{ | |
//Timer1 is declared in the Default.aspx markup. | |
//It has an interval of 15000 milliseconds, or 15 seconds | |
//So basically, we'll rebind the DataList1 data every | |
//15 seconds. | |
//The datalist is inside an AJAX UpdatePanel so | |
//this won't cause a full page load, just a partial load. | |
this.DataList1.DataBind(); | |
} | |
private void InitializeStorage() | |
{ | |
//If this method has already been called, | |
//don't do it again, just exit. | |
if (storageInitialized) | |
{ | |
return; | |
} | |
//Lock the static "gate" object so that | |
//we don't accidentally have multiple | |
//calls to this method running in parallel. | |
//This could occur if multiple browsers hit the page | |
//at the same time. | |
lock (gate) | |
{ | |
//Again double check that some other thread didn't | |
//just complete this. If it did, no need to do any more | |
//just return | |
if (storageInitialized) | |
{ | |
return; | |
} | |
try | |
{ | |
// read account configuration settings | |
var storageAccount = CloudStorageAccount.Parse(CloudConfigurationManager.GetSetting("DataConnectionString")); | |
// create blob container for images | |
blobStorage = storageAccount.CreateCloudBlobClient(); | |
CloudBlobContainer container = blobStorage.GetContainerReference("guestbookpics"); | |
container.CreateIfNotExists(); | |
// configure container for public access | |
var permissions = container.GetPermissions(); | |
permissions.PublicAccess = BlobContainerPublicAccessType.Container; | |
container.SetPermissions(permissions); | |
// create queue to communicate with worker role | |
queueStorage = storageAccount.CreateCloudQueueClient(); | |
CloudQueue queue = queueStorage.GetQueueReference("guestthumbs"); | |
queue.CreateIfNotExists(); | |
} | |
catch (WebException) | |
{ | |
throw new WebException("Storage services initialization failure. " | |
+ "Check your storage account configuration settings. If running locally, " | |
+ "ensure that the Development Storage service is running."); | |
} | |
//If we got here, everything worked. Flag the storageIntialized bool as | |
//true so this method will exit immediately next time it is called. | |
storageInitialized = true; | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using GuestBook_Data; | |
using Microsoft.WindowsAzure; | |
using Microsoft.WindowsAzure.ServiceRuntime; | |
using Microsoft.WindowsAzure.Storage; | |
using Microsoft.WindowsAzure.Storage.Blob; | |
using Microsoft.WindowsAzure.Storage.Queue; | |
using System.Diagnostics; | |
using System.Drawing; | |
using System.Drawing.Drawing2D; | |
using System.Drawing.Imaging; | |
using System.IO; | |
using System.Net; | |
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 = this.queue.GetMessage(); | |
if (msg != null) | |
{ | |
// parse message retrieved from queue | |
var messageParts = msg.AsString.Split(new char[] { ',' }); | |
var imageBlobName = messageParts[0]; | |
var partitionKey = messageParts[1]; | |
var rowkey = messageParts[2]; | |
Trace.TraceInformation("Processing image in blob '{0}'.", imageBlobName); | |
string thumbnailName = System.Text.RegularExpressions.Regex.Replace(imageBlobName, "([^\\.]+)(\\.[^\\.]+)?$", "$1-thumb$2"); | |
CloudBlockBlob inputBlob = this.container.GetBlockBlobReference(imageBlobName); | |
CloudBlockBlob outputBlob = this.container.GetBlockBlobReference(thumbnailName); | |
using (Stream input = inputBlob.OpenRead()) | |
using (Stream output = outputBlob.OpenWrite()) | |
{ | |
this.ProcessImage(input, output); | |
// commit the blob and set its properties | |
outputBlob.Properties.ContentType = "image/jpeg"; | |
string thumbnailBlobUri = outputBlob.Uri.ToString(); | |
// update the entry in table storage to point to the thumbnail | |
GuestBookDataSource ds = new GuestBookDataSource(); | |
ds.UpdateImageThumbnail(partitionKey, rowkey, thumbnailBlobUri); | |
// remove message from queue | |
this.queue.DeleteMessage(msg); | |
Trace.TraceInformation("Generated thumbnail in blob '{0}'.", thumbnailBlobUri); | |
} | |
} | |
else | |
{ | |
System.Threading.Thread.Sleep(1000); | |
} | |
} | |
catch (StorageException e) | |
{ | |
Trace.TraceError("Exception when processing queue item. Message: '{0}'", e.Message); | |
System.Threading.Thread.Sleep(5000); | |
} | |
} | |
} | |
public override bool OnStart() | |
{ | |
// Set the maximum number of concurrent connections | |
ServicePointManager.DefaultConnectionLimit = 12; | |
// read storage account configuration settings | |
var storageAccount = CloudStorageAccount.Parse(CloudConfigurationManager.GetSetting("DataConnectionString")); | |
// initialize blob storage | |
CloudBlobClient blobStorage = storageAccount.CreateCloudBlobClient(); | |
this.container = blobStorage.GetContainerReference("guestbookpics"); | |
// initialize queue storage | |
CloudQueueClient queueStorage = storageAccount.CreateCloudQueueClient(); | |
this.queue = queueStorage.GetQueueReference("guestthumbs"); | |
Trace.TraceInformation("Creating container and queue..."); | |
bool storageInitialized = false; | |
while (!storageInitialized) | |
{ | |
try | |
{ | |
// create the blob container and allow public access | |
this.container.CreateIfNotExists(); | |
var permissions = this.container.GetPermissions(); | |
permissions.PublicAccess = BlobContainerPublicAccessType.Container; | |
this.container.SetPermissions(permissions); | |
// create the message queue(s) | |
this.queue.CreateIfNotExists(); | |
storageInitialized = true; | |
} | |
catch (StorageException e) | |
{ | |
var requestInformation = e.RequestInformation; | |
var errorCode = requestInformation.ExtendedErrorInformation.ErrorCode;//errorCode = ContainerAlreadyExists | |
var statusCode = (System.Net.HttpStatusCode)requestInformation.HttpStatusCode;//requestInformation.HttpStatusCode = 409, statusCode = Conflict | |
if (statusCode == HttpStatusCode.NotFound) | |
{ | |
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(); | |
} | |
public void ProcessImage(Stream input, Stream output) | |
{ | |
int width; | |
int height; | |
var originalImage = new Bitmap(input); | |
if (originalImage.Width > originalImage.Height) | |
{ | |
width = 128; | |
height = 128 * originalImage.Height / originalImage.Width; | |
} | |
else | |
{ | |
height = 128; | |
width = 128 * originalImage.Width / originalImage.Height; | |
} | |
Bitmap thumbnailImage = null; | |
try | |
{ | |
thumbnailImage = new Bitmap(width, height); | |
using (Graphics graphics = Graphics.FromImage(thumbnailImage)) | |
{ | |
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; | |
graphics.SmoothingMode = SmoothingMode.AntiAlias; | |
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; | |
graphics.DrawImage(originalImage, 0, 0, width, height); | |
} | |
thumbnailImage.Save(output, ImageFormat.Jpeg); | |
} | |
finally | |
{ | |
if (thumbnailImage != null) | |
{ | |
thumbnailImage.Dispose(); | |
} | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment