Universal Apps with Facebook integration
Windows Phone 的登入在Dotblogs已經有幾位分享詳細的作法所以我就不細說了~今天要說明的是FaceBook上傳Photo、打卡、打卡景點搜尋、發文觀看權限。
使用的是Windows UAP的專案類型以及MVVM的開發方式
我先在View上面寫上幾個Button並用Command的方式來做連接Facebook的動作
<Page
x:Class="FacebookClient_test.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:FacebookClient_test"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:FacebookClient_test.ViewModel"
mc:Ignorable="d">
<Page.Resources>
<vm:MainPageViewModel x:Key="MainVM"/>
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" DataContext="{StaticResource MainVM}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" HorizontalAlignment="Center" VerticalAlignment="Center"
xmlns:cvt="using:FacebookClient_test.Converters">
<StackPanel.Resources>
<cvt:ValueWhenConverter x:Key="Cvt">
<cvt:ValueWhenConverter.When>
<x:Boolean>True</x:Boolean>
</cvt:ValueWhenConverter.When>
<cvt:ValueWhenConverter.Value>
<Visibility>Visible</Visibility>
</cvt:ValueWhenConverter.Value>
<cvt:ValueWhenConverter.Otherwise>
<Visibility>Collapsed</Visibility>
</cvt:ValueWhenConverter.Otherwise>
</cvt:ValueWhenConverter>
</StackPanel.Resources>
<Button Content="login" Command="{Binding LoginButtonCommand}" CommandParameter="{Binding RelativeSource={RelativeSource Mode=Self}, Path=Content}" Visibility="{Binding IsLogined, Converter={StaticResource Cvt}, Mode=TwoWay}"/>
<Button Content="post" Command="{Binding PostToWallButtonCommand}" CommandParameter="{Binding RelativeSource={RelativeSource Mode=Self}, Path=Content}"/>
<Button Content="get tag and checkin list" Command="{Binding GetUserCheckInAndTagsButtonCommand}" CommandParameter="{Binding RelativeSource={RelativeSource Mode=Self}, Path=Content}"/>
<Button Content="checkin" Command="{Binding GetLocationOfPlaceCommand}" CommandParameter="{Binding RelativeSource={RelativeSource Mode=Self}, Path=Content}"/>
</StackPanel>
<StackPanel Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock x:Name="DisplayTb" TextWrapping="Wrap" Style="{StaticResource HeaderTextBlockStyle}" FontSize="30"/>
</StackPanel>
</Grid>
</Page>
接者在ViewModel寫上如下的Code
using Facebook;
using FacebookClient_test.Model;
using Newtonsoft.Json;
using System;
using System.Dynamic;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading.Tasks;
using System.Windows.Input;
using Windows.ApplicationModel;
using Windows.Security.Authentication.Web;
using Windows.Storage;
using Windows.Web.Http;
namespace FacebookClient_test.ViewModel
{
public class MainPageViewModel : BaseViewModel
{
private const string _FB_BaseUri = "https://www.facebook.com/dialog/oauth?";
private const string _FB_APPID = "";
private const string _FB_APPSecret = "";
private const string _APP_SSID = "";
private const string _WP_APPID = "";
private const string _FB_Scpoe = "public_profile,email";
private string _CheckInPlaceID = string.Empty;
private string _ResponseData = string.Empty;
private string _FB_AccessToken = string.Empty;
private string _FB_ExpireTime = string.Empty;
private string outputStr = string.Empty;
private FacebookUserInfoModel _userClient;
public ICommand LoginButtonCommand { get; private set; }
public ICommand PostToWallButtonCommand { get; private set; }
public ICommand GetUserCheckInAndTagsButtonCommand { get; private set; }
public ICommand GetLocationOfPlaceCommand { get; private set; }
private Boolean _IsLogined = default(Boolean);
public Boolean IsLogined
{
get { return _IsLogined; }
set
{
if (_IsLogined != value)
{
_IsLogined = value;
NotifyChanged();
}
}
}
public MainPageViewModel()
{
LoginButtonCommand = new ButtonBaseCommand() { ExecuteCommand = new Action(() => GetLogin()) };
PostToWallButtonCommand = new ButtonBaseCommand() { ExecuteCommand = new Action(() => PostToWall()) };
GetUserCheckInAndTagsButtonCommand = new ButtonBaseCommand() { ExecuteCommand = new Action(() => GetCheckInAndTagInfo()) };
GetLocationOfPlaceCommand = new ButtonBaseCommand() { ExecuteCommand = new Action(() => GetLocationOfPlace("7-11", 25.04, 121.52, 1000)) };
}
private async void GetLogin()
{
var uriString = string.Format("{0}client_id={1}&redirect_uri={2}&response_type=token&display=popup&scope={3}", _FB_BaseUri, _FB_APPID, _APP_SSID, _FB_Scpoe);
var uri = new Uri(uriString);
#if WINDOWS_APP
var result = await WebAuthenticationBroker.AuthenticateAsync(WebAuthenticationOptions.None, uri);
if (result.ResponseStatus == WebAuthenticationStatus.Success)
{
_ResponseData = result.ResponseData;
_ResponseData = _ResponseData.Replace(_APP_SSID, string.Empty);
_FB_AccessToken = _ResponseData.Split('&').First().Replace("/#access_token=", string.Empty);
_FB_ExpireTime = _ResponseData.Split('&').Last().Replace("expires_in=", string.Empty);
await GetUserInfo();
}
#else
var redirectUri = "msft-" + _WP_APPID.Replace("-", string.Empty) + "://authorize";
var wpuriString = string.Format("{0}client_id={1}&redirect_uri={2}&response_type=token&display=popup&scope={3}", _FB_BaseUri, _FB_APPID, redirectUri, _FB_Scpoe);
var wpuri = new Uri(wpuriString);
WebAuthenticationBroker.AuthenticateAndContinue(wpuri, new Uri(redirectUri));
//var result = await WebAuthenticationBroker.AuthenticateAsync(WebAuthenticationOptions.None, uri);
//if (result.ResponseStatus == WebAuthenticationStatus.Success)
//{
// _ResponseData = result.ResponseData;
// _ResponseData = _ResponseData.Replace(_APP_SSID, string.Empty);
// _FB_AccessToken = _ResponseData.Split('&').First().Replace("/#access_token=", string.Empty);
// _FB_ExpireTime = _ResponseData.Split('&').Last().Replace("expires_in=", string.Empty);
// await GetUserInfo();
//}
#endif
}
private async void ExchangeLongternExpire()
{
var uriString = string.Format("https://graph.facebook.com/oauth/access_token?grant_type=fb_exchange_token&client_id={0}&redirect_uri={1}&client_secret={2}&fb_exchange_token={3}", _FB_APPID, _APP_SSID, _FB_APPSecret, _FB_AccessToken);
var uri = new Uri(uriString);
using (var client = new HttpClient())
using (var response = await client.GetAsync(uri))
{
response.EnsureSuccessStatusCode();
var responseString = await response.Content.ReadAsStringAsync();
System.Diagnostics.Debug.WriteLine(responseString);
}
}
private async Task GetUserInfo()
{
var uriString = string.Format("https://graph.facebook.com/v2.3/me?access_token={0}", _FB_AccessToken);
var uri = new Uri(uriString);
using (var client = new HttpClient())
using (var response = await client.GetAsync(uri))
{
response.EnsureSuccessStatusCode();
var responseString = await response.Content.ReadAsStringAsync();
System.Diagnostics.Debug.WriteLine(responseString);
_userClient = JsonConvert.DeserializeObject<FacebookUserInfoModel>(responseString);
outputStr = string.Format("ID:{0}\nEmail:{1}\nFirst name:{2}\nLast name{3}\nGender:{4}\nFull name:{5}\nLink:{6}\nlocal:{7}\nTimezone:{8}\nUpdate time:{9}\nVerified:{10}",
_userClient.id,
_userClient.email,
_userClient.first_name,
_userClient.last_name,
_userClient.gender,
_userClient.name,
_userClient.link,
_userClient.locale,
_userClient.timezone,
_userClient.updated_time,
_userClient.verified);
if (!String.IsNullOrEmpty(outputStr))
IsLogined = !IsLogined;
}
}
private async void PostToWall()
{
var client = new FacebookClient(_FB_AccessToken);
dynamic messagePost = new ExpandoObject();
messagePost.link = "http://www.microsoft.com/";
messagePost.name = "[name] Facebook name...";
messagePost.caption = " Facebook caption";
messagePost.description = "[description] Facebook description...";
messagePost.message = "[message] Facebook message...";
messagePost.privacy = new FacebookPriacyModel() { value = ValueEnum.SELF.ToString() };
messagePost.place = _CheckInPlaceID;
await client.PostTaskAsync("/feed", messagePost);
}
private async void PostImageToWall()
{
var client = new FacebookClient(_FB_AccessToken);
var media = new FacebookMediaObject()
{
FileName = "Asshole.jpg",
ContentType = "image/jpeg"
};
var assetsFolder = await Package.Current.InstalledLocation.GetFolderAsync("Assets");
var imageFolder = await assetsFolder.GetFolderAsync("Images");
var imageFile = await imageFolder.GetFileAsync("Asshole.jpg");
var buffer = await FileIO.ReadBufferAsync(imageFile);
media.SetValue(buffer.ToArray());
dynamic messagePost = new ExpandoObject();
messagePost.link = "http://www.microsoft.com/";
messagePost.name = "[name] Facebook name...";
messagePost.caption = " Facebook caption";
messagePost.description = "[description] Facebook description...";
messagePost.message = "[message] Facebook message...";
messagePost.source = media;
messagePost.privacy = new FacebookPriacyModel() { value = ValueEnum.SELF.ToString() };
await client.PostTaskAsync("/photos", messagePost);
}
private async void GetCheckInAndTagInfo()
{
var uriString = string.Format("https://graph.facebook.com/v2.3/{0}/tagged_places?access_token={1}", _userClient.id, _FB_AccessToken);
var uri = new Uri(uriString);
using (var client = new HttpClient())
using (var response = await client.GetAsync(uri))
{
response.EnsureSuccessStatusCode();
var responseString = await response.Content.ReadAsStringAsync();
System.Diagnostics.Debug.WriteLine(responseString);
}
}
private async void GetLocationOfPlace(string queryString, double latitude, double longitude, double distance)
{
var uriString = string.Format("https://graph.facebook.com/v2.3/search?q={0}&type=place¢er={1}&distance={2}&access_token={3}", queryString, string.Format("{0},{1}", latitude, longitude), distance, _FB_AccessToken);
var uri = new Uri(uriString);
using (var client = new HttpClient())
using (var response = await client.GetAsync(uri))
{
response.EnsureSuccessStatusCode();
var responseString = await response.Content.ReadAsStringAsync();
System.Diagnostics.Debug.WriteLine(responseString);
var responseObject = JsonConvert.DeserializeObject<FacebookSearchLocationModel>(responseString);
}
}
}
public class ButtonBaseCommand : ICommand
{
public Action ExecuteCommand { get; set; }
public bool CanExecute(object parameter)
{
if (parameter != null)
return true;
else
return false;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
if (CanExecuteChanged != null)
{
CanExecuteChanged(this, new EventArgs());
if (ExecuteCommand != null)
ExecuteCommand.Invoke();
}
}
}
}
這邊有幾點要注意!
先使用Nuget取得Facebook SDK (Facebook For .Net)以及 Json .Net
在上方的_FB_APPID就是註冊在FB的應用程式ID,然後_FB_APPSecrest就是在註冊在FB的應用程式Token或Secrect key。這邊兩個得申請FB的應用程式就會由FB產生提供給你!
然後_APP_SSID就是Windows Store 的APP ID(記得是要發佈在Store上取得唯一識別的ID才行),_WP_APPID 也是大同小異的是該Windows Phone的唯一識別ID
接者就是準備登入FB的部分啦!在Windows上使用WebAuthenticationBroker在Store上會呈現MessageDialog類似的形式,但在Windows Phone就是跳出App的方式!
所以Phone的部分需要再App.xaml.cs的OnActived裡面判斷是否由WebAuthenticationBroker來轉回該APP
protected override void OnActivated(IActivatedEventArgs args)
{
base.OnActivated(args);
var _WP_APPID = "";
var redirectUri = "msft-" + _WP_APPID.Replace("-", string.Empty) + "://authorize";
string _ResponseData = string.Empty;
string _FB_AccessToken = string.Empty;
string _FB_ExpireTime = string.Empty;
//string fbAccessToken = string.Empty;
if(args.Kind == ActivationKind.WebAuthenticationBrokerContinuation)
{
var webArgs = args as WebAuthenticationBrokerContinuationEventArgs;
if(webArgs.WebAuthenticationResult.ResponseStatus == Windows.Security.Authentication.Web.WebAuthenticationStatus.Success)
{
_ResponseData = webArgs.WebAuthenticationResult.ResponseData;
_ResponseData = _ResponseData.Replace(redirectUri, string.Empty);
_FB_AccessToken = _ResponseData.Split('&').First().Replace("/#access_token=", string.Empty);
_FB_ExpireTime = _ResponseData.Split('&').Last().Replace("expires_in=", string.Empty);
//await GetUserInfo();
}
}
}
發文
private async void PostToWall()
{
var client = new FacebookClient(_FB_AccessToken);
dynamic messagePost = new ExpandoObject();
messagePost.link = "http://www.microsoft.com/";
messagePost.name = "[name] Facebook name...";
messagePost.caption = " Facebook caption";
messagePost.description = "[description] Facebook description...";
messagePost.message = "[message] Facebook message...";
messagePost.privacy = new FacebookPriacyModel() { value = ValueEnum.SELF.ToString() };
//messagePost.place = "207403962629167";
await client.PostTaskAsync("/feed", messagePost);
}
現在Facebook的欄位在Facebook .Net都是使用Virtual的Method和dynamic的物件帶發文資訊!
以上發文就是帶上連結(第五行)、發文標題(第六行)、擷取資訊(第七行)、敘述(第八行)、發文文字(第九行)、觀看權限(第十行)
如果要附上位置[打卡位置]就是在第11行加上地點的Place ID然後使用PostTaskAsync並在第一個參數代上 /feed 來發文到自已的TimeLine。
發文觀看權限的Model Class如下
using System;
using System.Collections.Generic;
using System.Text;
namespace FacebookClient_test.Model
{
public class FacebookPriacyModel : BaseModel
{
private string _description;
public string description
{
get { return _description; }
set { _description = value; }
}
private string _value;
public string value
{
get { return _value; }
set { _value = value; }
}
private string _friends;
public string friends
{
get { return _friends; }
set { _friends = value; }
}
private string _allow;
public string allow
{
get { return _allow; }
set { _allow = value; }
}
private string _deny;
public string deny
{
get { return _deny; }
set { _deny = value; }
}
}
public enum FriendsEnum
{
ALL_FRIENDS, FRIENDS_OF_FRIENDS, SOME_FRIENDS
}
public enum ValueEnum
{
EVERYONE, ALL_FRIENDS, FRIENDS_OF_FRIENDS, SELF, CUSTOM
}
}
這邊要注意一下的是藉由ValueEnum轉換成String之後吐給外層Class的Value做設定。
若是要自訂觀看權限要先將Value的部分設定成CUSTOM(ValueEnum轉乘String)接者在設定Friend為FriendsEnum為String的Value。
發文以圖片為主
private async void PostImageToWall()
{
var client = new FacebookClient(_FB_AccessToken);
var media = new FacebookMediaObject()
{
FileName = "Asshole.jpg",
ContentType = "image/jpeg"
};
var assetsFolder = await Package.Current.InstalledLocation.GetFolderAsync("Assets");
var imageFolder = await assetsFolder.GetFolderAsync("Images");
var imageFile = await imageFolder.GetFileAsync("Asshole.jpg");
var buffer = await FileIO.ReadBufferAsync(imageFile);
media.SetValue(buffer.ToArray());
dynamic messagePost = new ExpandoObject();
messagePost.link = "http://www.microsoft.com/";
messagePost.name = "[name] Facebook name...";
messagePost.caption = " Facebook caption";
messagePost.description = "[description] Facebook description...";
messagePost.message = "[message] Facebook message...";
messagePost.source = media;
messagePost.privacy = new FacebookPriacyModel() { value = ValueEnum.SELF.ToString() };
//messagePost.place = "207403962629167";
//await client.PostTaskAsync("/feed", messagePost);
await client.PostTaskAsync("/photos", messagePost);
}
如上的Code先產生FacebookMediaObject的物件,接者將該圖片轉換成Buffer然後傳換成陣列的形式到Media(這邊的buffer.ToArray() 方法會需要用到 System.Runtime.InteropServices.WindowsRuntime 命名空間!VS並部會自動提示~)
然後把原先發文的 /feed 改為 /photos就可以發圖了~
打卡相關輔助方法
private async void GetCheckInAndTagInfo()
{
var uriString = string.Format("https://graph.facebook.com/v2.3/{0}/tagged_places?access_token={1}", _userClient.id, _FB_AccessToken);
var uri = new Uri(uriString);
using (var client = new HttpClient())
using (var response = await client.GetAsync(uri))
{
response.EnsureSuccessStatusCode();
var responseString = await response.Content.ReadAsStringAsync();
System.Diagnostics.Debug.WriteLine(responseString);
}
}
private async void GetLocationOfPlace(string queryString, double latitude, double longitude, double distance)
{
var uriString = string.Format("https://graph.facebook.com/v2.3/search?q={0}&type=place¢er={1}&distance={2}&access_token={3}", queryString, string.Format("{0},{1}", latitude, longitude), distance, _FB_AccessToken);
var uri = new Uri(uriString);
using (var client = new HttpClient())
using (var response = await client.GetAsync(uri))
{
response.EnsureSuccessStatusCode();
var responseString = await response.Content.ReadAsStringAsync();
System.Diagnostics.Debug.WriteLine(responseString);
var responseObject = JsonConvert.DeserializeObject<FacebookSearchLocationModel>(responseString);
}
}
GetCheckInAndTagInfo就是尋找之前的打卡地點以及標籤回傳為JSON的格式。
GetLocationOfPlace就是可以搜尋再依定經緯度為中心的方圓多少公里的打卡地點資訊。
FacebookSearchLocationModel的Class
using System.Collections.Generic;
namespace FacebookClient_test.Model
{
public class FacebookSearchLocationModel : BaseModel
{
public List<DataModel> data { get; set; }
}
public class DataModel : BaseModel
{
private string _category = string.Empty;
public string category
{
get { return _category; }
set
{
if (_category != value)
{
_category = value;
NotifyChange();
}
}
}
public List<CategoryModel> category_list { get; set; }
private string _name = string.Empty;
public string name
{
get { return _name; }
set
{
if (_name != value)
{
_name = value;
NotifyChange();
}
}
}
private string _id = string.Empty;
public string id
{
get { return _id; }
set
{
if (_id != value)
{
_id = value;
NotifyChange();
}
}
}
}
public class CategoryModel : BaseModel
{
private string _id;
public string id
{
get { return _id; }
set
{
if (_id != value)
{
_id = value;
NotifyChange();
}
}
}
private string _name;
public string name
{
get { return _name; }
set
{
if (_name != value)
{
_name = value;
NotifyChange();
}
}
}
}
public class LocationModel : BaseModel
{
private string _street = string.Empty;
public string street
{
get { return _street; }
set
{
if (_street != value)
{
_street = value;
NotifyChange();
}
}
}
private string _city = string.Empty;
public string city
{
get { return _city; }
set
{
if (_city != value)
{
_city = value;
NotifyChange();
}
}
}
private string _state = string.Empty;
public string state
{
get { return _state; }
set
{
if (_state != value)
{
_state = value;
NotifyChange();
}
}
}
private string _country = string.Empty;
public string country
{
get { return _country; }
set
{
if (_country != value)
{
_country = value;
NotifyChange();
}
}
}
private int _zip = 0;
public int zip
{
get { return _zip; }
set
{
if (_zip != value)
{
_zip = value;
NotifyChange();
}
}
}
private double _latitude;
public double latitude
{
get { return _latitude; }
set
{
if (_latitude != value)
{
_latitude = value;
NotifyChange();
}
}
}
private double _longitude;
public double longitude
{
get { return _longitude; }
set
{
if (_longitude != value)
{
_longitude = value;
NotifyChange();
}
}
}
}
}
發文到FACEBOOK的樣子
APP範例下載連結
https://www.dropbox.com/s/7eejxldz7823tq1/FacebookClient%20test_03Jun2015.zip?dl=0