The Windows Azure Toolkits – Integrated ACS with iOS、Windows Phone、Android and Windows 8

随着Windows Azure Platform的发展,其支持的平台及语言也越来越多,移动设备自然也是重点之一,从去年开始,Windows Azure Toolkit for Windows Phone率先推出,
紧接着for iOS与Android版本也释出了。10个月后的今天,这些Windows Azure Toolkits随着Windows Azure Platform的发展逐步演进


The Windows Azure Toolkits – Integrated ACS with iOSWindows PhoneAndroid and Windows 8

/黄忠成

   随着Windows Azure Platform的发展,其支持的平台及语言也越来越多,移动设备自然也是重点之一,从去年开始,Windows Azure Toolkit for Windows Phone率先推出,

紧接着for iOS与Android版本也释出了。10个月后的今天,这些Windows Azure Toolkits随着Windows Azure Platform的发展逐步演进,除了修正了许多Bugs之外,

还加入了新的生力军:Windows Azure Toolkits for Windows 8。

图1

这些Toolkits的功能其实都差不多,主要是提供Storage  Services(Table、Queue、Blob)存取能力,还有与ACS(Access Control Service)整合的能力,

在新加入的Windows Azure Toolkit for Windows 8中还支持了Service Bus。

本文先将重点摆在ACS部分,其余的Storage Services部分可请读者先下载去年Tech Days的投影片及范例,未来我会抽空补充这一部分。

http://www.code6421.com/TechDays2011/Publish.zip

关于ACS

ACS是Windows Azure Platform所提供的服务之一,主要是负责使用者身份验证的动作,ACS本身是一个中介者,协助应用程序使用不同的Identity Providers(Facebook、

Google、Windows Live、ADFS、Open ID)来进行使用者验证的动作。

应用程序使用ACS来进行验证最大的好处在于可允许使用者透过主流的Identity Providers来登入你提供的服务,在现今这个社群导向的年代,使用移动设备的使用者应该

都至少拥有Facebook、Windows Live、Google、Yahoo中的一个账号,而ACS支持这些主流社群网站,也就是说你的服务可以不必要求使用者注册,也不必保存使用者的账号密码,

透过ACS,应用程序只需要取得需要的数据即可(like Email),也因为不须注册就可以使用服务,使用者可以跳过繁琐的注册动作直接使用服务,这将提升使用者试用你的服务的意愿。

另外,ACS也可作为一种Single Sign-On(单一签入)的解决方案,不过这不在本文的讨论范围。

设定ACS

首先,你得先有Windows Azure账号才能使用ACS,需要的请至以下网址申请测试账号(免费的哦)

http://www.windowsazure.com/zh-tw/

  有了账号后,就可到Windows Azure Portal中启用ACS。

图2

点选上方的新增来添加一个服务命名空间。

图3

图4

于此填入命名空间的名称及选择国家/地区,完成后等待此服务空间启用。

图5

本文的范例是使用Facebook作为验证的Identity Provider,所以需要至http://developers.facebook.com/来申请建立一个应用程序(注意,

你必须拥有Facebook账号及通过手机简讯验证才能建立Facebook应用程序)。

图6

注意App Domains及Website with Facebook Login字段,此处需填入你的ACS 网站网址,通常是<服务命名空间>.accesscontrol.windows.net。

接着使用浏览器开启https://<服务命名空间>.accesscontrol.windows.net,便可看到类似下图的画面。

图7

点选识别提供者,接着再点选新增。

图8

此处选择Facebook应用程序。

图9

这里需输入一个显示名称,此处使用Facebook,接着要填入之前建立Facebook的应用程序识别码及密码(应用程序密钥)。

接着点选左方的信赖凭证者应用程序来新增一个应用程序。

图7

图10

此处需输入显示名称及领域,领域必须是一个URL,可使用localhost(这只是一个识别名称),接着选择权杖格式为SWT,下方会出现要求产生凭证的部分。

图11

此处只须点选产生后按下保存即可,这样就算完成ACS整合Facebook Identity Provider的部分了,接下来就是开始透过应用程序连接ACS来进行与Facebook的整合验证。

Windows Phone 7

  透过Windows Azure Toolkit for Windows Phone,应用程序可直接使用其提供的Library&Controls来存取Storage Services及ACS,读者可以由以下网址下载。

http://watwp.codeplex.com/

要连接ACS,只需建立一个新的Windows Phone Application项目,并将WAToolkitForWP7SamplesWP7.1LibrariesSL.Phone.Federation目录下的.csproj加到方案中,

再由Windows Phone Application项目加入对SL.Phone.Federation的参考即可。

图12

接着修改App.xaml,加入以下内容。

图13

建立一个新的页面(SignInControl),并加入Azure Tookit所提供的Login控件。

图14

Realm需对应信赖者凭证应用程序中的领域设定。

图10

ServiceNamespace则对应ACS命名空间(我们的ACS网址是demoacs3.accesscontrol.windows.net,所以此处为demoacs3)。

修改主页面(MainPage.xaml)。

……………………
using SL.Phone.Federation.Utilities;

namespace DemoACS
{
    public partial class MainPage : PhoneApplicationPage
    {
        RequestSecurityTokenResponseStore _rstrStore = null;

        // Constructor
        public MainPage()
        {
            InitializeComponent();
            _rstrStore = (RequestSecurityTokenResponseStore)App.Current.Resources["rstrStore"];
        }

        private void PhoneApplicationPage_Loaded(object sender, RoutedEventArgs e)
        {
            if (!_rstrStore.ContainsValidRequestSecurityTokenResponse())
            {
                NavigationService.Navigate(new Uri("/SignInControl.xaml", UriKind.Relative));
            }
            else
            {
                textBlock1.Text = "thanks for your login";
            }

        }
    }
}

完成后测试,将可看到以下的画面。

图15

图16

输入账密后,正确的话就可看到图17。

图17

Android

   Windows Azure Toolkit for Android可由以下网址下载。

https://github.com/WindowsAzure-Toolkits/wa-toolkit-android

下载后解压,透过Eclipse将WAToolkitForAndroidlibraryaccesscontrol Import到现在使用的Workspace,接着建立一个Android Application Project,Build SDK请选择Android 2.2,

修改此Project的Properties中Android页面设定,将accesscontrol这个Project新增为Library。

图18

修改Project中的AndroidManifest.xml如下。



    
    

    
        
            
                

                
            
        
        
            
                

                
            
        
        
            
                

                
            
        
        
                
		
    


加入Resource Strings。

/res/values/strings.xml



    DemoACSB
    Hello world!
    Settings
    MainActivity
    demoacs3
    http://www.code6421.com
    9FhZFekCsz1YsWpjdiQxJAbh2AhW3WS/aQ----blx3g=
    SuccessLoginActivity
    UnsuccessLogin


注意cloud_ready_acs_symmetric_key字段,这里要放置ACS中的凭证金钥,另外cloud_ready_acs_realm则是对应信赖者凭证应用程序中的领域设定。

加入两个Activity。

SuccessLoginActivity.java

package com.example.demoacsb;

import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.view.MenuItem;
import android.support.v4.app.NavUtils;

public class SuccessLoginActivity extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_success_login);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_success_login, menu);
        return true;
    }    
}

/res/layout/activity_success_login.xml



    

UnsuccessLogin.java

package com.example.demoacsb;

import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.view.MenuItem;
import android.support.v4.app.NavUtils;

public class UnsuccessLogin extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_unsuccess_login);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_unsuccess_login, menu);
        return true;
    }    
}

/res/layout/activity_unsuccess_login.xml



    

在主要的Activity对应的Layout加入一个Button。

res/layout/activity_main.xml



    

键入以下的程序。

MainActivity.java

package com.example.demoacsb;

import android.os.Bundle;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Intent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.support.v4.app.NavUtils;
import com.microsoft.samples.windowsazure.android.accesscontrol.core.IdentityProvidersRepository;
import com.microsoft.samples.windowsazure.android.accesscontrol.login.AccessControlLoginActivity;
import com.microsoft.samples.windowsazure.android.accesscontrol.login.AccessControlLoginContext;
import com.microsoft.samples.windowsazure.android.accesscontrol.swt.SimpleWebTokenHandler; 

public class MainActivity extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    	Button manualLoginButton = (Button) findViewById(R.id.button1);    	
		manualLoginButton.setOnClickListener(new OnClickListener() {
			public void onClick(View v) {	
				doManualLogin();
			}
		}); 
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_main, menu);
        
        return true;
    }
    
    private void doManualLogin() {
    	if (!validateACSConfiguration()) return;
    	
    	String acsNamespace = getString(R.string.cloud_ready_acs_namespace);
    	String acsRealm = getString(R.string.cloud_ready_acs_realm);
    	String acsSymmKey = getString(R.string.cloud_ready_acs_symmetric_key);
    	
		Intent intent = new Intent(this, AccessControlLoginActivity.class);

		AccessControlLoginContext loginContext = new AccessControlLoginContext();
    	loginContext.IdentityProviderRepository = new IdentityProvidersRepository(
    			String.format(
    					"https://%s.accesscontrol.windows.net/v2/metadata/IdentityProviders.js?protocol=javascriptnotify&realm=%s&version=1.0",
    					acsNamespace, acsRealm));    	
    	loginContext.AccessTokenHandler = new SimpleWebTokenHandler(acsRealm, acsSymmKey);
    	loginContext.SuccessLoginActivity = SuccessLoginActivity.class;
    	loginContext.ErrorLoginActivity = com.example.demoacsb.UnsuccessLogin.class;
    	intent.putExtra(AccessControlLoginActivity.AccessControlLoginContextKey, loginContext);	
	
    	startActivity(intent);    
    } 
    
    private boolean validateACSConfiguration() {
    	String acsNamespace = getString(R.string.cloud_ready_acs_namespace);
    	String acsRealm = getString(R.string.cloud_ready_acs_realm);
    	String acsSymmKey = getString(R.string.cloud_ready_acs_symmetric_key);

    	if (isValidConfigurationValue(acsNamespace) && isValidConfigurationValue(acsRealm) && isValidConfigurationValue(acsSymmKey))
    		return true;
    	
		AlertDialog dialog = new AlertDialog.Builder(this).create();
		dialog.setTitle("Configuration Error");
		dialog.setCanceledOnTouchOutside(true);
		dialog.setCancelable(true);
		dialog.setMessage("Please review your ACS configuration and try again");
		dialog.show();

		return false;
    } 
    
    private boolean isValidConfigurationValue(String value) {
		return !(value.startsWith("{") || value.trim() == "");
	}     
}

完成后执行前,请特别注意要使用Android 2.2的模拟器,因为2.2以上的模拟器在WebView对象处理上有Bugs,无法处理Scripting Injection部分。

图19

图20

图21

图22

iOS

Windows Azure Toolkit for iOS 可由以下网址下载。

https://github.com/WindowsAzure-Toolkits/wa-toolkit-ios/

下载后须先透过XCode 开启WAToolkitForIOSlibrarywatoolkitios-lib.xcodeproj这个Project,然后进行编译后取得.a及对应的.h文件。

接着透过XCode建立一个Single View的Project,先取名为DemoACS,将先前取得的.h文件复制到此Project目录下的watoolkitios-lib目录中,并将.a文件新增进此项目中,

接着修改DemoACS-Prefix.pch加入import。

DeniACS-Prefix.pch

//
// Prefix header for all source files of the 'DemoACS' target in the 'DemoACS' project
//

#import 

#ifndef __IPHONE_5_0
#warning "This project uses features only available in iOS SDK 5.0 and later."
#endif

#ifdef __OBJC__
    #import 
    #import 
    #import "watoolkitios-lib/WAToolkit.h"
#endif

然后修改MainStoryboard_iPhone.storyboard加入一个Button,并修改DemoACSViewController.h加入一个method。

DemoACSViewController.h

//
//  DemoACSViewController.h
//  DemoACS
//
//  Created by  on 12/7/6.
//  Copyright 2012年 __MyCompanyName__. All rights reserved.
//

#import 

@interface DemoACSViewController : UIViewController {}
- (IBAction)loginAction:(id)sender;
@end

紧接着修改DemoACSViewController.m。

//
//  DemoACSViewController.m
//  DemoACS
//
//  Created by  on 12/7/6.
//  Copyright 2012年 __MyCompanyName__. All rights reserved.
//

#import "DemoACSViewController.h"

NSString * const ACSNamespace = @"demoacs3";
NSString * const ACSRealm = @"http://www.code6421.com";
NSString * const NameIdentifierClaim = @"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier";
NSString * const AccessTokenClaim = @"http://www.facebook.com/claims/AccessToken";


@implementation DemoACSViewController- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Release any cached data, images, etc that aren't in use.
}

#pragma mark - View lifecycle

- (void)viewDidLoad
{
    [super viewDidLoad];
	// Do any additional setup after loading the view, typically from a nib.
}

- (void)viewDidUnload
{    [super viewDidUnload];
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
}

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
}

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
}

- (void)viewWillDisappear:(BOOL)animated
{
	[super viewWillDisappear:animated];
}

- (void)viewDidDisappear:(BOOL)animated
{
	[super viewDidDisappear:animated];
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    // Return YES for supported orientations
    if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
        return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
    } else {
        return YES;
    }
}
- (IBAction)loginAction:(id)sender {
    WACloudAccessControlClient *acsClient = [WACloudAccessControlClient accessControlClientForNamespace:ACSNamespace realm:ACSRealm];
    [acsClient showInViewController:self allowsClose:NO withCompletionHandler:^(BOOL authenticated) { 
        if (!authenticated) { 
            UIAlertView *dialog = [[UIAlertView alloc] initWithTitle:@"Info" message:@"Login Fail" delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil];
            [dialog show];
        } else {
            UIAlertView *dialog = [[UIAlertView alloc] initWithTitle:@"Info" message:@"Login Successed" delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil];
            [dialog show];         
        }
    }];
    
}
    
@end

最后透过Interface Builder把loginAction与画面上的Button建立系结,然后修改此Project的Build Settings中的Other Link Flags为  -ObjC -all_load 即可。

图23

图24

图25

图26

Windows 8

  Windows Azure Toolkit for Windows 8可由下列网址下载。

http://watwindows8.codeplex.com/

不过此版本目前仅支持Consumer Preview及Visual Studio 2012 Beta,要动点手脚才能正常使用,另外,其ACS部分仅提供使用protocol为WS-Federation模式的验证,跟我们前面使用

javascriptnotify为protocol的方式不同,前者目前需要一个收取claims的网站,因此我修改了Toolkit中的ACS部分,使其支持javascriptnotify验证模式(载点在文末)。

 请先下载我所提供的ACS Library,然后建立一个Windows Metro style App 项目,加入两个类。

LoginEventArgs.cs

// ----------------------------------------------------------------------------------
// Microsoft Developer & Platform Evangelism
// 
// Copyright (c) Microsoft Corporation. All rights reserved.
// 
// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, 
// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES 
// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
// ----------------------------------------------------------------------------------
// The example companies, organizations, products, domain names,
// e-mail addresses, logos, people, places, and events depicted
// herein are fictitious.  No association with any real company,
// organization, product, domain name, email address, logo, person,
// places, or events is intended or should be inferred.
// ----------------------------------------------------------------------------------

namespace Windows8.CSharp.Identity.AccessControl
{
    using System;
    using Windows8.Identity.AccessControl;

    public class LoginEventArgs : EventArgs
    {
        public LoginEventArgs(IAuthenticationResult result)
        {
            this.Result = result;
        }

        public IAuthenticationResult Result { get; private set; }
    }
}

ProvidersInfoEventArgs.cs

// ----------------------------------------------------------------------------------
// Microsoft Developer & Platform Evangelism
// 
// Copyright (c) Microsoft Corporation. All rights reserved.
// 
// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, 
// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES 
// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
// ----------------------------------------------------------------------------------
// The example companies, organizations, products, domain names,
// e-mail addresses, logos, people, places, and events depicted
// herein are fictitious.  No association with any real company,
// organization, product, domain name, email address, logo, person,
// places, or events is intended or should be inferred.
// ----------------------------------------------------------------------------------

namespace Windows8.CSharp.Identity.AccessControl
{
    using System;
    using Windows8.Identity.AccessControl;

    public class ProvidersInfoEventArgs : EventArgs
    {
        public ProvidersInfoEventArgs(IIdentityProvidersInfo info)
        {
            this.ProvidersInfo = info;
        }

        public IIdentityProvidersInfo ProvidersInfo { get; private set; }
    }
}

接着加入一个UserControl,取名为ACSLogin。

ACSLogin.xaml



    
        
        
    

    
        
        
        
            
                
                    
                
            
        
    

ACSLogin.xaml.cs

// ----------------------------------------------------------------------------------
// Microsoft Developer & Platform Evangelism
// 
// Copyright (c) Microsoft Corporation. All rights reserved.
// 
// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, 
// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES 
// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
// ----------------------------------------------------------------------------------
// The example companies, organizations, products, domain names,
// e-mail addresses, logos, people, places, and events depicted
// herein are fictitious.  No association with any real company,
// organization, product, domain name, email address, logo, person,
// places, or events is intended or should be inferred.
// ----------------------------------------------------------------------------------

namespace Windows8.CSharp.Identity.AccessControl
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    using Windows.Foundation;
    using Windows.UI.Core;
    using Windows.UI.Xaml;
    using Windows.UI.Xaml.Controls;
    using Windows.UI.Xaml.Data;
    using Windows8.Identity.AccessControl;

    public sealed partial class ACSLogin
    {
        public static readonly DependencyProperty ACSNamespaceProperty = DependencyProperty.Register("AccessControlNamespace", typeof(string), typeof(ACSLogin), new PropertyMetadata(string.Empty));
        public static readonly DependencyProperty RealmProperty = DependencyProperty.Register("Realm", typeof(string), typeof(ACSLogin), new PropertyMetadata(string.Empty));
        public static readonly DependencyProperty BouncerReplyUrlProperty = DependencyProperty.Register("BouncerReplyUrl", typeof(string), typeof(ACSLogin), new PropertyMetadata(string.Empty));
        public static readonly DependencyProperty BouncerEndUrlProperty = DependencyProperty.Register("BouncerEndUrl", typeof(string), typeof(ACSLogin), new PropertyMetadata(string.Empty));

        private AccessControlManager accessControlManager;

        public ACSLogin()
        {
            InitializeComponent();

            accessControlManager = new AccessControlManager();
        }

        public string AccessControlNamespace
        {
            get
            {
                return (string)GetValue(ACSNamespaceProperty);
            }
            set
            {
                SetValue(ACSNamespaceProperty, value);
            }
        }

        public string Realm
        {
            get
            {
                return (string)GetValue(RealmProperty);
            }
            set
            {
                SetValue(RealmProperty, value);
            }
        }

        public string BouncerReplyUrl
        {
            get
            {
                return (string)GetValue(BouncerReplyUrlProperty);
            }
            set
            {
                SetValue(BouncerReplyUrlProperty, value);
            }
        }

        public string BouncerEndUrl
        {
            get
            {
                return (string)GetValue(BouncerEndUrlProperty);
            }
            set
            {
                SetValue(BouncerEndUrlProperty, value);
            }
        }

        public void Login()
        {
            this.ConfigureAccessControlManager();

            this.ProgressBar.Visibility = Windows.UI.Xaml.Visibility.Visible;
            var getOperation = accessControlManager.GetIdentityProviders(true);

            getOperation.Completed = ((info, status) =>
            {
                var ipInfo = info.GetResults();
                this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
                    () =>
                    {
                        if (OnProviderListRetrieved != null) OnProviderListRetrieved(this, new ProvidersInfoEventArgs(ipInfo));

                        foreach (var ip in ipInfo.IdentityProviderList) 
                            IdentityProviderList.Items.Add(ip);

                        this.IdentityProviderList.SelectionChanged += new SelectionChangedEventHandler(IdentityProviderListItemSelected);
                        this.ProgressBar.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
                        this.IdentityProviderList.Visibility = Windows.UI.Xaml.Visibility.Visible;
                    });
            });
        }

        public event EventHandler OnLogin;

        public event EventHandler OnProviderListRetrieved;

        private void IdentityProviderListItemSelected(object sender, SelectionChangedEventArgs e)
        {
            var item = this.IdentityProviderList.SelectedItem as IIdentityProvider;
            IdentityProviderList.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
            var loginOperation = item.Login(root, true);

            loginOperation.Completed = ((info, status) =>
            {
                if (OnLogin != null) OnLogin(this, new LoginEventArgs(info.GetResults()));
            });
        }

        private void ConfigureAccessControlManager()
        {
            this.accessControlManager.AccessControlNamespace = this.AccessControlNamespace;
            this.accessControlManager.Realm = this.Realm;
            this.accessControlManager.BouncerReplyUrl = this.BouncerReplyUrl;
            this.accessControlManager.BouncerEndUrl = this.BouncerEndUrl;
        }
    }
}

最后在MainPage中添加ACSLogin控件即可。



    
        
    

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Popups;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
using Windows8.CSharp.Identity.AccessControl;

// The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=234238

namespace App13
{
    /// 
    /// An empty page that can be used on its own or navigated to within a Frame.
    /// 
    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();
            login.OnLogin += login_OnLogin;
        }

        void login_OnLogin(object sender, LoginEventArgs e)
        {
            new MessageDialog(e.Result.Token).ShowAsync();
        }

        /// 
        /// Invoked when this page is about to be displayed in a Frame.
        /// 
        /// 
Event data that describes how this page was reached.  The Parameter
        /// property is typically used to configure the page.
        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
        }

        private void MainPage_Loaded_1(object sender, RoutedEventArgs e)
        {
            login.Login();
        }
    }
}

图27

图28

图29

就这样,打完收工,下次有机会再来聊聊Storage Access部分。

ACS Library for Windows 8

http://www.code6421.com/BlogPics/W8ACS.zip