Building Your First Windows Azure Application
Training Kits里的第一篇实例教程,简单记录下过程,并根据理解做些旁注,方便日后查看。
1. 新建一个Cloud Service工程, 增加一个ASP.NET Web Role
2. 增加一个Class Library, 添加所需References,定义Entity Schema -- GuestBookEntry.cs
- using System;
- using Microsoft.WindowsAzure.StorageClient;
- namespace GuestBook_Data
- {
- public class GuestBookEntry : TableServiceEntity
- {
- public GuestBookEntry()
- {
- PartitionKey = DateTime.UtcNow.ToString("MMddyyyy");
- // Row key allows sorting, so we make sure the rows come back in time order.
- 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; }
- }
- }
注:
定义的 Entity 存于 Windows Azure table 中,故继承自 TableServiceEntity。
TableServiceEntity 类中定义了PartititionKey, RowKey 和 TimeStamp,该3个字段是存在Windows Azure table 中必要的字段,其中 PartitionKey 和 RowKey 都是string类型,合起来可以确定一个 DataServiceKey, 进而可以唯一确定 Table 中的一个 Entity。
该例在 Entity 构造函数中对 PartititionKey, RowKey 赋值,以便在新建立一个 Entity 时无须显式的为这两个字段赋值。其中用日期作为PartititionKey的值,这样对于每一天建立的Entities都会有一个Partition, 一般来讲, PartititionKey的选值需要考虑到节点间的负载平衡问题。 RowKey用反序的时间加一个guid来确定,加guid为了保证唯一性,时间反转则是考虑到排序问题,总是让最新的entities排在前面。
3. 定义DataContext -- GuestBookDataContext.cs
- using System.Linq;
- using Microsoft.WindowsAzure;
- using Microsoft.WindowsAzure.StorageClient;
- namespace GuestBook_Data
- {
- public class GuestBookDataContext : TableServiceContext
- {
- public GuestBookDataContext(string baseAddress, StorageCredentials credentials)
- : base(baseAddress, credentials)
- {
- }
- public IQueryable<GuestBookEntry> GuestBookEntry
- {
- get
- {
- return this.CreateQuery<GuestBookEntry>("GuestBookEntry");
- }
- }
- }
- }
注:
同样对应于table的DataContext需要继承于 TableServiceContext 类。 其中构造函数中StorageCredentials参数存储通过storage account访问Windows Azure storage service需要的验证信息等。
4. 定义GuestBookEntryDataSource.cs
- using System;
- using System.Linq;
- using System.Collections.Generic;
- using Microsoft.WindowsAzure;
- using Microsoft.WindowsAzure.StorageClient;
- namespace GuestBook_Data
- {
- public class GuestBookEntryDataSource
- {
- private static CloudStorageAccount storageAccount;
- private GuestBookDataContext context;
- public GuestBookEntryDataSource()
- {
- this.context = new GuestBookDataContext(storageAccount.TableEndpoint.AbsoluteUri, storageAccount.Credentials);
- this.context.RetryPolicy = RetryPolicies.Retry(3, TimeSpan.FromSeconds(1));
- }
- static GuestBookEntryDataSource()
- {
- storageAccount = CloudStorageAccount.FromConfigurationSetting("DataConnectionString");
- CloudTableClient.CreateTablesFromModel(
- typeof(GuestBookDataContext), storageAccount.TableEndpoint.AbsoluteUri, storageAccount.Credentials);
- }
- public IEnumerable<GuestBookEntry> Select()
- {
- var results = from g in this.context.GuestBookEntry
- where g.PartitionKey == DateTime.UtcNow.ToString("MMddyyyy")
- select g;
- return results;
- }
- public void AddGuestBookEntry(GuestBookEntry newItem)
- {
- this.context.AddObject("GuestBookEntry", newItem);
- this.context.SaveChanges();
- }
- public void UpdateImageThumbnail(string partitionKey, string rowKey, string thumbUrl)
- {
- var results = from g in this.context.GuestBookEntry
- where g.PartitionKey == partitionKey && g.RowKey == rowKey
- select g;
- var entry = results.FirstOrDefault<GuestBookEntry>();
- entry.ThumbnailUrl = thumbUrl;
- this.context.UpdateObject(entry);
- this.context.SaveChanges();
- }
- }
- }
注:
静态构造函数中 storageAccount = CloudStorageAccount.FromConfigurationSetting("DataConnectionString"); 配置信息存储在GuestBook_WebRole中,见下图:
5. 利用 Web Role 来完成UI部分. UI 部分的 code 如下
Default.aspx.cs
- using System;
- using System.Web.UI;
- using System.Net;
- using GuestBook_Data;
- using Microsoft.WindowsAzure;
- using Microsoft.WindowsAzure.StorageClient;
- 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)
- {
- Timer1.Enabled = true;
- }
- }
- protected void SignButton_Click(object sender, EventArgs e)
- {
- if (FileUpload1.HasFile)
- {
- InitializeStorage();
- // upload the image to blob storage
- string uniqueBlobName = string.Format("image_{0}.jpg", Guid.NewGuid().ToString());
- CloudBlobContainer container = blobStorage.GetContainerReference("guestbookpics");
- CloudBlockBlob blob = container.GetBlockBlobReference(uniqueBlobName);
- blob.Properties.ContentType = FileUpload1.PostedFile.ContentType;
- blob.UploadFromStream(FileUpload1.FileContent);
- System.Diagnostics.Trace.TraceInformation(
- "Uploaded image '{0}' to blob storage as '{1}'", FileUpload1.FileName, uniqueBlobName);
- // create a new entry in table storage
- GuestBookEntry entry = new GuestBookEntry() {
- GuestName = NameTextBox.Text,
- Message = MessageTextBox.Text,
- PhotoUrl = blob.Uri.ToString(),
- ThumbnailUrl = blob.Uri.ToString() };
- GuestBookEntryDataSource ds = new GuestBookEntryDataSource();
- 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
- 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);
- }
- NameTextBox.Text = "";
- MessageTextBox.Text = "";
- DataList1.DataBind();
- }
- protected void Timer1_Tick(object sender, EventArgs e)
- {
- DataList1.DataBind();
- }
- private void InitializeStorage()
- {
- if (storageInitialized)
- {
- return;
- }
- lock (gate)
- {
- if (storageInitialized)
- {
- return;
- }
- try
- {
- // read account configuration settings
- var storageAccount = CloudStorageAccount.FromConfigurationSetting("DataConnectionString");
- // create blob container for images
- blobStorage = storageAccount.CreateCloudBlobClient();
- CloudBlobContainer container = blobStorage.GetContainerReference("guestbookpics");
- container.CreateIfNotExist();
- // 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.CreateIfNotExist();
- }
- 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.");
- }
- storageInitialized = true;
- }
- }
- }
- }
注:
InitializeStorage方法建立了一个blob container用来存储图片,并且设置相应权限。
点击Sign button时,将Image信息存储于blob storage, 将GuestBook Entity信息存储于table storage。
6. 增加一个storage account settings, 双击GuestBook_WebRole,Add Settings
7. 修改WebRole.cs的 OnStart() 方法
- using System.Linq;
- using Microsoft.WindowsAzure.Diagnostics;
- using Microsoft.WindowsAzure.ServiceRuntime;
- using Microsoft.WindowsAzure;
- namespace GuestBook_WebRole
- {
- public class WebRole : RoleEntryPoint
- {
- public override bool OnStart()
- {
- DiagnosticMonitor.Start("DiagnosticsConnectionString");
- // For information on handling configuration changes
- // see the MSDN topic at http://go.microsoft.com/fwlink/?LinkId=166357.
- RoleEnvironment.Changing += RoleEnvironmentChanging;
- CloudStorageAccount.SetConfigurationSettingPublisher((configName, configSetter) =>
- {
- configSetter(RoleEnvironment.GetConfigurationSettingValue(configName));
- });
- return base.OnStart();
- }
- private void RoleEnvironmentChanging(object sender, RoleEnvironmentChangingEventArgs e)
- {
- // If a configuration setting is changing
- if (e.Changes.Any(change => change is RoleEnvironmentConfigurationSettingChange))
- {
- // Set e.Cancel to true to restart this role instance
- e.Cancel = true;
- }
- }
- }
- }
8. 新建一个Worker Role
- using System;
- using System.Diagnostics;
- using System.Linq;
- using System.Net;
- 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()
- {
- // Set the maximum number of concurrent connections
- ServicePointManager.DefaultConnectionLimit = 12;
- DiagnosticMonitor.Start("DiagnosticsConnectionString");
- // For information on handling configuration changes
- // see the MSDN topic at http://go.microsoft.com/fwlink/?LinkId=166357.
- RoleEnvironment.Changing += RoleEnvironmentChanging;
- // read storage account configuration settings
- CloudStorageAccount.SetConfigurationSettingPublisher((configName, configSetter) =>
- {
- configSetter(RoleEnvironment.GetConfigurationSettingValue(configName));
- });
- 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();
- 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 a configuration setting is changing
- if (e.Changes.Any(change => change is RoleEnvironmentConfigurationSettingChange))
- {
- // Set e.Cancel to true to restart this role instance
- 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;
- }
- }
- }
- }
注:
至此,该程序应该可以运行了,完成的功能就是用户通过页面上传GuestBook信息,其中Image存储于blob storage中,GuestBook Entity存储于table storage中, web role和worker role之间通过queue storage进行消息的传递,用户发的消息以一定格式存储于queue storage中, 在worker role中以一定频率去读取queue storage中的信息并进行相应处理。