如何在Xamarin.Forms中登录Facebook

46
我想创建一个Xamarin.Forms项目,目标平台为iOS、Android和Windows Phone。
我的应用程序需要使用Facebook验证用户。
我应该独立为每个平台实现登录,还是使用手动流程? https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow/v2.0 我更喜欢有一个登录流程的单一实现,并在所有平台上使用它。
如何获得Facebook登录流程的单一实现?
7个回答

105

更新 (10/24/17): 尽管几年前使用这种方法是可以的,但我现在强烈建议使用本机UI进行身份验证,而不是在此处显示的Webview方法。Auth0是通过使用各种身份提供程序为您的应用程序实现本机UI登录的好方法: https://auth0.com/docs/quickstart/native/xamarin

编辑: 我最终在Github上放了一个 示例

我在Xamarin论坛上发布了一篇回答. 在这里重复一下。

让我们从应用程序的核心开始,即Xamarin.Forms PCL项目。你的App类将看起来像这样:

namespace OAuth2Demo.XForms
{
    public class App
    {
        static NavigationPage _NavPage;
        
        public static Page GetMainPage ()
        {
            var profilePage = new ProfilePage();

            _NavPage = new NavigationPage(profilePage);

            return _NavPage;
        }

        public static bool IsLoggedIn {
            get { return !string.IsNullOrWhiteSpace(_Token); }
        }

        static string _Token;
        public static string Token {
            get { return _Token; }
        }

        public static void SaveToken(string token)
        {
            _Token = token;
        }
        
        public static Action SuccessfulLoginAction
        {
            get {
                return new Action (() => {
                    _NavPage.Navigation.PopModalAsync();
                });
            }
        }
    }
}

首先要注意的是GetMainPage()方法。这会告诉应用程序在启动时应加载哪个屏幕。

我们还有一个简单的属性和方法,用于存储从授权服务返回的Token,以及一个简单的IsLoggedIn属性。

还有一个Action属性;我把它放在这里是为了让平台实现有一种方法来执行Xamarin.Forms导航操作。稍后再详细介绍。

您还会在IDE中看到一些红色,因为我们尚未创建ProfilePage类。所以,让我们来创建它吧。

Xamarin.Forms PCL项目中创建一个非常简单的ProfilePage类。我们甚至不需要对其进行任何花哨的处理,因为那将取决于您的具体需求。为了在此示例中简单起见,它将只包含一个标签:

namespace OAuth2Demo.XForms
{
    public class ProfilePage : BaseContentPage
    {
        public ProfilePage ()
        {
            Content = new Label () {
                Text = "Profile Page", 
                VerticalOptions = LayoutOptions.CenterAndExpand,
                HorizontalOptions = LayoutOptions.CenterAndExpand, 
            };
        }
    }
}

再次提醒,您的IDE可能会出现红色警报,因为我们好像缺少了BaseContentPage类。 BaseContentPage类唯一的目的是确保在用户登录之前无法显示应用程序的任何屏幕。(在此简化演示中,我们只是将用户信息保存到内存中,因此每次运行应用程序时都需要重新登录。在真实的应用程序中,您将把已认证的用户信息存储到设备的密钥链中,这将消除每次启动应用程序时登录的需要。)

Xamarin.Forms PCL项目中创建一个BaseContentPage类:

namespace OAuth2Demo.XForms
{
    public class BaseContentPage : ContentPage
    {
        protected override void OnAppearing ()
        {
            base.OnAppearing ();

            if (!App.IsLoggedIn) {
                Navigation.PushModalAsync(new LoginPage());
            }
        }
    }
}

这里有几个有趣的事情:

  1. 我们正在重写OnAppearing()方法,这类似于iOS UIViewController中的ViewWillAppear方法。您可以在此处执行任何代码,以便在屏幕出现之前立即运行。

  2. 我们在此方法中唯一要做的事情是检查用户是否已登录。如果他们没有登录,则执行模态推送到名为LoginPage的类。如果您不熟悉模态的概念,它只是一个视图,将用户从正常应用程序流程中取出,以执行某些特殊任务;在我们的情况下,进行登录。

因此,让我们在Xamarin.Forms PCL项目中创建LoginPage类:

namespace OAuth2Demo.XForms
{
    public class LoginPage : ContentPage
    {

    }
}

等等……为什么这个类没有主体???

由于我们正在使用Xamatin.Auth组件(该组件负责构建和呈现与提供的OAuth2信息兼容的Web视图),因此实际上不希望在我们的LoginPage类中有任何实现。我知道这看起来很奇怪,但请耐心听我解释。

iOS的LoginPageRenderer

到目前为止,我们一直在Xamarin.Forms PCL项目中工作。但现在我们需要在iOS项目中提供我们的LoginPage的平台特定的实现。这就是Renderer的概念发挥作用的地方。

在Xamarin.Forms中,当您想要提供平台特定的屏幕和控件(即不从Xamarin.Forms PCL项目中的抽象页面派生其内容的屏幕)时,可以使用Renderers

在您的iOS平台项目中创建一个LoginPageRenderer类:

[assembly: ExportRenderer (typeof (LoginPage), typeof (LoginPageRenderer))]

namespace OAuth2Demo.XForms.iOS
{
    public class LoginPageRenderer : PageRenderer
    {
        public override void ViewDidAppear (bool animated)
        {
            base.ViewDidAppear (animated);

            var auth = new OAuth2Authenticator (
                clientId: "", // your OAuth2 client id
                scope: "", // the scopes for the particular API you're accessing, delimited by "+" symbols
                authorizeUrl: new Uri (""), // the auth URL for the service
                redirectUrl: new Uri ("")); // the redirect URL for the service

            auth.Completed += (sender, eventArgs) => {
            // We presented the UI, so it's up to us to dimiss it on iOS.
            App.SuccessfulLoginAction.Invoke();

            if (eventArgs.IsAuthenticated) {
                // Use eventArgs.Account to do wonderful things
                App.SaveToken(eventArgs.Account.Properties["access_token"]);
            } else {
                // The user cancelled
            }
        };

        PresentViewController (auth.GetUI (), true, null);
            }
        }
    }
}

需要注意以下几点:

  1. 在命名空间声明前面的顶部,使用了Xamarin.Forms的DependencyService[assembly: ExportRenderer (typeof (LoginPage), typeof (LoginPageRenderer))]。这并不是最优美的方式因为它不是IoC/DI,但无论如何...它可以工作。这是将我们的LoginPageRenderer“映射”到LoginPage的机制。

  2. 这是我们实际使用Xamarin.Auth组件的类。这就是OAuth2Authenticator引用的来源。

  3. 一旦登录成功,我们通过App.SuccessfulLoginAction.Invoke();启动Xamarin.Forms导航。这将带我们回到ProfilePage

  4. 由于我们在使用iOS平台,所以我们所有的逻辑都在ViewDidAppear()方法中进行。

Android的LoginPageRenderer

在您的Android平台项目中创建一个LoginPageRenderer类。(请注意,您正在创建的类名与iOS项目中的名称相同,但在Android项目中,PageRenderer继承自Android类而不是iOS类。)

[assembly: ExportRenderer (typeof (LoginPage), typeof (LoginPageRenderer))]

namespace OAuth2Demo.XForms.Android
{
    public class LoginPageRenderer : PageRenderer
    {
        protected override void OnModelChanged (VisualElement oldModel, VisualElement newModel)
        {
            base.OnModelChanged (oldModel, newModel);

            // this is a ViewGroup - so should be able to load an AXML file and FindView<>
            var activity = this.Context as Activity;

            var auth = new OAuth2Authenticator (
                clientId: "", // your OAuth2 client id
                scope: "", // the scopes for the particular API you're accessing, delimited by "+" symbols
                authorizeUrl: new Uri (""), // the auth URL for the service
                redirectUrl: new Uri ("")); // the redirect URL for the service

            auth.Completed += (sender, eventArgs) => {
            if (eventArgs.IsAuthenticated) {
                App.SuccessfulLoginAction.Invoke();
                // Use eventArgs.Account to do wonderful things
                App.SaveToken(eventArgs.Account.Properties["access_token"]);
            } else {
                // The user cancelled
            }
        };

        activity.StartActivity (auth.GetUI(activity));
        }
    }
}

让我们再来看一些有趣的事情:

  1. [assembly: ExportRenderer(typeof(LoginPage), typeof(LoginPageRenderer))] 这一行代码在命名空间声明之前(非常重要)使用了Xamarin.Forms DependencyService。这与iOS版本的 LoginPageRenderer 没有区别。

  2. 同样,这是我们实际使用 Xamarin.Auth 组件的地方,因此 OAuth2Authenticator 的引用也是从那里来的。

  3. 就像 iOS 版本一样,一旦登录成功,我们通过 App.SuccessfulLoginAction.Invoke(); 触发了一个 Xamarin.Forms 导航,使我们返回到 ProfilePage

  4. 与 iOS 版本不同的是,我们将所有逻辑都放在 OnModelChanged() 方法中而非 ViewDidAppear() 中。

iOS 版本如下:

Xamarin.Auth with Xamarin.Forms iOS example

...而 Android 版本如下:

Xamarin.Auth with Xamarin.Forms Android example

更新: 我在我的博客上提供了一个详细的示例:http://www.joesauve.com/using-xamarin-auth-with-xamarin-forms/


6
你是一个非常了不起的人。如果你有机会来到密歇根州格兰德拉皮兹地区,请让我请你喝一杯啤酒。twitter.com/kingdango - kingdango
1
我一定会接受你邀请的美味饮料!很高兴能帮到你。 - NovaJoe
2
我并不总是在 Stack Overflow 上回答问题,但当我这么做时,它们都很有趣。 ;) - NovaJoe
2
@NovaJoe,问题在于我正在使用Xamarin的另一个.dll文件,你正在使用v1.0.0.0版本,而我正在使用v1.2.1.0版本,其中有一个名为OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.Page> e);的不同函数,显然接收不同的参数。 - Mario Galván
在您的Facebook应用程序中,搜索“有效的OAuth URI重定向”,并添加http://www.facebook.com/connect/login_success.html。 - rafaelbpa
显示剩余15条评论

20
您可以使用 Xamarin.SocialXamarin.Auth 来实现此功能。它允许在任何平台上使用相同的API。
目前,这些库还不是PCL,但您仍然可以从共享资产项目中使用它们,或者将您需要的API封装在接口中,并使用DependencyService或任何其他DI容器进行注入。

注意:Xamarin.Social组件支持iOS和Android,不支持Windows Phone。 - Iducool

9

机制的工作方式如下:您从您的活动开始(我们称之为“主要活动”),然后单击按钮打开一个新的活动,即“Facebook活动”,当获取Facebook令牌时,您需要返回到您的“主要活动”。 如果您到达主要活动的OnActivityResult,则表示您正在正确执行流程。 您在哪一行代码中获得了空引用? - IdoT
非常酷。如果Instagram也有本地组件就好了!但现在,我们只能使用webview / auth url或实现自定义本地控件。 - NovaJoe
@IdoT 是的,我使用了你的代码,它运行得很好。现在打算尝试更新它以使用最新的Facebook SDK - 祝我好运!;-) - Tomás
伙计们,这段代码在iOS上运行得很好,但在Android渲染器上运行时,当代码到达activity.StartActivity(auth.GetUI(activity));处时,我收到了一个“Android.Content.ActivityNotFoundException已被抛出:无法在您的AndroidManifest.xml中找到显式活动类{com.example/md550f8a10a740eb4fb6376baab8337ce22.WebAuthenticatorActivity}?”的错误信息。我错过了什么吗? - rafaelbpa
1
@rafaelbpa - Facebook更改了他们的Android SDK并破坏了我的解决方案,有人升级了解决方案以适应新的SDK-https://github.com/mikescandy/XamarinFormsNativeFacebook请注意我还没有测试过它。 - IdoT
显示剩余7条评论

5

iOS 8:对于那些正在使用@NovaJoe代码且在视图上卡住的人,请添加以下代码以解决问题:

 bool hasShown;

    public override void ViewDidAppear(bool animated)
    {
        if (!hasShown)
        {
            hasShown = true;

            // the rest of @novaJoe code
        }

    }

干得好!这将帮助很多人。在我的Github存储库上发出拉取请求。或者我也可能自己解决它。 - NovaJoe
大家好,有人尝试使用 Facebook OAuth API 吗?我收到了一个 Facebook 安全警告。 - Illuminati
@Illuminati 安全警告是关于重定向URL的,请确保您重定向到一个有效的URL。 - Alexandre
@Alexandre,谢谢您帮我解决了安全错误问题,但我的视图仍然打开。我尝试了您的解决方案和网上的一些其他建议来执行PopModel/PopRootModel,但仍然没有成功。您有什么想法吗? - Illuminati
我曾经遇到过同样的问题,我的屏幕上一直弹出一个安全消息,解决方法是重定向到一个有效的URL(不带重定向!)。 - Alexandre
@Alexandre 谢谢,这非常有帮助。您介意讲一下您是如何找到这些信息的吗?在找到您的解决方案之前,我在 iOS 9 上遇到了“尝试关闭模态视图控制器,但其视图当前未显示”的错误,让我感到非常头疼整个下午。这里的潜在问题是什么? - Wade Tandy

3

@MongoBoy 谢谢你提供的例子,它告诉我这个网址的域名没有包含在应用程序的域名中,我该如何解决这个问题? - Loai

0

Android的PageRenderer的正确实现方式是:

using System;
using Android.App;
using Android.Content;
using OAuth2Demo.XForms.Android;
using Xamarin.Auth;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using XamarinAuth;

[assembly: ExportRenderer(typeof(LoginPage), typeof(LoginPageRenderer))]

namespace OAuth2Demo.XForms.Android
{
    public class LoginPageRenderer : PageRenderer
    {
        public LoginPageRenderer(Context context) : base(context) { }

        protected override void OnElementChanged(ElementChangedEventArgs<Page> e)
        {
            base.OnElementChanged(e);

            // this is a ViewGroup - so should be able to load an AXML file and FindView<>
            var activity = this.Context as Activity;

            var auth = new OAuth2Authenticator(
                clientId: "<Constants.clientId>", // your OAuth2 client id
                scope: "<Constants.scope>", // the scopes for the particular API you're accessing, delimited by "+" symbols
                authorizeUrl: new Uri("<Constants.authorizeUrl>"), // the auth URL for the service
                redirectUrl: new Uri("<Constants.redirectUrl>")); // the redirect URL for the service

            auth.Completed += (sender, eventArgs) =>
            {
                if (eventArgs.IsAuthenticated)
                {
                    App.SuccessfulLoginAction.Invoke();
                    // Use eventArgs.Account to do wonderful things
                    App.SaveToken(eventArgs.Account.Properties["access_token"]);
                }
                else
                {
                    // The user cancelled
                }
            };

            activity.StartActivity(auth.GetUI(activity));
        }
    }
}

“Correct” 一词的含义是什么?这只有一种方法吗? - Nico Haase

0

在@NovaJoe的代码中又有一个补充,在iOS8上使用Facebook,您需要修改Renderer类如下以在成功验证后关闭视图。

auth.Completed += (sender, eventArgs) => {
            // We presented the UI, so it's up to us to dimiss it on iOS.

/* 添加这行很重要 */

            DismissViewController (true, null);

/* */
            if (eventArgs.IsAuthenticated) {
                App.Instance.SuccessfulLoginAction.Invoke ();

                // Use eventArgs.Account to do wonderful things
                App.Instance.SaveToken (eventArgs.Account.Properties ["access_token"]);


            } else {
                // The user cancelled
            }
        };

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接