本文承接《在Azure上构建一个基于Facebook的营销式应用程序(上) 》 和《在Azure上构建一个基于Facebook的营销式应用程序(中)》
选择一个商店
如果我们在联系信息窗体中通过了验证,我们接下来就可以让用户选择一个他自己喜欢的商店了。这个应用程序会给用户展示一个包含三个商店的列表(这三个商店都在这个用户的邮政编码所在地方圆50英里之内,是按照从最近到最远的顺序来排序的)。要想让这个窗体快速地显示出来,尽量减少Web角色上的负载,提前计算距离,然后把据距离信息和商店数据整理到Azure Table Storage中是我们首先应该做的事情。要时刻谨记,我们的目标是可扩展性,而不是想当然的可升级性或未来的可预见性——营销式应用程序存在的时间相对较短。
我使用的是公用的来自于1999年人口普查的美国邮政编码列表(具体可以参考:http://www.census.gov/geo/www/tiger/zip1999.html)。这个列表包含每个邮政编码的经度和纬度坐标。然后,因为我没有自己的商店,所以我从这个列表随机地选择了一些邮政编码,然后用这些邮政编码生成了一个包含1000个样例商店的列表。然后,把这个商店列表通过CSV文件(这种文件格式对Azure Azure Storage Explorer比较友好)的形式,上传到我的***个Azure表中,“Store”实体如下所示:
Listing 7
public class Store: TableServiceEntity
{
// partition key is store number
// row key is empty
public Store()
{
this.RowKey = "";
}
public string Name { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Zip { get; set; }
}
接下来,我构建了第二张表,叫作“ZipStore”,对于这个国家中的每个邮政编码来说,它包含0到3行。每行是一个“邮政编码-商店”对,对于每个邮政编码来说,只考虑在这个邮政编码所在地方圆50英里内的三个最近的商店。要构建这样一张表,我必须要遍历邮政编码列表,然后使用一个基于经度和纬度的公式(具体可以参考:http://zips.sourceforge.net/),计算出各个邮政编码所在地到每一个商店的距离,把范围内最近的三个商店保存下来。如果方圆50英里之内根本就没有商店,那么我会放弃这个邮政编码。30分钟的数值分析之后,我得到了一个包含“邮政编码-商店”信息的CSV文件,然后我把这个文件上传到了Azure Table Storage中。“ZipStore”实体是十分简单的。
Listing 8
class ZipStore: TableServiceEntity
{
// partition key is zip code
// row key is distance rank (0 is nearest)
public string StoreNumber { get; set; }
}
“partition key”存储了邮政编码。“row keys”是每个商店的靠近等级。每个“partition”代表一个邮政编码,“partition”中的每一行代表一个商店和这个邮政编码所在地的靠近程度。
现在,我们可以看一看“Store”容器了:
Listing 9
public class StoreRepository : IStoreRepository
{
readonly AzureTable<ZipStore> _zipStore;
readonly AzureTable<Store> _store;
public StoreRepository()
{
var factory = new AzureStorageFactory ();
_zipStore = (AzureTable<ZipStore>)factory.GetTable><ZipStore>();
_store = (AzureTable<Store>)factory.GetTable><Store>();
}
public IEnumerable<Store> GetNearbyStores(string ZipCode)
{
var stores = new List<Store>();
Store store;
var query = _zipStore.Query.Where(zs => zs.PartitionKey == ZipCode);
foreach (ZipStore zs in query)
{
store = _store.Get(s => s.PartitionKey == zs.StoreNumber && s.RowKey == "");
if (store != null)
stores.Add(store);
}
Return stores;
}
}
这里值得一提的是“GetNearbyStores()”方法,控制器使用这个方法来加载可用的商店列表。***个查询只指定了“partition key”(邮政编码),然后返回三个或少于三个商店。因为“row key”是距离等级,所以这个查询会按照距离的顺序来返回各个商店。而对于每个商店来说,我们使用商店号作为“partition key”,从“store”表中获取商店名,城市,国家和邮政编码。
虽然对于这个视图来说,必须要进行四次查询,但是它们都是通过“partition key”和“row key”来进行的,速度非常快。而且,你还可以购买存储空间,通过把全部的“store”实体都直接存储到“ZipStore”表中,尽量减少对单一查询的检查的方式,来获得更好的性能。
#p#
存储和队列
这个叫作“SelectStore”的处理程序用用户选择的商店和注册标记来更新“contact”,然后保存这个“contact”。
Listing 10
[HttpPost]
[CanvasAuthorize]
public ActionResult SelectStore(SelectStoreViewModel model)
{
FacebookApp app = new FacebookApp ();
Contact contact = contactRepository.GetFromFacebookId(app.UserId);
if (ModelState.IsValid)
{
contact.StoreNumber = model.StoreNumber;
contact.Registered = true;
contactRepository.Save(contact);
QueueContact(contact);
return this.CanvasRedirectToAction("SignupComplete");
}
Return View(model);
此外,为了方便Worker角色进行处理,这个控制器会把“contact”排成队列。这是“QueueContact”的完整实现:
Listing 11
private void QueueContact(Contact contact)
{
// queue the contact for the worker role.
ContactQueueMessage message = new ContactQueueMessage();
message.FacebookId = contact.PartitionKey;
contactQueue.AddMessage(message);
}
Windows Azure Toolkit可以让你更方面地使用Azure队列。对于在多个角色(一个可扩展的应用程序是由这些角色组成的)之间进行通信来说,队列是必需的。“ContactQueue”继承于一个Windows Azure Toolkit类型,“ContactQueueMessage”也是如此,它们可以让队列方法的实现变得更加简单。但是,当这个工具包从Worker角色的队列中获取消息的时候,这个工具包会才会真正地大放异彩。接下来我们将会看到这一点。
在Worker角色中Dequeuing
Worker角色做了很多的工作,但是这一切都是在响应队列消息的过程中完成的。使用Windows Azure Toolkit,我们可以在这个角色的“OnStart()”方法中创建一个队列处理程序。我们只需把这段代码添加到默认的实现中就可以了:
Listing 12
AzureStorageFactory factory = new AzureStorageFactory();
IAzureQueue<ContactQueueMessage> queue =
factory.GetQueue<ContactQueueMessage>(AzureConstants.ContactQueueName);
QueueHandler.For<ContactQueueMessage>(queue)
.Every(TimeSpan.FromSeconds(5))
.Do(new EmailAndStoreContact());
在“OnStart”中,我们创建了一个“AzureStorageFactory”对象,然后使用它来查找“contact”队列。然后,我们创建了一个处理程序,在这个例子中,它每5秒检查一次队列,如果有一个消息可用的话,它就用这个消息调用我们的消息处理器(“EmailAndStoreContact”)。因为处理队列消息是Worker角色的工作,所以,我们需要做的所有事情只是把它写入到“EmailAndStoreContact”中而已。对于这个类来说,Main方法被叫作Run,它的实现如下所示:
Listing 13
public void Run(ContactQueueMessage message)
{
Contact contact = contactRepository.Get(message.FacebookId);
if (contact != null)
{
// TODO: contact another service to send off the email with
// the premium coupon here (outside the scope of this sample)!
// store the contact to sql Azure.
SQLContact sqlContact = new SQLContact();
MapContactToSqlContact(contact, sqlContact);
sqlContactRepository.Add(sqlContact);
sqlContactRepository.Save();
}
}
这个消息处理器从队列消息中获取“contact”的Facebook ID,然后使用它从这个容器中获取“contact”。因为在这个应用程序中我们并没有真正地email什么内容,所以剩下的所有事情只是把“contact”发送到SQL Azure中而已。这里,我们使用了一个“SQLContact”对象,它是“LINQ to SQL”中的一个数据类。
究竟为什么从Azure Table Storage迁移到SQL Azure呢?因为虽然Azure表很快,但是它们并不适合进行某些特定的查询。把联系数据迁移到SQL Azure中可以生成表,其他应用程序就可以查询这个“contact”表来判断我们的活动进行的怎么样了。
#p#
起点
你可以把这个应用程序的源码当成你自己在Facebook上的营销活动的起点。就像上面讨论的那样,(除了50000个奖品)你还需要添加一些其他的东西。首先,在Worker角色中,要有email的处理程序。其次,要有一些辅助性的Facebook要素,例如:支持这个应用程序在公司页面上作为一个标签来运行,为“like”你的页面的客户提供额外的奖励,通过邀请或Wall posts,进一步促进“病毒”的传播。在大多数情况下,支持这些特性意味着直接和Facebook JavaScript API打交道。
即使你从来没有发起过你自己的活动,对于那些使用C#的Azure开发者来说,也会有很多收获的。首先,使用Windows Azure toolkit可以简化访问Azure存储表和队列的过程。其次,如果你正在编写一个Facebook应用程序,那么你可以使用Facebook C# SDK。再次,使用Worker角色可以把后台任务分离出来。***(但并非到此为止),可以让你知道如何组织你的存储结构才能给你的应用程序提供***的可扩展性。
你需要的一些工具:
这些工具的***版本可以通过下面这些地址来下载:
Windows Azure SDK的下载地址:http://msdn.microsoft.com/en-us/windowsazure/cc974146.aspx
Windows Azure Toolkit的下载地址:http://azuretoolkit.codeplex.com/
Facebook C# SDK的下载地址:http://facebooksdk.codeplex.com/
样例代码:
这些样例代码是本文写作的时候使用的一些代码。
你可以通过如下地址来下载本文的样例代码:
http://facebooksdk.codeplex.com/releases/view/60694
本文承接《在Azure上构建一个基于Facebook的营销式应用程序(上) 》
和《在Azure上构建一个基于Facebook的营销式应用程序(中)》
原文名:Building a Facebook Marketing App on Azure 作者:Steve Apiki
【本文乃51CTO精选译文,转载请标明出处!】
【编辑推荐】