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






4.92/5 (28投票s)
关于开始使用 Facebook API 和 WPF 的探讨
目录
引言
我想了解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的工作原理。这个应用程序提供了这样的功能,没有复杂的抽象层、啰嗦的异常处理或成百上千的类;希望足以引导你走向正确的方向。
先决条件
- 运行此示例前需要.NET Framework 3.51。如果你没有,请从Microsoft下载并安装。我使用了这里的安装程序,可惜是一个232MB的巨头!
- 你需要一个Facebook应用程序来操作 -
运行演示
从归档文件中提取演示,然后双击FacebookApplication.exe。下面的图表和相应的说明将引导你了解用户界面。
![]() | 将你的Facebook应用程序的API密钥(见上面的先决条件)粘贴到[Your Key]处 |
![]() | 将你的Facebook应用程序的密钥(见上面的先决条件)粘贴到[Your Secret]处 |
![]() | 输入要从Facebook检索的朋友数量(我为了方便测试,将其限制在工作集内) |
![]() | 可选地保存这些设置 |
![]() | 点击“登录”并登录Facebook。首次运行时你需要允许Facebook应用程序的访问,并会提示你这样做。按照指示关闭登录窗口。 |
![]() | 点击“获取好友”以调用Facebook API,并观察应用程序填充用户界面。 |
使用代码
打开解决方案文件(源代码)后,首先要做的是生成解决方案,以便项目引用能正确解析,并且XAML能在设计器中查看。代码有详细的注释,所以我不打算赘述,但我会尽力解释其总体架构和项目结构,以帮助你更好地理解。我还考虑过一个更面向对象的做法,利用基类来实现更优雅、更易扩展的设计,但为了确保解决方案简单易懂,我放弃了。
下面的图表和相应说明将引导你了解解决方案文件
![]() | 提供的解决方案WPF_Facebook 包含两个项目
|
![]() Facebook | |
![]() ![]() |
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
。这个JSONstring
被反序列化为.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检索到的JSONstring
。这里有一个使用WCF的选项,但它会增加更多的代码和配置,所以为了保持简单和干净,我使用了WebClient
——我还认为使用统一的WCF方法可能会有一些并发症,因为我必须对从Facebook
API返回的某些结果进行string
操作,请参见JSON.Helpers
类。如果时间允许,我将把实现更改为WCF并发布更新版本的二进制文件。
![]() ![]() | 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页面,可能会引起兴趣。
![]() ![]() | JSON文件夹原本打算用于存放JSON的解析类,但利用.NET 3.5,我节省了很多工作。这简化为下面的方法。 |
JSON.Helper.cs:包含以下方法
Serialize<T>(T typeToserialize)
:将.NET对象序列化为JSONstring
T Deserialize<T>(string jsonToDeserialize)
:从JSONstring
反序列化.NET对象string StripSingleResult(string jsonIn)
:用于解析Facebook返回的仅包含单个值的JSON结果ArrayList ArrayFromJSON(string jsonIn)
:从JSONstring
中解析出项目数组string StripSquareBrackets(string jsonIn)
:移除Facebook在某些调用中返回的多余的方括号[和]。如果移除这些,JSONstring
将无法使用Microsoft的DataContractJsonSerializer
进行反序列化。
![]() ![]() | Types文件夹包含用于存储从Facebook返回的数据的结构。 |
这是代码“似乎”变得混乱的地方——是否应该使用Facebook.Types
作为命名空间还有待商榷。我决定采用一种紧密模仿Facebook
API的结构,尽管从.NET角度来看可读性稍差,但可以轻松引用你正在使用的Facebook
API的部分。命名与Facebook
API调用相匹配。WCF(Windows Communication Foundation)的DataContract
和DataMember
属性用于装饰属性和类,以便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
命名空间中找到。
![]() ![]() | app.config 和Settings 文件(命名空间Facebook.Properties )保存了以下项目的必需设置、访问和持久化: |
ApplicationKey
:要连接的应用程序的Facebook
应用程序密钥。Secret
:要连接的应用程序的Facebook
密钥。ApiVersion
:要连接的Facebook
API的版本。ApiUrl
:Facebook
RESTful API的URL。使用参数替换(例如 {0})来完成此字符串在代码中的构建。LoginUrl
:要使用的Facebook
登录URL。使用参数替换(例如 {0})来完成此字符串在代码中的构建。MaxFriends
:要检索的最大好友数量,主要用于通过处理比“所有好友”更小的集合来减少测试时间。
注意:这些设置是按用户持久化的,并且(默认情况下由.NET)存储在用户的C:\Users\<username>\AppData\Local\FacebookApplication文件夹中。
![]() FacebookApplication | |
![]() ![]() | 此项目包含一个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 - 暂时没有改动... :)