Wednesday, April 25, 2012

[FB] 新版Facebook JDK與C# SDK

FBSDK00

網路技術日新月異,Facebook的各項功能也常常在變化,以前的Facebook C# SDK還需要自己 build 出 DLL 來,現在已經不用了,新版的Facebook C# SDK不但網站改了地方,也改成用 Nuget 去取得程式,還加入了支援Windows Phone, Windows Azure, Windows Metro Style Apps,可說是相當多的改變。
(歐對,FB JDK也三不五時在改,請記得定時更新載入語法)


Install Facebook C# SDK via NuGet
要用新版的Facebook C# SDK不難,只要你有裝 NuGet 就很方便了,如果沒用過NuGet沒關係,黑暗執行緒有寫過一篇NuGet介紹,可以參考,其實也就很簡單,在Visual Studio的Extension Manager裡面去找NuGet然後裝就對了,如果你的NuGet版本比較舊無法更新到新版成功的話,請用Administrator權限去開VS,移除掉舊的NuGet再裝新的就好。



FBSDK01

NuGet裝好後,在專案上右鍵選擇 Manage NuGet Packages。



FBSDK02

如上圖找到 Facebook ,按下 Install 就安裝完了,你會在 bin (或他應該出現的目錄)下面找到多出 Facebook.dll 與 Facebook.xml 這兩個檔案。


First try - Get something

然後就可以使用FB C# SDK去抓資料了,以沒有取得access token的狀態下,我們可以直接拉一些公開資訊來用,例如拉Facebook創辦人Mark Zuckerberg的基本資訊。
var client = new FacebookClient();
dynamic me = client.Get("zuck");
string firstName = me.first_name;
string lastName = me.last_name;
Label1.Text = firstName + " , "  + lastName;
FBSDK04
上面這段程式就會把zuck的姓與名拉出來,顯示為 Mark , Zuckerberg 。


Authentication using Javascript

接下來就用來取得驗證了,其實可以用FB C# SDK從後端去跟FB用graph認證,但是因為在blog上比較難展示,所以就參考官方文件使用FB JS SDK來取得access token,然後丟給後端程式。你可以參考Facebook C# SDK的getting-started去加入下面的語法以載入FB JDK。

<div id="fb-root"></div>
<script>
  window.fbAsyncInit = function() {
    FB.init({
      appId      : 'YOUR_APP_ID', // App ID
      status     : true, // check login status
      cookie     : true, // enable cookies to allow the server to access the session
      xfbml      : true  // parse XFBML
    });

    // Additional initialization code here
  };

  // Load the SDK Asynchronously
  (function(d){
     var js, id = 'facebook-jssdk', ref = d.getElementsByTagName('script')[0];
     if (d.getElementById(id)) {return;}
     js = d.createElement('script'); js.id = id; js.async = true;
     js.src = "//connect.facebook.net/en_US/all.js";
     ref.parentNode.insertBefore(js, ref);
   }(document));
</script>

然後getting-started的教學會叫你用 FB.Event.subscribe('auth.authResponseChange') 的方式去取得認證狀態,這不是不能用,但是看起來只有認證狀態改變時才會有東西,所以我改成用 FB.getLoginStatus() 的方式去取,這樣可以確保每次都有東西,所以就是加入下列的javascript在上面 FB.init // Additional initialization code here 這邊。

FB.getLoginStatus(function (response) {
            if (response.status === 'connected') {
                // the user is logged in and has authenticated your
                // app, and response.authResponse supplies
                // the user's ID, a valid access token, a signed
                // request, and the time the access token 
                // and signed request each expire
                var uid = response.authResponse.userID;
                var accessToken = response.authResponse.accessToken;

                var form = document.createElement("form");
                form.setAttribute("method", 'post');
                form.setAttribute("action", '/WebTest/FacebookLogin.ashx');

                var field = document.createElement("input");
                field.setAttribute("type", "hidden");
                field.setAttribute("name", 'accessToken');
                field.setAttribute("value", accessToken);
                form.appendChild(field);

                document.body.appendChild(form);
                form.submit();
                // TODO: Handle the access token

            } else if (response.status === 'not_authorized') {
                alert('not_authorized');
                // the user is logged in to Facebook, 
                // but has not authenticated your app
            } else {
                alert('not_logged');
                // the user isn't logged in to Facebook.
            }
        });

基本上,code裡面都有註解了,所以也不用多做解釋,我只是在狀態為connected,也就是有用這個fb app的時候,將取得的access token丟到後端頁面去處理而已,而這個handler FacebookLogin.ashx 做的事情也很簡單,就只是將收到的access token轉成session。

FBSDK03
public void ProcessRequest (HttpContext context) {
        var accessToken = context.Request["accessToken"];
        context.Session["AccessToken"] = accessToken;
        context.Response.Redirect("FBTest.aspx");
    }
內容大概是長的像上面這樣,寫session後將頁面導到你要的地方,要注意的是在handler裡面用HttpContext.Session,必須要繼承 System.Web.SessionState.IRequiresSessionState ,不然會出錯。


在取得Access Token時,SDK就可以取得比較多的資料了,詳細說明可以看Facebook Graph API這邊的說明,我是很簡單的取得個人資料內的name , uid 還有資料更新時間,語法與結果如下。
var accessToken = Session["AccessToken"].ToString();
Token = accessToken;
var client = new FacebookClient(accessToken);
dynamic result = client.Get("me", new { fields = "name,id,updated_time" });
string name = result.name;
string id = result.id;
string updatedTime = result.updated_time;
Label1.Text = name + " , " + id + " , " + updatedTime;
FBSDK05


基本上大概就是這樣了,與之前寫的FB C# SDK教學差異不大,不過我這邊是用JDK去取得認證資訊而已,下次再來寫server-side的取得認證方式。


Example

這邊是個小範例,因為沒地方放aspx,所以我就用JS SDK假裝一下XD,效果大概就是跟我的測試程式一樣,會對應login/auth狀態做出不同的事,但是我這邊只吐出 Access Token而已。





My test code

下面是我的測試程式,大概的功能就是進入頁面時去判斷這個使用者有沒有登入FB、有沒有認證此APP,沒的話就顯示Facebook CEO的名字,有的話就顯示使用者的名字、uid與更新時間,然後針對使用者移除APP許可的狀況,去catch (OAuthException - #190) Error invalidating access token: User ## has not authorized application ##. 這個錯誤。

然後我有多加一個button方便測試,基本上是多的,可以拿掉無所謂。

FBTest.aspx
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="FBTest.aspx.cs" Inherits="FBTest" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
<script src='http://www.google.com/jsapi' type='text/javascript'></script>
<script language='javascript' type='text/javascript'>    google.load("jquery", "1.7.1");</script>
<script type='text/javascript'>
    $(document).ready(function () {

        window.fbAsyncInit = function () {
            FB.init({
                appId: 'YOUR_APP_ID', // App ID
                status: true, // check login status
                cookie: true, // enable cookies to allow the server to access the session
                xfbml: true  // parse XFBML
            });
            <% if(Session["AccessToken"] == null){ %>
            try_fb();
            <% } %>
        };

        // Load the SDK Asynchronously
        (function (d, s, id) {
        var js, fjs = d.getElementsByTagName(s)[0];
        if (d.getElementById(id)) return;
        js = d.createElement(s); js.id = id;
        js.src = "//connect.facebook.net/en_US/all.js#xfbml=1&appId=YOUR_APP_ID";
        fjs.parentNode.insertBefore(js, fjs);
        } (document, 'script', 'facebook-jssdk'));
    });

    function try_fb() {
        FB.getLoginStatus(function (response) {
            if (response.status === 'connected') {
                var uid = response.authResponse.userID;
                var accessToken = response.authResponse.accessToken;

                var form = document.createElement("form");
                form.setAttribute("method", 'post');
                form.setAttribute("action", '/WebTest/FacebookLogin.ashx');

                var field = document.createElement("input");
                field.setAttribute("type", "hidden");
                field.setAttribute("name", 'accessToken');
                field.setAttribute("value", accessToken);
                form.appendChild(field);

                document.body.appendChild(form);
                form.submit();
                // TODO: Handle the access token

            } else if (response.status === 'not_authorized') {
                alert('not_authorized');
                // the user is logged in to Facebook, 
                // but has not authenticated your app
            } else {
                alert('not_logged');
                // the user isn't logged in to Facebook.
            }
        });
    }
</script>
</head>
<body>
<div id="fb-root"></div>
    <form id="form1" runat="server">
    <div>
        <asp:Button ID="Button1" runat="server" Text="Button" onclick="Button1_Click" /><br />
        <div class="fb-login-button" data-show-faces="true" data-width="400" data-max-rows="1"></div><br />
        <asp:Label ID="Label1" runat="server" Text=""></asp:Label><br />
    </div>
    </form>
</body>
</html>


FBTest.aspx.cs
using System;
using Facebook;

public partial class FBTest : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        CallFb();
    }
    protected void Button1_Click(object sender, EventArgs e)
    {
        CallFb();
    }

    private void CallFb()
    {
        if (Session["AccessToken"] != null)
        {
            var accessToken = Session["AccessToken"].ToString();
            var client = new FacebookClient(accessToken);
            try
            {
                dynamic result = client.Get("me", new { fields = "name,id,updated_time" });
                string name = result.name;
                string id = result.id;
                string updatedTime = result.updated_time;
                Label1.Text = name + " , " + id + " , " + updatedTime;
            }
            catch (Exception ex)
            {
                Session.Remove("AccessToken");
                Label1.Text = (ex.Message.IndexOf("OAuthException - #190") > -1) ? "User has not authorized" : "unknow error";
            }

        }
        else
        {
            var client = new FacebookClient();
            dynamic me = client.Get("zuck");
            string firstName = me.first_name;
            string lastName = me.last_name;
            Label1.Text = firstName + " , " + lastName;
        }
    }
}


FacebookLogin.ashx
<%@ WebHandler Language="C#" Class="FacebookLogin" %>
using System.Web.SessionState;
using System.Web;

public class FacebookLogin : IHttpHandler, IRequiresSessionState
{
    public void ProcessRequest (HttpContext context) {
        var accessToken = context.Request["accessToken"];
        context.Session["AccessToken"] = accessToken;
        context.Response.Redirect("FBTest.aspx");
    }
 
    public bool IsReusable {
        get {
            return false;
        }
    }
}


就這樣了,有問題再問我吧。


11 comments:

  1. 請問 var client = new FacebookClient(accessToken);
    這行出錯的原因是什麼

    執行後會出現

    編譯器錯誤訊息: CS0122: 'test0729.FacebookClient' 的保護層級導致無法對其進行存取

    ReplyDelete
  2. 你可以考這篇
    http://msdn.microsoft.com/zh-tw/library/ha94aebs.aspx

    ReplyDelete
  3. 不好意思,為什麼我的Label1 說不存在於目前內容中?
    因為我是個完全的新手,不好意思

    ReplyDelete
  4. Label1是在aspx內的控制項,你可能有改名或漏掉了,在範例裡面是有的。

    ReplyDelete
  5. 你好,第一次登入時擷取的資料是正確的
    關掉網頁重新開啟之後再用其他帳號登入
    取得的資料卻是第一次登入的

    請問問題出在哪裡呢

    ReplyDelete
  6. 有可能cookie還是吃舊的,你可以確認一下cookie內容看看

    ReplyDelete
  7. 你好,再次請教
    請問有實作logout的範例嗎
    謝謝

    ReplyDelete
  8. 能帮我看看吗??
    System.IO.FileLoadException: 未能加载文件或程序集“Facebook, Version=6.0.10.0, Culture=neutral, PublicKeyToken=58cb4f2111d1e6de”或它的某一个依赖项。找到的程序集清单定义与程序集引用不匹配。 (异常来自 HRESULT:0x80131040)

    ReplyDelete
  9. 你好,我也會有換帳號登入後顯示的資料仍是上一個使用者資料的問題,
    試過清除cookie但仍未解決。
    我有架server弄成網頁,換其他電腦登入所顯示的資料還是上一位使用者的,
    不知道該怎麼解決...

    ReplyDelete
  10. 不好意思我解決了XD
    我在抓acess_token那個if前面先加了remove移掉之前的
    謝謝你:)

    ReplyDelete