在 Xamarin.Forms 專案,修正使用 Azure 行動應用的身分認證功能
在這裡,我們將要把我們寫好的 Xamarin.Forms 專案,加入使用 Azure 行動應用服務功能,並且要能夠進行身分驗證功能。
Mobile Apps 會使用 MobileServiceClient 的 LoginAsync 擴充方法,透過 App Service 驗證將使用者登入。
修改 Xamarin.Forms 專案
在這裡,我們將要修正 工作日報表 申請作業的相關功能。
建立資料表會用到的資料模型
- 滑鼠右擊核心PCL專案的
Models
資料夾,點選加入
>類別
- 在對話窗中,點選
Visual C#
>類別
- 在名稱欄位輸入
WorkLog
,最後點選新增
按鈕 - 將 WorkLog 類別修改成底下的程式碼
public class WorkLog
{
public string Id { get; set; }
public string 專案名稱 { get; set; }
public DateTime 日期 { get; set; }
public double 處理時間 { get; set; }
public string 工作內容 { get; set; }
}
在 ViewMolde 加入這個資料表的 CRUD 行動應用服務功能。
- 在核心PCL專案內,找到資料夾
ViewModels
內的填寫工作日報表HomePageViewModel.cs
- 滑鼠雙擊這個檔案,打開它
- 使用底下程式碼將其置換掉
public class 差旅費用申請HomePageViewModel : BindableBase, INavigationAware
{
#region Repositories (遠端或本地資料存取)
// 取得 Azure Mobile App 中的 差旅費用 資料表物件
IMobileServiceTable<BusinessTripExpense> 差旅費用Table = MainHelper.client.GetTable<BusinessTripExpense>();
#endregion
#region ViewModel Property (用於在 View 中作為綁定之用)
#region 差旅費用項目清單
private ObservableCollection<差旅費用項目ViewModel> _差旅費用項目清單 = new ObservableCollection<差旅費用項目ViewModel>();
/// <summary>
/// 工作日報表項目清單
/// </summary>
public ObservableCollection<差旅費用項目ViewModel> 差旅費用項目清單
{
get { return _差旅費用項目清單; }
set { SetProperty(ref _差旅費用項目清單, value); }
}
#endregion
#region 點選差旅費用項目
private 差旅費用項目ViewModel _點選差旅費用項目;
/// <summary>
/// PropertyDescription
/// </summary>
public 差旅費用項目ViewModel 點選差旅費用項目
{
get { return this._點選差旅費用項目; }
set { this.SetProperty(ref this._點選差旅費用項目, value); }
}
#endregion
#endregion
#region Field 欄位
BusinessTripExpense foo差旅費用;
public DelegateCommand 點選差旅費用項目Command { get; set; }
public DelegateCommand 新增按鈕Command { get; set; }
private readonly INavigationService _navigationService;
private readonly IEventAggregator _eventAggregator;
#endregion
#region Constructor 建構式
public 差旅費用申請HomePageViewModel(INavigationService navigationService, IEventAggregator eventAggregator)
{
#region 相依性服務注入的物件
_eventAggregator = eventAggregator;
_navigationService = navigationService;
#endregion
#region 頁面中綁定的命令
點選差旅費用項目Command = new DelegateCommand(async () =>
{
#region 建立要傳遞到下個頁面的參數
var fooNavigationParameters = new NavigationParameters();
fooNavigationParameters.Add("點選工作日報表項目", 點選差旅費用項目);
fooNavigationParameters.Add("新增或修改", 新增或修改Enum.修改);
#endregion
//點選工作日報表項目 = null;
await _navigationService.NavigateAsync("差旅費用申請紀錄修改Page", fooNavigationParameters);
});
新增按鈕Command = new DelegateCommand(async () =>
{
#region 建立要傳遞到下個頁面的參數
var fooNavigationParameters = new NavigationParameters();
fooNavigationParameters.Add("點選工作日報表項目", new 差旅費用項目ViewModel());
fooNavigationParameters.Add("新增或修改", 新增或修改Enum.新增);
#endregion
await _navigationService.NavigateAsync("差旅費用申請紀錄修改Page", fooNavigationParameters);
});
#endregion
#region 事件聚合器訂閱
_eventAggregator.GetEvent<差旅費用紀錄維護動作Event>().Subscribe(async x =>
{
switch (x.新增或修改Enum)
{
case 新增或修改Enum.新增:
#region Azure 行動應用服務的新增
foo差旅費用 = new BusinessTripExpense
{
備註 = x.差旅費用項目.備註,
出差日期 = x.差旅費用項目.出差日期,
國內外 = x.差旅費用項目.國內外,
地點 = x.差旅費用項目.地點,
是否有單據 = x.差旅費用項目.是否有單據,
費用 = x.差旅費用項目.費用,
項目名稱 = x.差旅費用項目.項目名稱,
類型 = x.差旅費用項目.類型,
};
await 差旅費用Table.InsertAsync(foo差旅費用);
await Init();
#endregion
break;
case 新增或修改Enum.修改:
#region 更新欄位資料
var fooObj = 差旅費用項目清單.FirstOrDefault(z => z.ID == x.差旅費用項目.ID);
if (fooObj != null)
{
var fooIdx = 差旅費用項目清單.IndexOf(fooObj);
差旅費用項目清單[fooIdx] = x.差旅費用項目;
}
#region Azure 行動應用服務的新增
foo差旅費用 = await 差旅費用Table.LookupAsync(x.差旅費用項目.ID);
foo差旅費用.備註 = x.差旅費用項目.備註;
foo差旅費用.出差日期 = x.差旅費用項目.出差日期;
foo差旅費用.國內外 = x.差旅費用項目.國內外;
foo差旅費用.地點 = x.差旅費用項目.地點;
foo差旅費用.是否有單據 = x.差旅費用項目.是否有單據;
foo差旅費用.費用 = x.差旅費用項目.費用;
foo差旅費用.項目名稱 = x.差旅費用項目.項目名稱;
foo差旅費用.類型 = x.差旅費用項目.類型;
await 差旅費用Table.UpdateAsync(foo差旅費用);
#endregion
#endregion
break;
case 新增或修改Enum.刪除:
var fooObjDel = 差旅費用項目清單.FirstOrDefault(z => z.ID == x.差旅費用項目.ID);
if (fooObjDel != null)
{
差旅費用項目清單.Remove(fooObjDel);
}
#region Azure 行動應用服務的新增
foo差旅費用 = await 差旅費用Table.LookupAsync(x.差旅費用項目.ID);
await 差旅費用Table.DeleteAsync(foo差旅費用);
#endregion
break;
default:
break;
}
});
#endregion
}
public void OnNavigatedFrom(NavigationParameters parameters)
{
}
public async void OnNavigatedTo(NavigationParameters parameters)
{
if (parameters.ContainsKey("差旅費用紀錄維護動作內容") == false)
{
await Init();
}
}
#endregion
#region Navigation Events (頁面導航事件)
#endregion
#region 設計時期或者執行時期的ViewModel初始化
#endregion
#region 相關事件
#endregion
#region 相關的Command定義
#endregion
#region 其他方法
/// <summary>
/// 進行要顯示資料的初始化
/// </summary>
private async Task Init()
{
差旅費用項目ViewModel foo差旅費用項目;
差旅費用項目清單.Clear();
#region 呼叫 Azure 行動應用後台,取得最新後台資料表的清單
var fooList = await 差旅費用Table.OrderByDescending(x => x.出差日期).ToListAsync();
foreach (var item in fooList)
{
foo差旅費用項目 = new 差旅費用項目ViewModel
{
ID = item.Id,
出差日期 = item.出差日期,
項目名稱 = item.項目名稱,
地點 = item.地點,
類型 = item.類型,
是否有單據 = item.是否有單據,
國內外 = item.國內外,
費用 = item.費用,
備註 = item.備註,
};
差旅費用項目清單.Add(foo差旅費用項目);
}
#endregion
}
#endregion
}
- 在核心PCL專案內,找到資料夾
ViewModels
內的填寫工作日報表記錄修改PageViewModel.cs
- 滑鼠雙擊這個檔案,打開它
- 使用底下程式碼將其置換掉
public class 填寫工作日報表記錄修改PageViewModel : BindableBase, INavigationAware
{
#region Repositories (遠端或本地資料存取)
//工作日報表Repository foo工作日報表Repository = new 工作日報表Repository();
IMobileServiceTable<WorkLog> WorkLogTable = MainHelper.client.GetTable<WorkLog>();
#endregion
#region ViewModel Property (用於在 View 中作為綁定之用)
#region 工作日報表項目
private 工作日報表項目ViewModel _工作日報表項目;
/// <summary>
/// 工作日報表項目
/// </summary>
public 工作日報表項目ViewModel 工作日報表項目
{
get { return this._工作日報表項目; }
set { this.SetProperty(ref this._工作日報表項目, value); }
}
#endregion
#region Title
private string _Title;
/// <summary>
/// Title
/// </summary>
public string Title
{
get { return this._Title; }
set { this.SetProperty(ref this._Title, value); }
}
#endregion
#region 顯示刪除按鈕
private bool _顯示刪除按鈕;
/// <summary>
/// 顯示刪除按鈕
/// </summary>
public bool 顯示刪除按鈕
{
get { return this._顯示刪除按鈕; }
set { this.SetProperty(ref this._顯示刪除按鈕, value); }
}
#endregion
#endregion
#region Field 欄位
新增或修改Enum 新增或修改 = 新增或修改Enum.修改;
string fooID = "";
WorkLog 工作日報表Model;
public DelegateCommand 儲存按鈕Command { get; set; }
public DelegateCommand 取消按鈕Command { get; set; }
public DelegateCommand Can刪除按鈕Command { get; set; }
private readonly INavigationService _navigationService;
private readonly IEventAggregator _eventAggregator;
public readonly IPageDialogService _dialogService;
#endregion
#region Constructor 建構式
public 填寫工作日報表記錄修改PageViewModel(INavigationService navigationService, IEventAggregator eventAggregator,
IPageDialogService dialogService)
{
#region 相依性服務注入的物件
_dialogService = dialogService;
_eventAggregator = eventAggregator;
_navigationService = navigationService;
#endregion
#region 頁面中綁定的命令
Can刪除按鈕Command = new DelegateCommand(async () =>
{
var fooAnswer = await _dialogService.DisplayAlertAsync("警告", "您確定刪除這筆請假記錄", "確定", "取消");
if (fooAnswer == true)
{
工作日報表Model = await WorkLogTable.LookupAsync(fooID);
if (工作日報表Model != null)
{
await WorkLogTable.DeleteAsync(工作日報表Model);
_eventAggregator.GetEvent<漢堡按鈕啟動或隱藏Event>().Publish(true);
await _navigationService.GoBackAsync();
}
else
{
return;
}
}
},
() =>
{
//設定這個命令在何種狀態下可以被執行
return 新增或修改 == 新增或修改Enum.新增 ? false : true;
}
);
儲存按鈕Command = new DelegateCommand(async () =>
{
var fooCheck = await Check資料完整性();
if (fooCheck == false)
{
return;
}
if (新增或修改 == 新增或修改Enum.新增)
{
工作日報表Model = new WorkLog();
工作日報表Model.專案名稱 = 工作日報表項目.專案名稱;
工作日報表Model.工作內容 = 工作日報表項目.工作內容;
工作日報表Model.日期 = 工作日報表項目.日期;
工作日報表Model.處理時間 = 工作日報表項目.處理時間;
await WorkLogTable.InsertAsync(工作日報表Model);
_eventAggregator.GetEvent<漢堡按鈕啟動或隱藏Event>().Publish(true);
await _navigationService.GoBackAsync();
return;
}
else
{
工作日報表Model = await WorkLogTable.LookupAsync(fooID);
if (工作日報表Model != null)
{
工作日報表Model.Id = 工作日報表項目.ID;
工作日報表Model.專案名稱 = 工作日報表項目.專案名稱;
工作日報表Model.工作內容 = 工作日報表項目.工作內容;
工作日報表Model.日期 = 工作日報表項目.日期;
工作日報表Model.處理時間 = 工作日報表項目.處理時間;
await WorkLogTable.UpdateAsync(工作日報表Model);
_eventAggregator.GetEvent<漢堡按鈕啟動或隱藏Event>().Publish(true);
await _navigationService.GoBackAsync();
return;
}
else
{
return;
}
}
});
取消按鈕Command = new DelegateCommand(async () =>
{
var fooAnswer = await _dialogService.DisplayAlertAsync("警告", "您確定取消這筆請假記錄", "確定", "取消");
if (fooAnswer == true)
{
_eventAggregator.GetEvent<漢堡按鈕啟動或隱藏Event>().Publish(true);
await _navigationService.GoBackAsync();
}
});
#endregion
_eventAggregator.GetEvent<漢堡按鈕啟動或隱藏Event>().Publish(false);
}
public void OnNavigatedFrom(NavigationParameters parameters)
{
}
public async void OnNavigatedTo(NavigationParameters parameters)
{
if (parameters.ContainsKey("ID") == true)
{
fooID = parameters["ID"] as string;
新增或修改 = 新增或修改Enum.修改;
Title = "工作日報記錄修改";
顯示刪除按鈕 = true;
}
else
{
fooID = "";
新增或修改 = 新增或修改Enum.新增;
Title = "工作日報記錄新增";
顯示刪除按鈕 = false;
}
// 要求檢查這個命令是否可以被使用
Can刪除按鈕Command.RaiseCanExecuteChanged();
await Init();
}
#endregion
#region Navigation Events (頁面導航事件)
#endregion
#region 設計時期或者執行時期的ViewModel初始化
#endregion
#region 相關事件
#endregion
#region 相關的Command定義
#endregion
#region 其他方法
private async Task Init()
{
if (string.IsNullOrEmpty(fooID) == true)
{
工作日報表項目 = new 工作日報表項目ViewModel();
}
else
{
工作日報表Model = await WorkLogTable.LookupAsync(fooID);
if (工作日報表Model == null)
{
工作日報表項目 = new 工作日報表項目ViewModel();
}
else
{
工作日報表項目 = new 工作日報表項目ViewModel
{
ID = 工作日報表Model.Id,
專案名稱 = 工作日報表Model.專案名稱,
工作內容 = 工作日報表Model.工作內容,
日期 = 工作日報表Model.日期,
是否顯示日期區塊 = true,
當日累計工時 = 0,
處理時間 = 工作日報表Model.處理時間,
};
}
}
}
private async Task<bool> Check資料完整性()
{
if (string.IsNullOrEmpty(工作日報表項目.專案名稱) == true)
{
await _dialogService.DisplayAlertAsync("警告", "專案名稱 必須要輸入", "確定");
return false;
}
return true;
}
#endregion
}
設計身分驗證機制
定義與實作 IAuthenticate
為了驗證 Xamarin Forms 專案,請在應用程式的可攜式類別庫中定義 IAuthenticate 介面。 然後,將 [登入] 按鈕新增至可攜式類別庫中定義的使用者介面,讓您按一下來開始驗證。 驗證成功後,將會從行動應用程式後端載入資料。
定義 IAuthenticate
- 打開核心PCL專案的
App.xaml.cs
檔案 - 在 App 類別定義之前緊接著加入下列 IAuthenticate 介面定義
/// <summary>
/// 要透過 Azure 行動應用服務進行身分驗證的介面
/// 實際實作的部分,會在每個原生專案中實作與
/// </summary>
public interface IAuthenticate
{
Task<bool> Authenticate(MobileServiceAuthenticationProvider p登入方式);
}
請修正相關命名空間錯誤
- 將下列靜態成員新增至 App 類別,以使用平台特定實作來初始化介面。
/// <summary>
/// 該介面所實際物件,會在原生專案中,指定實際實作物件
/// </summary>
public static IAuthenticate Authenticator { get; private set; }
/// <summary>
/// Azure 行動應用服務進行身分驗證的介面初始化方法
/// </summary>
/// <param name="authenticator"></param>
public static void Init(IAuthenticate authenticator)
{
Authenticator = authenticator;
}
- 底下為 App.xaml.cs 的完整程式碼
using Microsoft.WindowsAzure.MobileServices;
using Prism.Unity;
using System.Threading.Tasks;
using XFDoggy.Helpers;
using XFDoggy.Models;
using XFDoggy.Views;
namespace XFDoggy
{
using Microsoft.WindowsAzure.MobileServices;
using Prism.Unity;
using System.Threading.Tasks;
using XFDoggy.Helpers;
using XFDoggy.Models;
using XFDoggy.Views;
namespace XFDoggy
{
#region Azure Mobile 身分驗證介面
/// <summary>
/// 要透過 Azure 行動應用服務進行身分驗證的介面
/// 實際實作的部分,會在每個原生專案中實作與
/// </summary>
public interface IAuthenticate
{
Task<bool> Authenticate(MobileServiceAuthenticationProvider p登入方式);
}
#endregion
public partial class App : PrismApplication
{
#region Azure Mobile 身分驗證介面初始化
/// <summary>
/// 該介面所實際物件,會在原生專案中,指定實際實作物件
/// </summary>
public static IAuthenticate Authenticator { get; private set; }
/// <summary>
/// Azure 行動應用服務進行身分驗證的介面初始化方法
/// </summary>
/// <param name="authenticator"></param>
public static void Init(IAuthenticate authenticator)
{
Authenticator = authenticator;
}
#endregion
public App(IPlatformInitializer initializer = null) : base(initializer) { }
protected override void OnInitialized()
{
InitializeComponent();
//NavigationService.NavigateAsync($"xf:///MDPage?Menu={MenuItemEnum.關於.ToString()}/NaviPage/MainPage?title=多奇數位創意有限公司");
#region 進行離線資料庫初始化
MainHelper.AzureMobileOfflineInit();
#endregion
NavigationService.NavigateAsync($"LoginPage");
}
protected override void RegisterTypes()
{
Container.RegisterTypeForNavigation<MainPage>();
Container.RegisterTypeForNavigation<MDPage>();
Container.RegisterTypeForNavigation<NaviPage>();
Container.RegisterTypeForNavigation<OnCall電話Page>();
Container.RegisterTypeForNavigation<我要請假HomePage>();
Container.RegisterTypeForNavigation<我要請假記錄修改Page>();
Container.RegisterTypeForNavigation<差旅費用申請HomePage>();
Container.RegisterTypeForNavigation<差旅費用申請紀錄修改Page>();
Container.RegisterTypeForNavigation<最新消息HomePage>();
Container.RegisterTypeForNavigation<填寫工作日報表HomePage>();
Container.RegisterTypeForNavigation<填寫工作日報表記錄修改Page>();
Container.RegisterTypeForNavigation<LoginPage>();
Container.RegisterTypeForNavigation<LoginPage>();
}
}
}
在 Android 中,實作 IAuthenticate
- 在原生 Android 專案的 開啟
MainActivity.cs
檔案 - 更新 MainActivity 類別來實作 IAuthenticate 介面更新 MainActivity 類別,新增 IAuthenticate 介面所需的 MobileServiceUser 欄位和 Authenticate 方法
- 在 MainActivity 類別的 OnCreate 方法中,在呼叫 LoadApplication() 之前,需要將核心PCL內的
Authenticator
介面,指定這裡的實作物件 - 底下為修正後的 MainActivity 類別定義
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity, IAuthenticate
{
#region Azure 行動應用之身分驗證用到程式碼
// Define a authenticated user.
private MobileServiceUser user;
public async Task<bool> Authenticate(MobileServiceAuthenticationProvider p登入方式)
{
// 驗證結果
var success = false;
// 要顯示的訊息
var message = string.Empty;
try
{
// 每次登入的時候,都要強制重新登入,不使用上次登入的資訊
CookieManager.Instance.RemoveAllCookie();
await MainHelper.client.LogoutAsync();
// 呼叫 Azure Mobile 用戶端的 LoginAsync 方法,依據指定的登入類型,進行身分驗證登入
user = await MainHelper.client.LoginAsync(this, p登入方式);
if (user != null)
{
message = string.Format("you are now signed-in as {0}.", user.UserId);
success = true;
}
}
catch (Exception ex)
{
message = ex.Message;
}
// 顯示登入成功或者失敗.
//AlertDialog.Builder builder = new AlertDialog.Builder(this);
//builder.SetMessage(message);
//builder.SetTitle("Sign-in result");
//builder.Create().Show();
return success;
}
#endregion
protected override void OnCreate(Bundle bundle)
{
TabLayoutResource = Resource.Layout.tabs;
ToolbarResource = Resource.Layout.toolbar;
base.OnCreate(bundle);
global::Xamarin.Forms.Forms.Init(this, bundle);
#region Azure 行動應用之身分驗證用到程式碼
// 將在 Android 原生平台實作的 IAuthenticate 物件,指定到 核心PCL 專案內
App.Init((IAuthenticate)this);
#endregion
LoadApplication(new App(new AndroidInitializer()));
}
}
在 iOS 中,實作 IAuthenticate
更新 AppDelegate 類別來實作 IAuthenticate 介面
- 打開原生 iOS 專案的
AppDelegate.cs
檔案 - 將底下程式碼替換掉
AppDelegate
類別定義 - 在這裡,我們新增了一個類別欄位
private MobileServiceUser user
用來儲存認證通過的使用者資訊 - 實作出
IAuthenticate
介面中的Authenticate
方法,我們依據傳入要認證的類型,呼叫LoginAsync
方法,顯示出登入頁面。
public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate, IAuthenticate
{
#region Azure Mobile 身分驗證
// Define a authenticated user.
private MobileServiceUser user;
public async Task<bool> Authenticate(MobileServiceAuthenticationProvider p登入方式)
{
var success = false;
var message = string.Empty;
try
{
// Sign in with Facebook login using a server-managed flow.
if (user == null)
{
// 每次登入的時候,都要強制重新登入,不使用上次登入的資訊
foreach (var cookie in NSHttpCookieStorage.SharedStorage.Cookies)
{
NSHttpCookieStorage.SharedStorage.DeleteCookie(cookie);
}
await MainHelper.client.LogoutAsync();
// 呼叫 Azure Mobile 用戶端的 LoginAsync 方法,依據指定的登入類型,進行身分驗證登入
user = await MainHelper.client.LoginAsync(UIApplication.SharedApplication.KeyWindow.RootViewController, p登入方式);
if (user != null)
{
message = string.Format("You are now signed-in as {0}.", user.UserId);
success = true;
}
}
}
catch (Exception ex)
{
message = ex.Message;
}
// Display the success or failure message.
UIAlertView avAlert = new UIAlertView("Sign-in result", message, null, "OK", null);
avAlert.Show();
return success;
}
#endregion
//
// This method is invoked when the application has loaded and is ready to run. In this
// method you should instantiate the window, load the UI into it and then make the window
// visible.
//
// You have 17 seconds to return from this method, or iOS will terminate your application.
//
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
global::Xamarin.Forms.Forms.Init();
LoadApplication(new App(new iOSInitializer()));
// http://stackoverflow.com/questions/24521355/azure-mobile-services-invalid-operation-exception-platform-specific-assembly-n
Microsoft.WindowsAzure.MobileServices.CurrentPlatform.Init();
return base.FinishedLaunching(app, options);
}
}
將驗證加入登入頁面中
- 打開核心PCL專案的
ViewModels
資料夾內的LoginPageViewModel.cs
- 在這裡,我們修正了這兩個命令
Facebook登入Command
/Google登入Command
只有當身分認證成功之後,才會進入到這個應用程式的首頁 - 修正建構式如同底下程式碼
public LoginPageViewModel(INavigationService navigationService, IPageDialogService dialogService)
{
_dialogService = dialogService;
_navigationService = navigationService;
登入Command = new DelegateCommand(async () =>
{
await _navigationService.NavigateAsync($"xf:///MDPage?Menu={MenuItemEnum.關於.ToString()}/NaviPage/MainPage?title=多奇數位創意有限公司");
});
Facebook登入Command = new DelegateCommand(async () =>
{
var fooResult = await App.Authenticator.Authenticate(Microsoft.WindowsAzure.MobileServices.MobileServiceAuthenticationProvider.Facebook);
if(fooResult == true)
{
await _navigationService.NavigateAsync($"xf:///MDPage?Menu={MenuItemEnum.關於.ToString()}/NaviPage/MainPage?title=多奇數位創意有限公司");
}
});
Google登入Command = new DelegateCommand(async () =>
{
var fooResult = await App.Authenticator.Authenticate(Microsoft.WindowsAzure.MobileServices.MobileServiceAuthenticationProvider.Google);
if (fooResult == true)
{
await _navigationService.NavigateAsync($"xf:///MDPage?Menu={MenuItemEnum.關於.ToString()}/NaviPage/MainPage?title=多奇數位創意有限公司");
}
});
}