一連寫了四篇筆記,介紹了Live SDK,也實際玩過取得使用者Live ID身分、連上SkyDrive等把戲:
埋了這些哽,其實背後我最想做的,是從WP7程式連上SkyDrive!
手機基於儲存空間的限制,以及跨裝置/平台共享的需求,非常需要結合網路儲存空間做為後盾,而WP7配上微軟體系的SkyDrive,感覺上是很棒的組合。
Live SDK有提供WP7專用的元件庫,下載安裝Live SDK後,可在WP7專案參照中找到Microsoft.Live及Microsoft.Live.Controls,學習中心有篇WP7整合Live SDK教學,是很好的入門。
Live SDK提供SignInButton控制項,表面上是顆登入登出鈕,背地則幫我們處理掉在App內嵌瀏覽器載入Live帳號登入網頁以及同意網頁等繁瑣細節,是整合Live SDK最簡便的做法。如果在Visual Studio工具箱沒看到它,可自行找到Microsoft.Live.Controls.dll加入工具箱(如下圖)。
不過,有一點要注意,由於在App運作時,不像在ASP.NET範例中會有Callback.aspx負責接入Authorization Code再呼叫API取得Access Token。請參考註冊App一文第3點提及的做法,申請Client ID時,Redirect Domain請留白,然後勾選"Mobile client app"為"Yes",如此,此Client ID即可使用httqs://oauth.live.com/desktop做為Redirect Page,並直接取得Access Token,如此在行動裝置上即可獨立運作。
我想試做的玩具是一個SkyDrive簡易瀏覽器,功能跟上一篇的ASP.NET版差不多,取得使用者對wl.skydrive範圍的同意權後,列出使用者SkyDrive下的目錄,點選目錄可展開列出其下的子項目,若是檔案則可進行檢視,不過WP7能支援的檔案格式比PC少,我只先選擇對圖片(jpg, png)及影片(wmv, mp3, mp4)提供檢視功能。
MainPage.xaml放入一個SignInButton及ListBox:
<Grid x:Name="LayoutRoot" Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
<TextBlock x:Name="ApplicationTitle" Text="Mini SkyDrive Explorer"
Style="{StaticResource PhoneTextNormalStyle}"/>
<my:SignInButton x:Name="btnSignIn" Branding="Skydrive"
SessionChanged="btnSignIn_SessionChanged" CientId="0000000016888888"
RedirectUri="https://oauth.live.com/desktop"
Scopes="wl.signin wl.basic wl.skydrive"/>
</StackPanel>
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<ListBox x:Name="DirList" FontSize="36"
MouseLeftButtonUp="DirList_MouseLeftButtonUp">
</ListBox>
</Grid>
</Grid>
MainPage.xaml.cs則加入程式邏輯,主要原理是在使用者完成登入及同意後(btnSignIn_SessionChanged事件)建立一個LiveConnectClient,即可使用它呼叫REST API執行各種操作。呼叫結果由client_GetCompleted事件解析,相關的註解說明我附加在程式裡,所以直接看Code:
using System;
using System.Collections.Generic;
using System.Windows.Input;
using Microsoft.Phone.Controls;
using Microsoft.Live;
using Microsoft.Live.Controls;
namespace MiniSkyDriveExplorer
{
public partial class MainPage : PhoneApplicationPage
{
// Constructor
public MainPage()
{
InitializeComponent();
}
private LiveConnectClient client = null;
private void btnSignIn_SessionChanged(object sender,
LiveConnectSessionChangedEventArgs e)
{
if (e.Status == LiveConnectSessionStatus.Connected)
{
DirItem.LiveSession = e.Session;
client = new LiveConnectClient(e.Session);
client.GetCompleted +=
new EventHandler<LiveOperationCompletedEventArgs>(
client_GetCompleted);
//呼叫me/skydrive,取得根目錄folderId
client.GetAsync("me/skydrive",
new MyUserState(ApiMethod.SkyDriveProp));
}
}
private const string goUpperSymbol = "\t";
//所有的REST API呼叫動作完成後,都會觸發此段解析回傳結果
//為降低範例程式複雜度,以下並未包含防呆容錯的邏輯,實際開發時應補上
void client_GetCompleted(object
sender, LiveOperationCompletedEventArgs e)
{
MyUserState state = e.UserState as MyUserState;
if (state == null) return;
switch (state.Method)
{
//取得SkyDrive主資料夾的folderId
case ApiMethod.SkyDriveProp:
//取出id,列出根目錄下的項目
ListFiles(e.Result["id"].ToString());
break;
//取得目錄清單
case ApiMethod.SkyDriveDir:
//由data取得陣列
List<object> items =
e.Result["data"] as List<object>;
if (items != null)
{
DirList.Items.Clear();
DirList.DisplayMemberPath = "DisplayName";
//加入回上層的邏輯
if (folderIdStack.Count > 1)
{
DirItem di = new DirItem(goUpperSymbol, "[..]");
DirList.Items.Add(di);
}
foreach (Dictionary<string, object> item in items)
{
DirItem di = new DirItem(
item["id"].ToString(),
item["name"].ToString()
);
//資料夾時額外取得子項目數
if (di.IsFolder)
di.Count = int.Parse(item["count"].ToString());
else //檔案則取得下載網址
di.SrcUrl = item["source"].ToString();
//加入清單
DirList.Items.Add(di);
}
}
break;
}
}
//用以保存上層目錄的folderId
private Stack<string> folderIdStack = new Stack<string>();
private void ListFiles(string folderId)
{
if (client == null) return;
if (folderId == goUpperSymbol)
{
folderIdStack.Pop();
folderId = folderIdStack.Peek();
}
else folderIdStack.Push(folderId);
client.GetAsync(folderId + "/files",
new MyUserState(ApiMethod.SkyDriveDir));
}
private void DirList_MouseLeftButtonUp(object sender,
MouseButtonEventArgs e)
{
DirItem di = DirList.SelectedItem as DirItem;
//資料夾的話,繼續展開
if (di.IsFolder || di.Id == goUpperSymbol) ListFiles(di.Id);
//否則試著下載回來檢視
else
{
DirItem.Current = di; //將此DirItem設為Current
NavigationService.Navigate(
new Uri("/Viewer.xaml", UriKind.Relative));
}
}
}
}
為了讓程式更結構化一點,我宣告了一個很簡單的SkyDrive項目物件模型: (即上面MainPage.xaml.cs中所出現DirItem、MyUserState物件的由來)
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Microsoft.Live;
namespace MiniSkyDriveExplorer
{
#region 用以記錄REST API呼叫資訊的狀態物件
public enum ApiMethod
{
SkyDriveProp, // me/skydrive
SkyDriveDir, // folderId/files
SkyDriveGetImage,
SkyDriveGetMedia
}
public class MyUserState
{
public ApiMethod Method;
public string FileName;
public MyUserState(ApiMethod method)
{
Method = method;
}
}
#endregion
#region 簡單的目錄物件
public class DirItem
{
//是否為資料夾
public bool IsFolder
{
get { return Id.StartsWith("folder"); }
}
//顯示名稱
public string Name;
//foderId或fileId
public string Id;
//上層資料夾的folderId
public string ParentId;
//若為資料夾時,標示其下項目數
public int Count;
//下載來源URL
public string SrcUrl;
//顯示名稱,資料夾為[folderName](count),檔案則為fileName
public string DisplayName
{
get
{
return IsFolder && !Name.Equals("..") ?
string.Format("[{0}]({1})", Name, Count) : Name;
}
}
public DirItem(string id, string name)
{
Id = id;
Name = name;
}
//用此靜態屬性當作頁面間的傳遞媒介
public static DirItem Current = null;
//供跨頁面共用LiveConnectSession
public static LiveConnectSession LiveSession = null;
}
#endregion
}
除了SkyDrive清單展示,我另外做了一個Viewer.xaml,用來呈現SkyDrive中的圖片(png, jpg)及媒體檔(wmv, mp3, mp4)。
註: Windows Phone模擬器不支援部分媒體格式,測試前可先看一下MSDN文件,其中有些已註明This codec is unsupported in Windows Phone Emulator的格式,測試前請認清,以免在模擬器上猛試不成白花時間! (謎之聲: 這條註解怎麼隱含著強烈的怨念?)
要從SkyDrive下載檔案,可以透過LiveConnectClient.DownloadAsync輕鬆達成,在client_DownloadCompleted事件中可以直接取得byte[],十分方便。媒體檔的部分,我還沒試出可以串流播放的做法,所以先採行的方式是先將下載結果存成IsolatedStorageFile,再叫出MediaPlayerLauncher進行播放。程式範例如下:
using System;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using Microsoft.Phone.Controls;
using Microsoft.Live;
using System.Windows.Media.Imaging;
using System.IO.IsolatedStorage;
using Microsoft.Phone.Tasks;
namespace MiniSkyDriveExplorer
{
public partial class Viewer : PhoneApplicationPage
{
public Viewer()
{
InitializeComponent();
}
private string[] imgExts = "png,jpg".Split(',');
private string[] mediaExts = "wmv,mp3,mp4".Split(',');
private LiveConnectClient client = null;
private void PhoneApplicationPage_Loaded(object sender, RoutedEventArgs e)
{
if (DirItem.LiveSession == null || DirItem.Current == null)
return;
//建立LiveConnectClient
client = new LiveConnectClient(DirItem.LiveSession);
client.DownloadCompleted +=
new EventHandler<LiveDownloadCompletedEventArgs>(
client_DownloadCompleted);
//清空內容
ContentPanel.Children.Clear();
//取得要檢視的檔案
DirItem di = DirItem.Current;
ApplicationTitle.Text = di.Name;
//由副檔名決定開啟方式
string ext = System.IO.Path.GetExtension(di.Name).ToLower().TrimStart('.');
if (imgExts.Contains(ext))
client.DownloadAsync(di.SrcUrl, new MyUserState(ApiMethod.SkyDriveGetImage));
else if (mediaExts.Contains(ext))
//針對媒體檔,比較貼心的方式是透過Streaming方式播放
//此處只簡單示範整個檔案下載完畢後才撥放
client.DownloadAsync(di.SrcUrl,
new MyUserState(ApiMethod.SkyDriveGetMedia) { FileName = di.Name });
else
ContentPanel.Children.Add(new TextBox() { Text = "Unsupported File Type" });
}
void client_DownloadCompleted(object sender, LiveDownloadCompletedEventArgs e)
{
MyUserState state = e.UserState as MyUserState;
if (state == null) return;
switch (state.Method)
{
case ApiMethod.SkyDriveGetImage:
BitmapImage bmp = new BitmapImage();
bmp.SetSource(e.Result);
Image img = new Image();
img.Source = bmp;
ContentPanel.Children.Add(img);
break;
case ApiMethod.SkyDriveGetMedia:
//此處採用將檔案下載儲存後再播放的做法
IsolatedStorageFileStream fs =
IsolatedStorageFile.GetUserStoreForApplication()
.CreateFile(state.FileName);
e.Result.CopyTo(fs);
fs.Close();
MediaPlayerLauncher mpl = new MediaPlayerLauncher()
{
Location = MediaLocationType.Data,
Media = new Uri(state.FileName, UriKind.Relative)
};
mpl.Show();
break;
}
}
}
}
程式實際執行結果如下,按下SignInButton後,畫面會變成Windows Live的登入畫面,使用者輸入Windows Live帳號並同意授權後:
程式便可透過me/skydrive REST API取得使用者SkyDrive根目錄的Id,接著使用folderId/files就可取回該目錄下所有子項目的資訊,以ListBox的方式展示出來。
檢視圖片及影片的範例:
很簡單吧!? 大家一起為WP7 App加入SkyDrive支援吧!