预测:多云

云服务混合应用程序

Joseph Fultz

下载代码示例

Joseph Fultz
到目前为止,我已经花了些时间来介绍使用 Microsoft Windows Azure 或 SQL Azure 来增强解决方案体系结构的解决方案。本月,我将介绍一下如何将多项云服务组合成一个应用程序。我将以组合 Windows Azure、Windows Azure AppFabric 访问控制、Bing Maps 和 Facebook 为例来介绍如何组合云服务。

有些人在考虑联合身份或社交网络的实际价值时会有些犹豫,对于这些人,我想向他们介绍一下 Marcelus。他是我的一位好友,他拥有一家从事住宅及商业清洁业务的公司。与我父亲处理他的业务和私人事务的做法类似,对于您想要或需要的任何东西,Marcelus 基本上都有一个认识的人能够通过以物易物的方式做成或得到。有些人可能会将这种行为视为广受诟病的“熟人关系网”,但从 Marcelus 身上,我看到了一个将 Windows Azure AppFabric 访问控制服务(Access Control service,简称为 ACS)与强大的社交网络组合的活生生的例子。在现实生活中,我可以利用 Marcelus 以及像他一样的人来为我提供帮助。

然而,在虚拟世界中,当我使用大量云服务时,这些服务通常需要知道我的身份才会允许我访问它们的功能。因为我确实不能将 Marcelus 编到程序中来提供网页,所以,我打算使用图 1 中的云服务来提供一些功能。

图 1 云服务及其功能

服务 功能
Windows Azure 托管我的站点并提供页面
AppFabric 访问控制 管理并协商我的站点和 Facebook 之间的身份验证
Facebook 验证用户的身份并提供社交网络服务
Bing Maps 查看好友的家乡

方案是这样的:导航到我的站点的主页需要经过 Facebook 的身份验证,声明会被传回我的站点。然后我的站点会从 Facebook 中调取该用户的好友名单,随后提取出所选好友的信息。如果所选好友指定了家乡,则用户可以单击该家乡名称,然后 Bing Maps 便会显示该家乡。

配置服务之间的身份验证

《MSDN 杂志》2010 年 12 月刊中有一篇文章很好地概述了 ACS,这篇文章可以在 msdn.microsoft.com/magazine/gg490345 上找到。我将介绍将我的站点与 Facebook 联合所需要做的具体事项。为了顺利进行,我使用了 AppFabric Labs,它是 Windows Azure AppFabric 的开发人员预览版。此外,我还使用了 Windows Azure SDK 1.3,并且我已经安装了 Windows Identity Foundation SDK 4.0。首先,我转到 portal.appfabriclabs.com 进行了注册。当我访问 ACS 后,我便按照 ACS 示例和文档(实验室)CodePlex 页面 (bit.ly/fuxkbl) 上的说明的第一部分设置了服务命名空间。下一个目标是将 Facebook 设置为身份标识提供程序,但是,我必须先创建一个 Facebook 应用程序(请参见 bit.ly/e9yE3I 上的说明),从而产生一个如图 2 中所示的摘要,然后才能实现这个目标。

image: Facebook Application Configuration Summary

图 2 Facebook 应用程序配置摘要

此摘要页面很重要,因为在 ACS 中将 Facebook 配置为身份标识提供程序时我要使用其中的信息。具体来说,我将需要应用程序 ID 和应用程序密码,这些可以在图 3 中所示的 ACS 中的配置信息中看到。

image: ACS Facebook Identity Provider Configuration

图 3 ACS Facebook 身份标识提供程序配置

请注意,我在应用程序权限文本框中添加了 friends_hometown。我将需要好友家乡的地址才能绘制其地图,如果没有在这里指定地址,那么就无法默认返回这个地址。如果我希望通过 Graph API 调用来返回关于该用户的某些其他数据,就需要在 Facebook 开发人员站点 (bit.ly/c8UoAA) 上进行查询,并将相应的项目添加到应用程序权限列表中。

使用 ACS 时需要指明一点:您需要指定将使用各个身份标识提供程序的依赖方。如果我的站点位于 jofultz.cloudapp.net 上,则会在身份标识提供程序配置中将其指定为依赖方。对于我的本地主机,同样如此。因此,如果我不想将其放在云中进行测试,我将需要配置一个本地主机依赖方,然后选择它,如图 4 所示。

image: ACS Facebook Identity Provider Configuration: Relying Parties

图 4 ACS Facebook 身份标识提供程序配置:依赖方

图 3图 4 位于同一个用于配置身份标识提供程序的页面上。同样,如果我只为本地主机配置身份标识提供程序,然后尝试从我的网站进行身份验证,则是行不通的。我可以创建一个自定义登录页面。ACS 管理站点的“应用程序集成”下提供了执行此操作的说明和示例。在这个示例中,我直接使用了默认的 ACS 托管页面。

到目前为止,我已经对 ACS 和我的 Facebook 应用程序进行了配置,使其一经调用就开始发挥作用。下一步是为我的站点配置此身份标识提供程序,使其成为一种身份验证途径。完成这一步最简单的方法是安装 Windows Identity Foundation SDK 4.0(可以在 bit.ly/ew6K5z 中找到)。安装后,便会出现一个右键单击菜单选项,该选项用于添加 STS 引用,如图 5 所示。

image: Add STS Reference Menu Option

图 5 添加 STS 引用菜单选项

在我的示例中,我通过选择新的 Web 角色项目使用了在 Visual Studio 中创建的默认 ASP.NET 站点。创建站点后,我右键单击该站点,然后按照向导的指示一步一步操作。我将通过在向导中选择相应选项并提供 WS-Federation 元数据的路径来对该站点进行配置,使之使用现有的安全令牌服务 (STS)。因此,对于我的访问控制命名空间,路径是:

jofultz.accesscontrol.appfabriclabs.com/

    FederationMetadata/2007-06/

    FederationMetadata.xml

利用这些信息,向导将配置节 <microsoft.identityModel/> 添加到站点配置中。完成此操作后,将 <httpRuntime requestValidationMode=“2.0” /> 添加到 <system.web/> 元素的下面。假如我指定了本地主机作为依赖方,那么我应该能够运行应用程序,并且当应用程序启动后,我将能够看到一个 ACS 托管登录页面,该页面将显示 Facebook、Windows Live 或 Google(视配置而定)。microsoft.identityModel 元素取决于 Microsoft.Identity 程序集是否存在,所以您必须确保将站点中的 DLL 引用设置为“始终复制”。如果不这样设置,当站点被推送到 Windows Azure 后,它就没有 DLL,因而将无法运行。针对我之前关于需要对本地主机和 Windows Azure 托管站点进行配置的说法,当向导完成后,还需要进行一项配置。因此,如果在向导中进行配置时使用了本地主机路径,则需要将 Windows Azure 站点的路径添加到此处显示的 <audienceUris> 元素中:

<microsoft.identityModel>

  <service>

    <audienceUris>

      <add value="http://jofultz.cloudapp.
net/" />

      <add value="http://localhost:81/" />

    </audienceUris>

此外,在配置中,wsFederation 元素的 realm 属性将需要反映当前所需的运行时位置。因此,当部署到 Windows Azure 时,对于我来说,realm 属性如下所示:

<federatedAuthentication>

  <wsFederation passiveRedirectEnabled="true" issuer=

   "https://jofultz.accesscontrol.appfabriclabs.com/v2/wsfederation" 

   realm="http://jofultz.cloudapp.
net/" requireHttps="false" />

  <cookieHandler requireSsl="false" />

</federatedAuthentication>

但是,如果我想对此进行调试,使其在我的本地主机上在运行时可以正常运行(本地调试),则我将更改 realm 属性,使其表示站点的本地托管位置,如下所示:

<federatedAuthentication>

  <wsFederation passiveRedirectEnabled="true" 

   issuer="https://jofultz.accesscontrol.
appfabriclabs.com/v2/wsfederation" 

   realm="http://localhost:81/" 

   requireHttps="false" />

  <cookieHandler requireSsl="false" />

</federatedAuthentication>

正确配置一切后,我将能够运行站点,并且在尝试浏览至默认页面时,将被重定向到 ACS 托管登录页面,在该页面上我可以选择 Facebook 作为身份标识提供程序。当我单击 Facebook 后,我被转到 Facebook 登录页面接受身份验证(请参见图 6)。

image: Facebook Login

图 6 Facebook 登录

由于我之前没有使用过我的应用程序,因此,Facebook 向我显示了一个针对我的应用程序的“请求权限”对话框,如图 7 所示。

image: Application Permission Request

图 7 应用程序权限请求

我可不想被排除在使用这种神奇应用程序的核心人群之外,所以我迅速单击“允许”,之后 Facebook、ACS 以及我的应用程序通过浏览器重定向交换了信息,最终我被重定向到了我的应用程序。此时,我看到的仅仅是一个空白页面,但它知道我是谁,并且页面右上角显示“欢迎您,Joseph Fultz”消息。

Facebook Graph API

对于我的应用程序,我需要提取构成我的社交网络的好友名单,然后检索关于这些好友的信息。Facebook 提供的 Graph API 使开发人员能够完成上述操作。Graph API 内容非常详细,最重要的一点是,它是一种扁平化的、简单的实现,因而易于理解和使用。为了提出请求,我将需要访问令牌。幸运的是,它已在声明中返回,并且在 Windows Identity Foundation SDK 的帮助下,声明被放置到主体身份标识中。声明如下所示:

https://schemas.xmlsoap.org/ws/2005/05/  

    identity/claims/nameidentifier

  https://schemas.microsoft.com/ws/2008/06/

    identity/claims/expiration

  https://schemas.xmlsoap.org/ws/2005/05/

    identity/claims/emailaddress

  https://schemas.xmlsoap.org/ws/2005/05/

    identity/claims/name 

  http://www.facebook.com/claims/AccessToken

  https://schemas.microsoft.com/

    accesscontrolservice/2010/07/claims/

    identityprovider

我真正想从这些声明中获得的是完整名称的最后一部分(例如,nameidentifier 和 expiration 等)以及相关值。因此,我创建了 ParseClaims 方法来分解声明,并将声明及其值放入哈希表中以备后用,然后在页面加载事件中调用该方法:

protected void ParseClaims()

{

  string username = default(string);

  username = Page.User.Identity.Name;



  IClaimsPrincipal Principal = (IClaimsPrincipal) Thread.CurrentPrincipal;

  IClaimsIdentity Identity = (IClaimsIdentity) Principal.Identity;



  foreach (Claim claim in Identity.Claims)

  {

    string[] ParsedClaimType = claim.ClaimType.Split('/');

    string ClaimKey = ParsedClaimType[ParsedClaimType.Length - 1];



    _Claims.Add(ClaimKey, claim.Value);

  }             

}

我创建了 FBHelper 类,我将在其中创建方法来访问我所需要的 Facebook 信息。首先,我创建一个方法来帮助提出所需的所有请求。我将使用 WebClient 对象提出各个请求,并使用 JavaScript Serializer 解析响应:

public static Hashtable MakeFBRequest(string RequestUrl)

{

  Hashtable ResponseValues = default(Hashtable);



  WebClient WC = new WebClient();

  Uri uri = new Uri(String.Format(RequestUrl, fbAccessToken));

           

  string WCResponse = WC.DownloadString(uri);

  JavaScriptSerializer JSS = new JavaScriptSerializer();

  ResponseValues = JSS.Deserialize<Hashtable>(WCResponse);



  return ResponseValues;

}

在此代码段中可以看到,每个请求将需要拥有在声明中返回的访问令牌。 当我的可重用请求方法准备就绪后,我创建一个方法来提取我的好友名单,并将他们解析到哈希表中,该哈希表包含他们各自的 Facebook ID 和名称:

public static Hashtable GetFBFriends(string AccessToken)

{

  Hashtable FinalListOfFriends = new Hashtable();

  Hashtable FriendsResponse = MakeFBRequest(_fbFriendsListQuery, AccessToken);

  object[] friends = (object[])FriendsResponse["data"];



  for (int idx = 0; idx < friends.Length;idx++ )

  {

    Dictionary<string, object> FriendEntry = 

      (Dictionary<string, object>)friends[idx];

    FinalListOfFriends.Add(FriendEntry["id"], FriendEntry["name"]);

  }

  return FinalListOfFriends;

}

好友列表响应的反序列化将产生 Hashtable->Hashtable->Dictionary 这种嵌套结构。 因此,我必须做些工作来提取这些信息,然后将其放到我自己的哈希表中。 放好后,我切换到我的 default.aspx 页面,添加一个 ListBox,编写一些代码来获取好友名单并将结果绑定到我的新 ListBox:

protected void GetFriends()

  {

    _Friends = FBHelper.GetFBFriends((string)_

      Claims["AccessToken"]);

    this.ListBox1.DataSource = _Friends;

    ListBox1.DataTextField = "value";

    ListBox1.DataValueField = "key";

    ListBox1.DataBind();

  }

如果我此时运行应用程序,则当我通过身份验证后,我便会立即看到一个包含我的所有 Facebook 好友的列表。 不过等等,还需要其他信息! 我需要获得任何所选好友的可用信息,以便我可以使用这些信息来在地图上查看他们的家乡。 再回到我的 FBHelper 类,我添加一个简单的方法,该方法将返回访问令牌和所选好友的 ID:

public static Hashtable GetFBFriendInfo(string AccessToken, string ID)

{

  Hashtable FriendInfo = 

    MakeFBRequest(String.Format(_fbFriendInfoQuery, ID) + 

    "?access_token={0}", AccessToken);

  return FriendInfo;

}

请注意,在我创建的两种 Facebook 帮助程序方法中,我引用了包含所需的 Graph API 查询的常量字符串:

public const string _fbFriendsListQuery =   

  "https://graph.facebook.com/me/friends?access_token={0}"; 

public const string _fbFriendInfoQuery = "https://graph.facebook.com/{0}/";

当我的最后一个 Facebook 方法准备就绪后,我将向页面添加一个 GridView,并对其进行设置使之与一个哈希表绑定,然后在 ListBox 的 SelectedIndexChanged 方法的隐藏代码中,我会将其与 GetFBFriendInfo 方法返回的 Hashtable 绑定,如图 8 所示。

图 8 添加 GridView

protected void ListBox1_SelectedIndexChanged(object sender, EventArgs e)

{

  Debug.WriteLine(ListBox1.SelectedValue.ToString());

  Hashtable FriendInfo = 

    FBHelper.GetFBFriendInfo((string)_Claims["AccessToken"],  

    ListBox1.SelectedValue.ToString());

  GridView1.DataSource = FriendInfo;

  GridView1.DataBind();

  try

  {

    Dictionary<string, object> HometownDict = 

      (Dictionary<string, object>) FriendInfo["hometown"];

      _Hometown = HometownDict["name"].ToString();

  }

  catch (Exception ex)

  {

    _Hometown = "";//Not Specified";

  }

}

现在,我已经从 Facebook 获得了我的好友名单及好友信息,下面我将介绍如何在地图上显示他们的家乡。

没有什么地方比得上家

对于指定了其家乡的好友,我希望能够在单击其家乡名称后让地图导航到那里。 第一步是将地图添加到页面中。 这个任务非常简单,为了完成这个任务,Bing 提供一个非常好的交互式 SDK,该 SDK 将演示功能,然后允许您查看并复制源代码。 该 SDK 可在microsoft.com/maps/isdk/ajax/ 上找到。 在 default.aspx 页面中,我添加一个 div 来存放地图,如下所示:

<div  id="myMap" style="position:relative; width:400px; height:400px;" ></div>

但是,为了将地图放入该 div,我在 SiteMaster 页面中添加了脚本引用和一些脚本:

<script type="text/javascript" src="http://ecn.dev.virtualearth.
net/

  mapcontrol/mapcontrol.ashx?v=6.2"></script>      

  <script type="text/javascript">

    var map = null;

    function GetMap() {

      map = new VEMap('myMap');

      map.LoadMap();

    }

  </script>

添加完后,当我打开该页面时,我将在默认位置看到一幅地图,但我希望当我选择地图时它就会移动到我好友的家乡。 在之前讨论的 SelectedIndexChanged 事件的过程中,我还将页面中的标签与名称绑定,并添加了一个客户端单击事件,以便使地图根据标签的值查找位置:

onclick="map.Find(null, hometown.innerText, 

    null, null, null, null, true, null, true); 

    map.SetZoomLevel(6);"

在 map.Find 调用中,如果需要,大多数后缀参数都可以省略。可以在以下网址找到关于 Find 方法的参考资料:msdn.microsoft.com/library/bb429645。以上就是在这个简单的示例中显示地图并与地图进行交互所需的所有操作。现在,我已准备好运行该方法,让它大显神通。

如之前我所说的,如果我已适当配置了 identityModel,使之可以在我的本地主机上正常运行,那么我可以按 F5,然后在调试模式下本地运行它。因此,我按 F5,看到一个浏览器窗口弹出对话框,该对话框中显示了我的登录选项。我选择 Facebook,然后被转到图 6 中所示的登录页面。登录后,我又被定向回我的 default.aspx 页面,该页面现在显示我的好友以及一幅默认地图,如图 9 所示。

image: Demo Homepage

图 9 演示主页

接下来,我将浏览我的好友并单击一位好友。我将获得一些信息,具体可以获得哪些信息取决于好友的安全设置以及我设置身份标识提供程序时所请求的应用程序权限(如图 2 所示)。接下来,我将单击位于地图上方的家乡名称,然后地图便会移动,在中心位置显示该家乡,如图 10 所示。

image: Hometown in Bing Maps

图 10 Bing Maps 中的家乡

结论

我希望我已经清楚地介绍了如何将 Windows Azure 平台、Bing Maps 和 Facebook 的多个方面组合到一起,而且证明了这非常简单。使用 ACS,我能够将各种云技术组合到一起来创建示例应用程序。只要稍微加工,就可以同样轻松地结合您自己的身份标识服务来根据需要提供。这种身份标识联合的优点在于,使用 Windows Azure 使您能够基于其他供应商和其他平台开发服务和整合其他供应商和其他平台的服务,而在没有这种身份标识联合时,您只能选择一个提供商及其服务,或者必须找出一种低保真集成方法。Microsoft Windows Azure 平台具有强大的优势,而其中部分优势在于它可以很轻松地与其他云服务组合到一起。

Joseph Fultz 是达拉斯 Microsoft 技术中心的架构师,协助企业客户和 ISV 设计和制作软件解决方案以满足企业和市场需求。他在 Tech·Ed 及类似的内部培训活动中做过讲座。

衷心感谢以下技术专家对本文的审阅:Steve Linehan