65.9K
CodeProject 正在变化。 阅读更多。
Home

Facebook桌面(客户端)应用程序、C#、WPF/XAML和JSON入门

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.92/5 (28投票s)

2009年1月21日

CPOL

15分钟阅读

viewsIcon

327923

downloadIcon

11768

关于开始使用 Facebook API 和 WPF 的探讨

Facebook_API/WPF_facebook1.gif

目录

  1. 引言
  2. 背景
  3. 必备组件
  4. 运行演示
  5. Using the Code
  6. 关注点
  7. 历史

引言

我想了解Windows Presentation Foundation (WPF),并且我对Facebook API也很感兴趣,它可以通过RESTful服务访问。我曾计划使用统一的Windows Communication Foundation (WCF)方法来访问API,但这增加了复杂性,文章下面会解释。相反,我从.NET 3.51中提取了最有意义、最简单的解决方案,使用了WebClient对象、数据契约以及.NET 3.51自带的JSON序列化器。最终我得到了一个方便的Facebook应用程序,其他人也可能觉得有用,所以我写了这篇文章。

背景

我研究了几种将Facebook连接到C#的选项,社区中有一些很棒的努力。但我没有找到一个简单、可用的入门指南,来帮助初学者构建C# Facebook应用程序,并理解API的工作原理。这个应用程序提供了这样的功能,没有复杂的抽象层、啰嗦的异常处理或成百上千的类;希望足以引导你走向正确的方向。

先决条件

  1. 运行此示例前需要.NET Framework 3.51。如果你没有,请从Microsoft下载并安装。我使用了这里的安装程序,可惜是一个232MB的巨头!
  2. 你需要一个Facebook应用程序来操作 -
    1. 登录:Facebook,并在提示时允许访问Facebook开发者应用程序。
    2. 点击右上角的“设置新应用程序”按钮
    3. 给应用程序起个名字,同意条款,然后点击“保存更改”按钮
    4. 你需要设置一个回调URL,我设置的是Google
    5. 请保管好API密钥密钥,运行应用程序时需要用到它们

运行演示

从归档文件中提取演示,然后双击FacebookApplication.exe。下面的图表和相应的说明将引导你了解用户界面。

Talking to facebook!

Yellow1 将你的Facebook应用程序的API密钥(见上面的先决条件)粘贴到[Your Key]处
Yellow2 将你的Facebook应用程序的密钥(见上面的先决条件)粘贴到[Your Secret]处
Yellow3 输入要从Facebook检索的朋友数量(我为了方便测试,将其限制在工作集内)
Yellow4 可选地保存这些设置
Yellow5 点击“登录”并登录Facebook。首次运行时你需要允许Facebook应用程序的访问,并会提示你这样做。按照指示关闭登录窗口。
Yellow5 点击“获取好友”以调用Facebook API,并观察应用程序填充用户界面。

使用代码

打开解决方案文件(源代码)后,首先要做的是生成解决方案,以便项目引用能正确解析,并且XAML能在设计器中查看。代码有详细的注释,所以我不打算赘述,但我会尽力解释其总体架构和项目结构,以帮助你更好地理解。我还考虑过一个更面向对象的做法,利用基类来实现更优雅、更易扩展的设计,但为了确保解决方案简单易懂,我放弃了。

下面的图表和相应说明将引导你了解解决方案文件

Solution Explorer

Orange1 提供的解决方案WPF_Facebook包含两个项目
  • Facebook:一个类库,包含所有与Facebook API的交互、辅助方法、窗口和代码
  • FacebookApplication:一个简单的Windows Presentation Foundation (WPF)项目,演示如何使用Facebook项目。
Blue2 项目:Facebook
Blue2YellowA

Code文件夹包含了利用Facebook API的大部分代码。

  • Facebook.API.cs:这是一个面向公众的类,负责协调项目中其他部分调用Facebook API并返回所需信息。构造函数初始化一个新的会话(sessionInfo = new Facebook.auth.getSession();),该会话在API集成期间一直存在。你可能注意到了奇怪的命名约定Facebook.auth.getSession,这在你查看Facebook API Auth.getSession时会变得有意义,我的代码试图紧密模仿相关的Facebook API。

    此文件还包括两个Facebook API调用:public Facebook.users.getInfo GetUsersInfo(double userId)public ArrayList GetListOfFriends()。我将详细介绍第一个调用,以解释此类库中的API调用是如何协调的。

     public Facebook.users.getInfo GetUsersInfo(double userId)
    {
        // Facebook parameter list (key value pair) that (needs to be
        // sorted in order to get the MD5 hash correct - see Facebook.Security
        var parameters = new SortedDictionary<string, string>();
        parameters.Add("method", "facebook.users.getinfo");
        parameters.Add("session_key", sessionInfo.session_key);
        parameters.Add("uids", userId.ToString());
        parameters.Add("fields", "first_name,last_name,pic_big,pic_small,profile_url,
            status,hs_info,hometown_location");
    
        string jsonResult = JSON.Helper.StripSquareBrackets(
            Facebook.Web.SendCallToFacebook(parameters));
        return (JSON.Helper.Deserialize<Facebook.users.getInfo>(jsonResult));
    }

    参数被添加到已排序的字典集合中,然后执行API调用,返回一个JavaScript Object Notation (JSON)的string。这个JSON string被反序列化为.NET类,然后可以像任何普通.NET代码一样进行处理。由于Facebook JSON反序列化的细微差别,我不得不创建辅助函数JSON.Helper.StripSquareBrackets,它将Facebook返回的JSON格式化为Microsoft的DataContractJsonSerializer(.NET 3.5)可以处理的格式。

    与会话一样,C#的返回类型反映了Facebook API的调用——请参阅Users.getInfo

  • Facebook.Helpers.cs:这里只有一个方法ConvertUnixTimestampToDotNet,它将神秘的“unix”日期转换为.NET友好格式。
  • Facebook.Login.cs:在你弄清楚它是如何工作之前,登录Facebook可能会让人感到困惑。代码有很好的注释,以下将帮助你理解整体情况。
    • 首先阅读Facebook文档 - Facebook如何认证你的应用程序
    • 它“简单”到:1 - 创建一个身份验证令牌,2 - 执行登录,3 - 跟踪会话以便在API调用中使用。
    • 出于Facebook的“安全原因”,你总是需要通过Facebook的网页登录。这意味着你需要打开一个浏览器窗口或一个带有浏览器控件的窗口(参见Facebook.Windows.Login.XAML),然后传递一个格式正确的URI——登录URI的解析在此类中完成——参见:GetFacebookLoginPage(string auth_token)。据我所知,没有办法在不进行幕后操作的情况下实现静默登录。
  • Facebook.Security.cs:这个类包含方法GenerateFacebookParametersMD5Hash<string, string>。Facebook使用RESTful API通过互联网进行通信。共享密钥用于确保从客户端发送的信息与到达Facebook服务器的信息完全相同。本质上,MD5用于签名客户端应用程序的传出数据,Facebook在数据到达服务器时会验证它。Facebook和客户端应用程序都拥有共享密钥的副本,但在API调用过程中不需要在网络上传输密钥,从而避免被拦截。(关于共享密钥的更多信息请参见此维基百科链接)。

    为了确保MD5哈希在客户端和Facebook服务器上产生相同的结果,参数需要在哈希之前排序——这就是我将它们存储在已排序字典集合中的原因。

  • Facebook.Web.cs:这个类包含客户端和Facebook服务器之间基于HTTP通信的方法。
    • BuildFacebookPostString:接收一个已排序的字典参数,并生成Facebook API可识别的参数string。这个string将被发送到Facebook REST服务进行处理。
    • SendCallToFacebook:将通用的API调用参数(有时是必需的,有时则忽略)添加到传入的参数中。然后使用Facebook.Security类中的GenerateFacebookParametersMD5Hash对调用进行签名,并进行API调用,将请求通过网络发送。响应会被检查是否有错误,错误会通过简单的throw暴露。如果一切正常,则返回从Facebook检索到的JSON string。这里有一个使用WCF的选项,但它会增加更多的代码和配置,所以为了保持简单和干净,我使用了WebClient——我还认为使用统一的WCF方法可能会有一些并发症,因为我必须对从Facebook API返回的某些结果进行string操作,请参见JSON.Helpers类。

      如果时间允许,我将把实现更改为WCF并发布更新版本的二进制文件。

Blue2YellowB Windows文件夹包含了成功使用Facebook API所需的所有WPF窗口。
  • Facebook.Windows.Login.XAML:一个简单的XAML窗口,包含一个WPF Frame控件,可以托管一个浏览器控件。Facebook要求使用其Web前端进行登录。这可能会令人沮丧,但如果Facebook调用的URI格式正确且参数有效,Facebook会负责登录和检查凭据,并提供一个完整的界面来确认对目标Facebook应用程序的访问。

    当此页面通过构造函数加载时,它会接收一个URI作为参数。这个URI需要为Facebook登录进行正确格式化和参数化,窗口的Frame控件会立即导航到该地址。

    页面加载需要几秒钟,我想,为了指示活动,我将显示一个等待指示——一个旋转的XAML时钟。

  • Facebook.Windows.WaitPage.XAML:上面提到的等待时钟。它与Facebook API集成无关,但它是WPF动画的一个简单示例。这并没有我预期的那么成功,因为在XAML时钟加载和切换时仍然存在一些“空白”时间。我想把它留在解决方案中,因为它是一个有趣的XAML页面,可能会引起兴趣。
Blue2YellowC JSON文件夹原本打算用于存放JSON的解析类,但利用.NET 3.5,我节省了很多工作。这简化为下面的方法。

JSON.Helper.cs:包含以下方法

  • Serialize<T>(T typeToserialize):将.NET对象序列化为JSON string
  • T Deserialize<T>(string jsonToDeserialize):从JSON string反序列化.NET对象
  • string StripSingleResult(string jsonIn):用于解析Facebook返回的仅包含单个值的JSON结果
  • ArrayList ArrayFromJSON(string jsonIn):从JSON string中解析出项目数组
  • string StripSquareBrackets(string jsonIn):移除Facebook在某些调用中返回的多余的方括号[和]。如果移除这些,JSON string将无法使用Microsoft的DataContractJsonSerializer进行反序列化。
Blue2YellowD Types文件夹包含用于存储从Facebook返回的数据的结构。

这是代码“似乎”变得混乱的地方——是否应该使用Facebook.Types作为命名空间还有待商榷。我决定采用一种紧密模仿Facebook API的结构,尽管从.NET角度来看可读性稍差,但可以轻松引用你正在使用的Facebook API的部分。命名与Facebook API调用相匹配。WCF(Windows Communication Foundation)的DataContractDataMember属性用于装饰属性和类,以便DataContractJsonSerializer可以对其进行序列化和反序列化。这个方便的功能帮助节省了大量开发时间,并为处理Facebook返回的JSON结果提供了一个非常优雅的编码解决方案。

代码非常简单,其中一个契约如下所示

// Namespace does not match code / folder hierarchy as I wanted it to reflect
// the facebook structure
namespace Facebook.users
{
    /// <summary>
    /// Type that can be serialized from JSON to a .net class
    /// http://wiki.developers.facebook.com/index.php/Users.getInfo
    /// </summary>
    [DataContract]
    public class getInfo
    {
        [DataMember]
        public string first_name { get; set; }

        [DataMember]
        public string last_name { get; set; }
        ...
        ...

序列化器和反序列化器知道如何使用这些契约进行工作,无需任何额外的编码。IgnoreDataMemberAttribute也用于排除计算出的字段和附加字段,防止它们干扰序列化器,并提供一种简洁、有用的机制来扩展类,超越Facebook返回的字段。例如,可以公开.NET可以轻松使用的格式的Facebook日期。

// Property to return the time in a .net friendly datetime instead of the unix EPOC time
[IgnoreDataMember] // Marked as ignore as it is not to be populated from the facebook
// lookup(i.e. not a facebook field)
public DateTime time_DateTime { get {
    return Facebook.Helpers.ConvertUnixTimestampToDotNet(time); } }

这些WCF属性非常强大,并且支持嵌套的DataContracts(即,一个DataContract内包含另一个DataContract)。令我惊讶的是,这与DataContractJsonSerializer一起在第一次就成功了。一个很好的例子是Facebook.users命名空间中getInfo类中嵌套的hometown_location

所有这些优点都可以在.NET的System.Runtime.Serialization命名空间中找到。

Blue2YellowE app.configSettings文件(命名空间Facebook.Properties)保存了以下项目的必需设置、访问和持久化:
  • ApplicationKey:要连接的应用程序的Facebook应用程序密钥。
  • Secret:要连接的应用程序的Facebook密钥。
  • ApiVersion:要连接的Facebook API的版本。
  • ApiUrlFacebook RESTful API的URL。使用参数替换(例如 {0})来完成此字符串在代码中的构建。
  • LoginUrl:要使用的Facebook登录URL。使用参数替换(例如 {0})来完成此字符串在代码中的构建。
  • MaxFriends:要检索的最大好友数量,主要用于通过处理比“所有好友”更小的集合来减少测试时间。

注意:这些设置是按用户持久化的,并且(默认情况下由.NET)存储在用户的C:\Users\<username>\AppData\Local\FacebookApplication文件夹中。

Blue3 项目:FacebookApplication
Blue3YellowA 此项目包含一个WPF/XAML窗体FacebookFriendsList。在该窗体上,你会找到在运行演示中描述的按钮和字段。这些控件下方是一个开放区域,包含一个标准的WPF ListView控件。

网格布局

窗口使用网格布局,设置为随窗口大小拉伸。通过尽量少地使用绝对尺寸的窗口控件,可以轻松生成一个可以流畅调整大小并良好缩放的窗口。

 <Grid HorizontalAlignment="Stretch" x:Name="uiGridMain" VerticalAlignment="Stretch">

默认样式

我使用了按钮、文本框和标签的默认样式,以减少XAML中的重复和混乱。这些默认样式适用于整个窗口,你可以搜索找到一个示例。

<Style x:Key="ButtonStyle" TargetType="{x:Type Button}">

文本框和设置

文本框通过数据绑定到应用程序设置,绑定是在textbox元素上完成的。

<TextBox Grid.Row="0" Grid.Column="1" x:Name="uiTbxApplicationKey"  TabIndex="0"
   Text="{Binding Path=ApplicationKey, Mode=TwoWay,
   Source={x:Static p:Settings.Default}}"/>

这种声明式绑定不需要“代码”,只需通过XAML进行配置。保存设置按钮会调用Facebook程序集中的一个静态方法来保存绑定的设置。(Facebook.Properties.Settings.Default.Save();

登录按钮

登录按钮的点击事件映射到代码隐藏中的private void uiBtnLogin_Click(object sender, RoutedEventArgs e)。首先,它执行简单的参数长度验证,主要是为了确保用户已将其更改为正确(至少在长度上)的值。然后实例化Facebook项目中的API,API中的构造函数会启动Facebook登录。登录失败会抛出异常,因此接下来的代码可以假定登录成功。成功后,uiBtnGetFriends按钮会被启用,以便用户可以执行此操作。

如前所述,登录失败会抛出错误,该错误会被捕获并显示给用户,并在状态栏中记录。

注意Facebook API引用private Facebook.API fbi;被保存在类级别。这是因为它封装了与Facebook服务器的会话,并且可以在每次调用API时重用,无需在每次需要调用时都实例化。

获取好友按钮

获取好友按钮的点击事件映射到代码隐藏中的private void uiBtnGetFriends_Click(object sender, RoutedEventArgs e)。除了设置一些状态消息外,它执行两个主要功能。

首先,它通过Facebook API调用,检索当前用户好友的Facebook用户ID的ArrayList。参见ArrayList facebookFriends = fbi.GetListOfFriends();

其次,该方法遍历ID的ArrayList,并检索其中每个用户(最多Facebook.Properties.Settings.Default.MaxFriends个)的更多详细信息。使用API调用fbi.GetUsersInfo来执行此操作,每个生成的Facebook.users.getInfo对象(基本上是Facebook 'user' 实体的“等价物”)存储在一个ObservableCollection中。这个集合对象的优点是它会自动通知UI集合中的变化,UI也会自动更新。在这个项目中,这会导致列表随着从Facebook检索并添加的每个用户而可见地增长。真棒!

列表视图和列表视图项

ListView是通过使用ItemTemplate填充的。在这种情况下,我使用了一个StackPanel来生成一个复合控件,以封装Facebook用户详细信息的子集,这个ItemTemplate在这里绑定到集合。

var facebookFriendsDetails = new ObservableCollection<Facebook.users.getInfo>();
uiLvFriends.ItemsSource = facebookFriendsDetails;

有趣的是,绑定是在数据添加之前完成的,而不是像典型的过程那样先检索数据,然后将这些数据绑定到列表或网格。

下面的ItemTemplate是为每个返回的Facebook好友渲染的,一个不错的增强功能是将其移到一个自定义的XAML用户控件中。

<ListView.ItemTemplate>
    <DataTemplate>

        <StackPanel FlowDirection="LeftToRight" Orientation="Horizontal">
            <Image Margin="3" Source="{Binding Path=pic_small}" />
            <StackPanel Margin="3" VerticalAlignment="Center">
                <StackPanel Orientation="Horizontal">
                    <TextBlock>
                        <Hyperlink Click="NavigateToUserProfile"
                            NavigateUri="{Binding Path=profile_url}">

                            <InlineUIContainer>
                        	<TextBlock FontSize="12" FontWeight="Bold"
                               Text="{Binding Path=full_name}" />
                            </InlineUIContainer>
                        </Hyperlink>
                    </TextBlock>
                </StackPanel>

                <StackPanel Orientation="Horizontal">
                    <TextBlock FontStyle="Italic" Text="Status: "/>
                    <TextBlock Text="{Binding Path=status.status_line}" />
                </StackPanel>
            </StackPanel>
        </StackPanel>

    </DataTemplate>
</ListView.ItemTemplate>

最后一点需要注意,超链接元素的点击事件映射到代码隐藏中的NavigateToUserProfile方法,允许应用程序用户点击以跳转到相应的Facebook好友个人资料,该资料会在客户端的默认浏览器中打开。

值得关注的点

  • 通过WebClient类和JSON序列化对象连接到Facebook RESTful API似乎是最简单的。统一的WCF方法可以取代它,并会是一个有趣的调查,对比所需的额外代码和配置与潜在的好处。
  • 我越使用WPF,越喜欢它——它已经有很多东西了(其中一半我可能永远也搞不懂……),而且我仍然希望它有更多(例如,真正的视觉层)。
  • 同样,对于“WPF层”,似乎没有明确的方法来zOrder控件,它们似乎按照它们在XAML中出现的顺序排列。
  • WPF中的动画比你想象的要容易——尽管它确实会让你重拾你的数学(Logo?)技能:)
  • 在WPF中,少即是多——你硬编码的尺寸越少,设计就能更好地适应不同的屏幕分辨率和缩放。
  • Facebook API似乎声名狼藉,但一旦你理解了它,API就是健壮和可靠的。
  • Facebook API似乎经常变化,这也是我将此示例范围限制在简单好友查找的原因之一,希望它能“保持最新”一段时间。
  • 在开始之前,Facebook API文档非常值得一读,这和Google回答了我大部分的问题。

历史

  • V1.0 - 暂时没有改动... :)
© . All rights reserved.