Universal App 8.1 Facebook integration ( 發文、打卡 )

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&center={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

Nuget

在上方的_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的方式!

App Confirm

所以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&center={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的樣子

TT

 

APP範例下載連結

https://www.dropbox.com/s/7eejxldz7823tq1/FacebookClient%20test_03Jun2015.zip?dl=0