XAML in Xamarin.Forms 基礎篇 電子書

XAML in Xamarin.Forms 基礎篇 電子書
XAML in Xamarin.Forms 基礎篇 電子書

Xamarin.Forms 快速入門 電子書

Xamarin.Forms 快速入門 電子書
Xamarin.Forms 快速入門 電子書

2017/03/30

Xamarin 的專案,因為路徑過長所造成 Xamarin 異常問題

當您建立一個 Xamarin.Forms 的專案,按照之前的作法,都沒有遇到問題,可是,對於剛剛建立的專案,當要建置的時候,卻得到底下奇怪的錯誤訊息。
明明還沒有做任何工作,只是建立專案,建置,卻告知 styles.xml 檔案有問題,可是,這個檔案卻是由 Visual Studio 專案樣板產生的,您也沒有做任何修改。
嚴重性 程式碼 說明  專案  檔案  行   隱藏項目狀態
錯誤      No resource found that matches the given name: attr 'colorAccent'.  XRemoteNotificationDroid.Droid  D:\Vulcan\GitBook\Temp\XRemoteNotificationDroid\XRemoteNotificationDroid\XRemoteNotificationDroid.Droid\Resources\values\styles.xml 2   
錯誤      Error retrieving parent for item: No resource found that matches the given name 'Theme.AppCompat.Light.NoActionBar'.    XRemoteNotificationDroid.Droid  D:\Vulcan\GitBook\Temp\XRemoteNotificationDroid\XRemoteNotificationDroid\XRemoteNotificationDroid.Droid\Resources\values\styles.xml 2   
錯誤      No resource found that matches the given name: attr 'colorPrimary'. XRemoteNotificationDroid.Droid  D:\Vulcan\GitBook\Temp\XRemoteNotificationDroid\XRemoteNotificationDroid\XRemoteNotificationDroid.Droid\Resources\values\styles.xml 2   
錯誤      No resource found that matches the given name: attr 'colorPrimaryDark'. XRemoteNotificationDroid.Droid  D:\Vulcan\GitBook\Temp\XRemoteNotificationDroid\XRemoteNotificationDroid\XRemoteNotificationDroid.Droid\Resources\values\styles.xml 2   
此時,若您清除 Android 專案,並且選擇建置,就會看到底下錯誤訊息。
沒錯,就是這個 完整的檔名必須少於 260 個字元,並且目錄名稱必須少於 248 個字元。
因此,將這個專案搬移到距離跟目錄比較近的地方或者重新建立一個專案,使用比較短的方案名稱吧。
嚴重性 程式碼 說明  專案  檔案  行   隱藏項目狀態
錯誤      "ResolveLibraryProjectImports" 工作發生未預期的失敗。
System.IO.PathTooLongException: 指定的路徑、檔名,或是兩者都太長。完整的檔名必須少於 260 個字元,並且目錄名稱必須少於 248 個字元。
   於 System.IO.PathHelper.GetFullPathName()
   於 System.IO.Path.LegacyNormalizePath(String path, Boolean fullCheck, Int32 maxPathLength, Boolean expandShortPaths)
   於 System.IO.Path.NormalizePath(String path, Boolean fullCheck, Int32 maxPathLength, Boolean expandShortPaths)
   於 System.IO.Path.NormalizePath(String path, Boolean fullCheck, Int32 maxPathLength)
   於 System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, Int32 rights, Boolean useRights, FileShare share, Int32 bufferSize, FileOptions options, SECURITY_ATTRIBUTES secAttrs, String msgPath, Boolean bFromProxy, Boolean useLongPath, Boolean checkHost)
   於 System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share)
   於 Xamarin.Tools.Zip.ZipEntry.DoExtract(IntPtr zipFile, String destinationPath, FileMode outputFileMode, EntryExtractEventArgs args)
   於 Xamarin.Tools.Zip.ZipEntry.Extract(String destinationDir, String destinationFileName, FileMode outputFileMode)
   於 Xamarin.Android.Tools.Files.ExtractAll(ZipArchive zip, String destination, Action`2 progressCallback)
   於 Xamarin.Android.Tasks.ResolveLibraryProjectImports.Extract(DirectoryAssemblyResolver res, ICollection`1 jars, ICollection`1 resolvedResourceDirectories, ICollection`1 resolvedAssetDirectories, ICollection`1 resolvedEnvironments)
   於 Xamarin.Android.Tasks.ResolveLibraryProjectImports.Execute()
   於 Microsoft.Build.BackEnd.TaskExecutionHost.Microsoft.Build.BackEnd.ITaskExecutionHost.Execute()
   於 Microsoft.Build.BackEnd.TaskBuilder.<ExecuteInstantiatedTask>d__26.MoveNext()  XRemoteNotificationDroid.Droid          

2017/03/29

使用 Azure Mobile App 與 Xamarin 開發出企業內部應用的跨平台開發程式

需求說明

要進行這本書中的內容練習,您需要準備:
  • Azure 使用帳號
  • Facebook 帳號
  • Google 帳號

建立 Azure Mobile App

這裡有兩種方法可以建立 Azure Mobile App Service,若您對於 Azure Mobile App 不孰悉的話,建議您使用第二個作法。

使用 Web + 行動 > Moibl App 建立 Azure Mobile App

在這裡我們需要建立一個 Azure Mobile App Server來提供我們後端的資料存取服務。
  • 打開 Microsoft Azure 儀表板
  • 在 Microsoft Azure 儀表板頁面左上方,找到 + 新增 區域,點擊這個連結
  • 在 新增 刀鋒視窗中,選擇 Web + 行動
  • 在 Web + 行動 刀鋒視窗中,選擇 Mobile App
  • 在 Mobile App 刀鋒視窗中,依序輸入 應用程式名稱 / 訂戶名稱 / 資源群組 / App Service 方案/位置,下圖為這個練習所建立的 Mobile App 資訊。
    應用程式名稱 : XamarinAzureDay
    資源群組(新建): DoggyXamarin
    App Service 方案/ DoggyXamarinAsiaAppService / Southeast Asia
    定價層:F1免費
  • 在 Mobile App 刀鋒視窗下,點選 釘選到儀表板
  • 接著點 建立 按鈕,完成建立這個 Azure Mobile App 服務
  • 您需要等候一段時間,Azure 會進行部署相關服務到特定機器上。

使用 Mobile Apps Quickstart 建立 Azure Mobile App

在這裡,我們使用 Azure 提供的 Mobile App 快速入門 應用程式,來快速幫我們建立起這一個後端資料庫存取服務
  • 打開 Microsoft Azure 儀表板
  • 在 Microsoft Azure 儀表板頁面左上方,找到 + 新增 區域,點擊這個連結
  • 在 新增 刀鋒視窗中,請在搜尋文字輸入盒內(有 搜尋Marketplace 文字處),輸入 Mobile Apps
  • 請在 Everything 刀鋒視窗中,點選 Mobile Apps Quickstart 這個項目
  • 在 Mobile Apps Quickstart 刀鋒視窗中,請點選 建立 按鈕
    底下為這個刀鋒視窗中顯示的說明文字:
    Accelerate your mobile app development with this ready-to-use Mobile App todo sample. Azure Mobile Apps uses Azure App Service to provide a turnkey way to store structured data, authenticate users, and send push notifications. With native and cross-platform SDKs for iOS, Android, Windows, and JavaScript, as well as a powerful and flexible REST API, Azure Mobile Apps empowers you to build connected applications for any platform and deliver a consistent experience across devices.
    • Pre-configured for a basic SQLite database
    • Connect to on-premise or Azure-hosted SQL databases
    • Authenticate users via Azure Active Directory, Facebook, Google, Microsoft and Twitter
    • Push notifications via Apple, Google and Microsoft services
    • Gain insights with mobile analytics
    • Auto-scale to millions of devices
  • 在新出現的刀鋒視窗中,請依序填入底下欄位所需要的內容
    應用程式名稱 : XamarinAzureQuickstartDoggy
    (請依照您的需求,填入您想要的應用程式名稱)
    資源群組(使用現有項目): DoggyXamarin
    (您可以新建或者選擇使用現有項目)
    App Service 方案/ DoggyXamarinAsiaAppService / Southeast Asia
    定價層:F1免費
    (這裡可以依照您的需求來選取或定義)
    底下為這個練習範例所建立的 Mobile Apps Quickstart 資訊
  • 最後,請勾選 釘選到儀表板 ,並且點選 建立 按鈕
  • 您需要等候一段時間,Azure 會進行部署相關服務到特定機器上。
  • 當這個服務建立完成之後,會出現底下刀鋒視窗 XamarinAzureQuickstartDoggy / Mobile Apps Quickstart
    最上方中間的那個圖示 xamarinazurequickstart 行動App 就是這個後端服務設定入口點。

建立 Azure 行動應用要用到的 SQL 資料庫

在這裡,我們將會使用剛剛建立的 XamarinAzureDay App Service 來進行設定要存取的後端資料庫。
  • 首先,開啟 Microsoft Azure 找到 XamarinAzureDay 行動App ,並且開啟它
  • 在 XamarinAzure 刀鋒視窗中,找到 快速入門 項目,點選這個 快速入門 > Xamarin.Forms
  • 在 Xamarin.Forms 快速入門 刀鋒視窗中,點選 連結資料庫 下方的區塊
  • 在 資料連接 刀鋒視窗中,點選 + 新增 按鈕
  • 在 新增資料連線 刀鋒視窗中,類型欄位,選擇 SQL Database
  • 點選 SQL Database 設定必要設定
  • 在 資料庫 刀鋒視窗中,點選 建立新的資料庫
  • 在 SQL Database 刀鋒視窗中,找到
    名稱欄位,請輸入 XamarinAzureDB
    定價層欄位,請選擇 B基本 (這種資料庫主機服務為:最多 2GB / 異地複寫 / 還原時間點 7 天 / 稽核,預設為 S0 標準方案)
    目標伺服器欄位,在這裡,我們心建立一個測試用的資料庫伺服器,使用設定參數如下:
    • 伺服器名稱:XamarinAzureSQLServer
    • 伺服器管理員登入:請在這裡填入您想要的 SQL Server 管理員帳號
    • 密碼:請在這裡填入該SQL Server管理員的密碼
    • 位置:東南亞
    • 允許 AZURE 服務存取此伺服器:請勾選這個項目
    • 點選 選取 按鈕
    再回到 SQL Database 刀鋒視窗後,點選該視窗下方的 選取 按鈕
  • 當回到 新增資料連線 刀鋒視窗中,點選 連接字串 下方區域
  • 在 連接字串 刀鋒視窗中,點選 確定 按鈕
  • 當回到 新增資料連線 刀鋒視窗中,點選 確定 按鈕
    這個時候,Azure 會開始建立 SQL Server 主機與資料庫,請稍後片刻。
  • SQL Server 與 資料庫建立完成之後,會看到如下畫面。

下載後端伺服器使用的 ASP.NET 專案與發佈到 Azure 上

  • 您可以接續上一個步驟繼續接下來的動作,或者,先回到 Azure 儀表板首頁,找到 xamarinazureday 行動 APP 圖示,點選這個圖示;接著,從 XamarinAzureDay App Service 刀鋒視窗中,點選 快速入門 > Xamarin.Forms
  • 現在,您應該可以看到如下圖的內容,請在 2.建立資料表 API 區塊來進行操作
    設定 後端語言 為 C#
    點選下載按鈕,下載後端專案原始碼

測試與佈署到 Azure 上

  • 在這裡,您可以打開這個 ASP.NET 的專案,建置並確認沒有問題發生
  • 滑鼠右擊專案 XamarinAzureDayService,選擇 發行
  • 在 發行 對話窗,在設定檔中點選 Microsoft Azure App Service
  • 在 App Service 對話窗中,選擇剛剛建立的 App Service 方案,也就是 DoggyXamarin > XamarinAzureDay
  • 點選 `確定 按鈕
  • 在 連接 頁次,點選 驗證連接 按鈕
  • 若 驗證連接 成功,點選 發行 按鈕
  • 此時,您 Azure 上的後端 API 服務,將會有這個專案來取代。

下載 Xamarin.Forms 用戶端專案

  • 您可以接續上一個步驟繼續接下來的動作,或者,先回到 Azure 儀表板首頁,找到 xamarinazureday 行動 APP 圖示,點選這個圖示;接著,從 XamarinAzureDay App Service 刀鋒視窗中,點選 快速入門 > Xamarin.Forms
  • 現在,您應該可以看到如下圖的內容,,請在 3.設定您的用戶端應用程式 區塊來進行操作
    請點選 下載 按鈕,下載 Xamarin.Forms 專案檔案
  • 若您點選了 連接現有應用程式 則會出現底下內容,告訴您如何在您現有的專案中,加入 Microsoft.Azure.Mobile.Client 並且經建立適當程式碼,就可以存取您的 Azure Mobile App 後端服務了。

測試 Xamarin.Forms 專案

  • 在這裡需要實際執行與測試 Azure Mobile App 所提供的 Xamarin.Forms 專案
  • 請打開您剛剛下載的 Xamarin.Forms 專案,使用 Visual Studio 2015 打開 XamarinAzureDay.sln 檔案。
  • 滑鼠右擊 Android 原生專案 ( XamarinAzureDay.Droid ),選擇,設定為起始專案
  • 按下 F5 開始進行這個 Xamarin.Forms 專案偵測
  • 此時,您可以在電腦中的 Android 模擬器中,這個應用程式實際執行結果,如同看到底下畫面
  • 您也可以實際在 Item name 欄位中輸入代辦工作事項名稱,接著點選右方 + 按鈕,此時,這筆紀錄就會儲存到遠端 Azure SQL Server 上了

如何確認資料有寫到遠端 Azure SQL Server 上了

  • 在這裡,我們將會使用 PostMan 來做為展示說明,當然,您也可以在 C# 中實際使用這個方法來呼叫。
  • 首先,打開 XamarinAzureDay App Service 項目。
  • 在刀鋒視窗右上方,找到 URL ,將這個 URL 值複製下來
  • 打開您的 PostMan App
  • 請選擇 Get 方法,在網址列填入 http://xamarinazureday.azurewebsites.net/tables/todoitem
    其中, http://xamarinazureday.azurewebsites.net 是剛剛從 Azure 儀表板中複製下來的網址。
  • 在 Header 頁次中,輸入 Key 為 ZUMO-API-VERSION / value 為 2.0.0
  • 點選最右方的 Send 藍色按鈕,就可以查詢到這個代辦清單表格內的所有資料。

在 Azure 行動應用後端專案,新增差旅費用頁面需要用的資料表

為了要進行這個部分練習,需要建立一個新的SQL Database資料表,因此,我們需要啟用 Code First Migration 功能,來幫助我們依據專案內的資料模型,進行自動調整遠端資料庫內的綱要 (Schema) 內容。
  • 開啟您從 Azure Moible App 項目中下載的 XamarinAzureService 內的 XamarinAzureDay.sln Visual Studio 專案
    下圍呈現了這個 Visual Studio 方案/專案的結構
  • 按下 F5 ,在本地端執行這個專案
    您可以使用 PostMan 來檢查本地端執行與運作結果是否正常
  • 中斷除錯執行
  • 點選功能表 工具 > NuGet封裝管理員 > 套件管理員主控台
  • 在 套件管理員主控台 中輸入底下指令
Enable-Migrations
完成後,會出現底下訊息
Checking if the context targets an existing database...
Detected database created with a database initializer. Scaffolded migration '201702111723152_InitialCreate' corresponding to existing database. To use an automatic migration instead, delete the Migrations folder and re-run Enable-Migrations specifying the -EnableAutomaticMigrations parameter.
Code First Migrations enabled for project XamarinAzureDayService.
  • 接著,執行底下指令
Add-Migration Initial
完成後,會出現底下訊息
Scaffolding migration 'Initial'.
The Designer Code for this migration file includes a snapshot of your current Code First model. This snapshot is used to calculate the changes to your model when you scaffold the next migration. If you make additional changes to your model that you want to include in this migration, then you can re-scaffold it by running 'Add-Migration Initial' again.
  • 打開資料夾 App_Start 底下的 Startup.MobileApp.cs 檔案
  • 在這個檔案最上方加入底下 using
using System.Data.Entity.Migrations;
using XamarinAzureDayService.Migrations;
  • 將這行程式碼註解起來 Database.SetInitializer(new XamarinAzureDayInitializer());
  • 在剛剛註解的程式碼後,加入底下程式碼
            var migrator = new DbMigrator(new Migrations.Configuration());
            migrator.Update();
  • 按下 F5 ,在本地端執行這個專案
    您可以使用 PostMan 來檢查本地端執行與運作結果是否正常
  • 中斷除錯執行

建立資料傳輸物件 (DTO) 類別

接下來,我們需要修改這個後端 API 專案,讓這個專案可以提供更多的功能。
  • 為了要開始產生一個我們自訂的資料表,我們需要定義資料表控制器, 設定資料表控制器需要三個步驟︰
這裡,我們需要定義一個差旅費用的申請紀錄表單,用來定義 SQL Database 內的資料表。
  • 請滑鼠右擊 DataObjects 資料夾,選擇 加入 > 類別
  • 在對話窗下方的 名稱 輸入 BusinessTripExpense
  • 點選 新增 按鈕
  • 請將剛剛產生的類別 BusinessTripExpense,修改成為如下程式碼:
    public class BusinessTripExpense : EntityData
    {
        public DateTime 出差日期 { get; set; }
        public string 類型 { get; set; }
        public string 項目名稱 { get; set; }
        public string 地點 { get; set; }
        public double 費用 { get; set; }
        public string 備註 { get; set; }
        public bool 國內外 { get; set; }
        public bool 是否有單據 { get; set; }
    }
若有找不到的類別或者命名空間,請點選燈泡來修正

EntityData 類別

每個要新增的資料表類別,需要繼承 EntityData 這個類別,這個類別的定義如下。
    public bool Deleted { get; set; }
您可以看到我們並不需要額外定義 Id 作為每筆紀錄的唯一紀錄欄位,因為在 EntityData 內已經幫我們加入進來了 (要實作出 ITableData 這個介面);而其他的欄位 CreatedAt / Deleted / UpdatedAt / Version 將會用於方便 Azure Mobile 服務進行使用 Entity Framework 工作處理之用。
    //
    // 摘要:
    //     An abstract implementation of the Microsoft.Azure.Mobile.Server.Tables.ITableData
    //     interface indicating how the system properties for a given table data model are
    //     to be serialized when communicating with clients when using Entity Framework
    //     for accessing the backend store. The uniform serialization of system properties
    //     ensures that the clients can process the system properties uniformly across platforms.
    //     Concrete entity framework models can derive from this base class in order to
    //     support the system properties.
    public abstract class EntityData : ITableData
    {
        protected EntityData();

        [System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedAttribute]
        [Index(IsClustered = true)]
        [TableColumn(TableColumnType.CreatedAt)]
        public DateTimeOffset? CreatedAt { get; set; }
        [TableColumn(TableColumnType.Deleted)]
        public bool Deleted { get; set; }
        [System.ComponentModel.DataAnnotations.KeyAttribute]
        [TableColumn(TableColumnType.Id)]
        public string Id { get; set; }
        [System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedAttribute]
        [TableColumn(TableColumnType.UpdatedAt)]
        public DateTimeOffset? UpdatedAt { get; set; }
        [TableColumn(TableColumnType.Version)]
        [System.ComponentModel.DataAnnotations.TimestampAttribute]
        public byte[] Version { get; set; }
    }

建立資料表控制器。

如果已安裝 Azure SDK,您現在可以建立範本資料表控制器,請進行底下操作

使用 Azure 行動應用程式資料表控制器

  • 滑鼠右擊 Controller 資料夾,選擇 加入 > 控制器
  • 在對話窗中,選擇 Azure 行動應用程式資料表控制器
  • 點選新增按鈕
  • 在出現 新增控制器 對話窗,請輸入或者選擇如下圖結果
    模型類別:您剛剛定義的 SQL 資料表型別,可用下拉選單來選擇
    資料內容類別:,也就是這個專案內繼承 DbContext 的類別,可用下拉選單來選擇
    控制器名稱:可以使用自動產生的名稱,注意,最後面一定要有 Controller
  • 點選 新增 按鈕,完成這個資料表控制器產生動作。
  • 底下為產生後的 BusinessTripExpenseController 資料控制器程式碼
    您可以看到,對於差旅費用資料表的基本 CRUD API 程式碼,都已經幫您自動產生好了
    這裡請特別注意,在使用 Azure Mobile App 的資料表控制項類別中,必須要繼承 TableController 這個類別,而不是要使用 ApiController 類別
    public class BusinessTripExpenseController : TableController<BusinessTripExpense>
    {
        protected override void Initialize(HttpControllerContext controllerContext)
        {
            base.Initialize(controllerContext);
            XamarinAzureDayContext context = new XamarinAzureDayContext();
            DomainManager = new EntityDomainManager<BusinessTripExpense>(context, Request);
        }

        // GET tables/BusinessTripExpense
        public IQueryable<BusinessTripExpense> GetAllBusinessTripExpense()
        {
            return Query(); 
        }

        // GET tables/BusinessTripExpense/48D68C86-6EA6-4C25-AA33-223FC9A27959
        public SingleResult<BusinessTripExpense> GetBusinessTripExpense(string id)
        {
            return Lookup(id);
        }

        // PATCH tables/BusinessTripExpense/48D68C86-6EA6-4C25-AA33-223FC9A27959
        public Task<BusinessTripExpense> PatchBusinessTripExpense(string id, Delta<BusinessTripExpense> patch)
        {
             return UpdateAsync(id, patch);
        }

        // POST tables/BusinessTripExpense
        public async Task<IHttpActionResult> PostBusinessTripExpense(BusinessTripExpense item)
        {
            BusinessTripExpense current = await InsertAsync(item);
            return CreatedAtRoute("Tables", new { id = current.Id }, current);
        }

        // DELETE tables/BusinessTripExpense/48D68C86-6EA6-4C25-AA33-223FC9A27959
        public Task DeleteBusinessTripExpense(string id)
        {
             return DeleteAsync(id);
        }
    }

加入 Migration

  • 點選功能表 工具 > NuGet封裝管理員 > 套件管理員主控台
  • 在 套件管理員主控台 中輸入底下指令
Add-Migration BusinessTripExpense
  • 按下 F5 ,在本地端執行這個專案
    您可以使用 PostMan 來檢查本地端執行與運作結果是否正常
  • 中斷除錯執行

發佈到 Azure 雲端上

PostMan 測試資料

本地端

Azure

Header 資訊

Key : ZUMO-API-VERSION
Value : 2.0.0

這個練習範例專案,請參考

XamarinAzureService_Custom

在 Xamarin.Forms 專案,修正使用 Azure 行動應用的線上資料表

在這裡,我們將要把我們寫好的 Xamarin.Forms 專案,加入使用 Azure 行動應用服務功能。

第一次準備工作

在您原先做好的 Xamarin.Forms 專案中,需要加入相關 NuGet 套件與建立一個 MobileServiceClient 物件

加入 NuGet 套件

  • 使用 Visual Studio 2015 打開 XFDoggy.sln 專案
  • 滑鼠右擊 方案XFDoggy,選擇 管理方案的 NuGet套件
  • 在 瀏覽 標籤頁次的搜尋文字輸入盒內,填入 Microsoft.Azure.Mobile.Client 搜尋這個套件
  • 點選所有專案都要安裝這個套件,並點選 安裝 按鈕

加入 MobileServiceClient 物件

  • 滑鼠右擊核心PCL專案的 XFDoggy,點選 加入 > 新增資料夾
  • 輸入 Helpers
  • 滑鼠右擊核心PCL專案的 核心PCL專案 資料夾,點選 加入 > 類別
  • 在對話窗中,點選 Visual C# > 類別
  • 在名稱欄位輸入 MainHelper,最後點選 新增 按鈕
  • 將 MainHelper 類別修改成底下的程式碼
    請修正相關命名空間錯誤
    public class MainHelper
    {
        /// <summary>
        /// 指向 Azure Mobile App 服務的主要網址
        /// </summary>
        public static string MainURL = "https://xamarinazureday.azurewebsites.net";
        /// <summary>
        /// Azure Mobile App 線上版本的用戶端
        /// </summary>
        public static MobileServiceClient client = new MobileServiceClient(MainURL);
    }
關於建立 MobileServiceClient 物件的時候,需要傳遞的 URL 參數 ( 行動應用的主要 URL ),您可以從 Azure 儀表板中,找到 XamarinAzureDay 行動應用程式,在這個 XamarinAuzreDay App Service 刀鋒視窗中,預設會開啟 概觀 標籤頁次,您可以從右方看到 URL 欄位,這裡就是這個行動應用的主要 URL。
請使用 Https 的通訊協定

修改 Xamarin.Forms 專案

在這裡,我們將要修正 差旅費用 申請作業的相關功能;在這裡,我們需要修正兩個頁面的 ViewModel,分別是,顯示清單與紀錄新增或修改頁面。原則上,我們在這裡並不會修正原有這個頁面中 Xamarin.Forms 的程式處理邏輯。

建立資料表會用到的資料模型

  • 滑鼠右擊核心PCL專案的 Models 資料夾,點選 加入 > 類別
  • 在對話窗中,點選 Visual C# > 類別
  • 在名稱欄位輸入 BusinessTripExpense,最後點選 新增 按鈕
  • 將 BusinessTripExpense 類別修改成底下的程式碼
    public class BusinessTripExpense
    {
        public string Id { get; set; }
        public DateTime 出差日期 { get; set; }
        public string 類型 { get; set; }
        public string 項目名稱 { get; set; }
        public string 地點 { get; set; }
        public double 費用 { get; set; }
        public string 備註 { get; set; }
        public bool 國內外 { get; set; }
        public bool 是否有單據 { get; set; }
    }

在 ViewMolde 加入這個資料表的 CRUD 行動應用服務功能。

  • 在核心PCL專案內,找到資料夾 ViewModels 內的 差旅費用申請HomePageViewModel.cs
  • 滑鼠雙擊這個檔案,打開它

宣告 Azure 行動應用服務資料物件

  • 我們需要加入 Azure 行動應用服務的資料表宣告,這樣,我們才能夠針對遠端 SQL Server 資料庫內的資料表,進行 CRUD 處理。
    請解析要加入的適當命名空間
        IMobileServiceTable<BusinessTripExpense> 差旅費用Table = MainHelper.client.GetTable<BusinessTripExpense>();

查詢/排序/過濾紀錄

  • 要從遠端SQL資料庫內查詢資料表內的所有資料,可以使用底下程式碼。
    您可以針對 差旅費用Table 物件,使用 LINQ 方法,進行所要查詢資料的條件、排序行為等等工作,Azure 行動應用服務物件會將您的需求透過 OData 要求轉譯成 SQL 查詢。
            #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

新增紀錄

  • 要新增一筆資料到遠端SQL資料庫內,可以使用底下程式碼
    產生一個新的資料表紀錄物件,呼叫 差旅費用Table.InsertAsync,便可新增一筆紀錄到遠端SQL資料庫內
    在這裡,請不要指定 Id 屬性有任何值,就樣才能夠讓 Azure 行動應用用戶端幫我們新增一筆記錄到資料表中
                        #region Azure 行動應用服務的新增
                        foo差旅費用 = new BusinessTripExpense
                        {
                            備註 = x.差旅費用項目.備註,
                            出差日期 = x.差旅費用項目.出差日期,
                            國內外 = x.差旅費用項目.國內外,
                            地點 = x.差旅費用項目.地點,
                            是否有單據 = x.差旅費用項目.是否有單據,
                            費用 = x.差旅費用項目.費用,
                            項目名稱 = x.差旅費用項目.項目名稱,
                            類型 = x.差旅費用項目.類型,
                        };
                        await 差旅費用Table.InsertAsync(foo差旅費用);
                        await Init();
                        #endregion

修改紀錄

  • 要修改一筆資料到遠端SQL資料庫內,可以使用底下程式碼
    差旅費用Table.UpdateAsync,便可修改遠端SQL資料庫的紀錄
                        #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

刪除紀錄

  • 要刪除一筆資料到遠端SQL資料庫內,可以使用底下程式碼
    差旅費用Table.DeleteAsync,便可刪除遠端SQL資料庫的紀錄
                        #region Azure 行動應用服務的新增
                        foo差旅費用 = await 差旅費用Table.LookupAsync(x.差旅費用項目.ID);
                        await 差旅費用Table.DeleteAsync(foo差旅費用);
                        #endregion

完整的 差旅費用申請HomePage View / ViewModels

差旅費用申請HomePage.xaml

這裡修正了一些物件名稱
<?xml version="1.0" encoding="utf-8" ?>
<!--NavigationPage.BackButtonTitle="上一頁" 這個設定,主要是針對 iOS 平台來使用到的-->
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
             xmlns:localControls="clr-namespace:XFDoggy.CustomControls"
             xmlns:behaviors="clr-namespace:XFDoggy.Behaviors"
             prism:ViewModelLocator.AutowireViewModel="True"
             x:Class="XFDoggy.Views.差旅費用申請HomePage"
             Title="差旅費用申請"
             x:Name="ThisPage"
             NavigationPage.BackButtonTitle="上一頁">

    <!--這個頁面的版面配置-->
    <!--Data Binding 資料繫結請參考:https://developer.xamarin.com/guides/xamarin-forms/xaml/xaml-basics/data_bindings_to_mvvm/-->
    <Grid
        RowSpacing="0" ColumnSpacing="0"
        BackgroundColor="{StaticResource 頁面背景Color}"
        >
        <!--該頁面的主要背景顏色設定-->
        <Grid
            RowSpacing="0" ColumnSpacing="0"
            Margin="10"
            BackgroundColor="{StaticResource 頁面內容本文背景Color}"
            >
            <Grid.RowDefinitions>
                <!--這個部分為頁面標題與文字的空間規劃-->
                <RowDefinition Height="40"/>
                <!--這個部分是其他內容區域-->
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>

            <!--頁面標題與文字-->
            <Grid
                RowSpacing="0" ColumnSpacing="0"
                BackgroundColor="{StaticResource 頁面內容標題背景Color}"
                >
                <Grid.RowDefinitions>
                    <RowDefinition Height="40"/>
                </Grid.RowDefinitions>
                <Label
                    Text="差旅費用申請清單"                    
                    Style="{StaticResource 頁面內文標題文字Style}"
                    />
            </Grid>

            <!--清單資料-->
            <!--ListView 請參考:https://developer.xamarin.com/api/type/Xamarin.Forms.ListView/-->
            <ListView
                HorizontalOptions="Fill" VerticalOptions="Fill"
                Grid.Row="1"
                ItemsSource="{Binding 差旅費用項目清單}"
                SelectedItem="{Binding 點選差旅費用項目, Mode=TwoWay}"
                SeparatorVisibility="None"
                HasUnevenRows="True"
                >
                <!--這個部分為要使用 MVVM 綁定命令方式,將要透過 XAML Behavior來做到-->
                <!--Xamarin.Forms Behavior 請參考:https://developer.xamarin.com/guides/xamarin-forms/behaviors/-->
                <ListView.Behaviors>
                    <behaviors:EventHandlerBehavior EventName="ItemTapped">
                        <behaviors:InvokeCommandAction Command="{Binding 點選差旅費用項目Command}"  />
                    </behaviors:EventHandlerBehavior>
                </ListView.Behaviors>
                <ListView.ItemTemplate>
                    <!--定義每筆紀錄要出現的樣貌-->
                    <DataTemplate>
                        <!--ViewCell 請參考:https://developer.xamarin.com/api/type/Xamarin.Forms.ViewCell/-->
                        <ViewCell>
                            <Grid
                                RowSpacing="0" ColumnSpacing="0"
                                >
                                <StackLayout
                                    Orientation="Vertical"
                                    >
                                    <Label
                                        Margin="10,0"
                                        Text="{Binding 出差日期, StringFormat='{0:yyyy-MM-dd}'}}"
                                        TextColor="Black"
                                        FontSize="24"
                                        HorizontalOptions="Start" VerticalOptions="Start"
                                        LineBreakMode="WordWrap">
                                    </Label>
                                    <StackLayout
                                        Orientation="Horizontal"
                                        Margin="0,5">
                                        <Label
                                            Margin="10,0,0,0"
                                            Text="{Binding 類型}"
                                            TextColor="Black"
                                            FontSize="14"
                                            HorizontalOptions="Start" VerticalOptions="Start"
                                            LineBreakMode="TailTruncation"
                                            WidthRequest="150">
                                        </Label>
                                        <Label
                                            Margin="10,0,10,0"
                                            Text="{Binding 費用, StringFormat='{0} 元'}"
                                            TextColor="Black"
                                            FontSize="14"
                                            HorizontalOptions="End" VerticalOptions="Start"
                                            LineBreakMode="TailTruncation">
                                        </Label>
                                    </StackLayout>
                                </StackLayout>

                                <!--分隔線-->
                                <BoxView 
                                    HorizontalOptions="Fill" VerticalOptions="End"
                                    HeightRequest="2"
                                    Color="#8c8c8c" />
                            </Grid>
                        </ViewCell>
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>

        </Grid>
        <!--新增按鈕-->
        <!--這裡採用浮動式的按鈕設計-->
        <Grid
            RowSpacing="0" ColumnSpacing="0"
            HorizontalOptions="End" VerticalOptions="End"
            Margin="0,0,30,30"
            >
            <BoxView Color="Black" WidthRequest="50" HeightRequest="50"/>
            <localControls:FontAwesomeLabel 
                Text="&#xf067;"
                Font="36"
                TextColor="White"
                HorizontalOptions="Center" VerticalOptions="Center"
                >
                <localControls:FontAwesomeLabel.GestureRecognizers>
                    <TapGestureRecognizer Command="{Binding 新增按鈕Command}" />
                </localControls:FontAwesomeLabel.GestureRecognizers>
            </localControls:FontAwesomeLabel>
        </Grid>
    </Grid>

</ContentPage>

差旅費用申請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

    }

iOS 原生專案修正

  • 打開 iOS 原生專案中 AppDelegate.cs 檔案
  • 在類別 AppDelegate 的 FinishedLaunching 方法內的呼叫 FinishedLaunching 方法前,加入底下程式碼
            Microsoft.WindowsAzure.MobileServices.CurrentPlatform.Init();

實際執行結果

在這裡,我們使用 Android 平台做為測試標的
我們在這裡可以新增任意數量紀錄,也可以隨意修改與刪除

檢測後端資料庫儲存結果

您可以使用 PostMan 來取得後端SQL資料庫內的 差旅費用 資料表,實際儲存的紀錄內容。
在這裡,我們將會使用底下 URL 來查詢(記得要加入適當的 Header)
底下是實際查詢結果內容
[
  {
    "deleted": false,
    "updatedAt": "2017-02-12T07:36:18.204Z",
    "createdAt": "2017-02-12T07:31:55.314Z",
    "version": "AAAAAAAACAE=",
    "id": "6de91a3368964410b514d50e22a3f1a6",
    "是否有單據": false,
    "國內外": true,
    "備註": null,
    "費用": 5200,
    "地點": "Taipei",
    "項目名稱": "Accommondation cost",
    "類型": "Hotel",
    "出差日期": "2017-02-11T16:00:00Z"
  },
  {
    "deleted": false,
    "updatedAt": "2017-02-12T07:30:20.064Z",
    "createdAt": "2017-02-12T07:30:20.064Z",
    "version": "AAAAAAAAB/w=",
    "id": "dbdf03a4240b4a84b9e0ac9d42510182",
    "是否有單據": false,
    "國內外": true,
    "備註": "By Uber",
    "費用": 310,
    "地點": "Taipei",
    "項目名稱": "Airport to Hotel",
    "類型": "Traffic Taxi",
    "出差日期": "2017-02-10T16:00:00Z"
  }
]

在 Azure 行動應用後端專案,新增我要請假頁面需要用的資料表

請接續進行 Azure行動應用服務的後端專案

建立資料傳輸物件 (DTO) 類別

接下來,我們需要修改這個後端 API 專案,讓這個專案可以提供更多的功能。
  • 為了要開始產生一個我們自訂的資料表,我們需要定義資料表控制器, 設定資料表控制器需要三個步驟︰
這裡,我們需要定義一個請假紀錄的申請紀錄表單,用來定義 SQL Database 內的資料表。
  • 請滑鼠右擊 DataObjects 資料夾,選擇 加入 > 類別
  • 在對話窗下方的 名稱 輸入 LeaveRecord
  • 點選 新增 按鈕
  • 請將剛剛產生的類別 LeaveRecord,修改成為如下程式碼:
    public class LeaveRecord : EntityData
    {
        public string 申請人 { get; set; }
        public DateTime 請假日期 { get; set; }
        public TimeSpan 開始時間 { get; set; }
        public int 請假時數 { get; set; }
        public string 假別 { get; set; }
        public string 請假理由 { get; set; }
        public string 職務代理人 { get; set; }
    }
若有找不到的類別或者命名空間,請點選燈泡來修正

建立資料表控制器。

如果已安裝 Azure SDK,您現在可以建立範本資料表控制器,請進行底下操作

使用 Azure 行動應用程式資料表控制器

  • 滑鼠右擊 Controller 資料夾,選擇 加入 > 控制器
  • 在對話窗中,選擇 Azure 行動應用程式資料表控制器
  • 點選新增按鈕
  • 在出現 新增控制器 對話窗,請輸入或者選擇如下圖結果
    模型類別:您剛剛定義的 SQL 資料表型別,可用下拉選單來選擇
    資料內容類別:,也就是這個專案內繼承 DbContext 的類別,可用下拉選單來選擇
    控制器名稱:可以使用自動產生的名稱,注意,最後面一定要有 Controller
  • 點選 新增 按鈕,完成這個資料表控制器產生動作。
  • 底下為產生後的 LeaveRecordController 資料控制器程式碼
    您可以看到,對於請假紀錄資料表的基本 CRUD API 程式碼,都已經幫您自動產生好了
    這裡請特別注意,在使用 Azure Mobile App 的資料表控制項類別中,必須要繼承 TableController 這個類別,而不是要使用 ApiController 類別
    public class LeaveRecordController : TableController<LeaveRecord>
    {
        protected override void Initialize(HttpControllerContext controllerContext)
        {
            base.Initialize(controllerContext);
            XamarinAzureDayContext context = new XamarinAzureDayContext();
            DomainManager = new EntityDomainManager<LeaveRecord>(context, Request);
        }

        // GET tables/LeaveRecord
        public IQueryable<LeaveRecord> GetAllLeaveRecord()
        {
            return Query(); 
        }

        // GET tables/LeaveRecord/48D68C86-6EA6-4C25-AA33-223FC9A27959
        public SingleResult<LeaveRecord> GetLeaveRecord(string id)
        {
            return Lookup(id);
        }

        // PATCH tables/LeaveRecord/48D68C86-6EA6-4C25-AA33-223FC9A27959
        public Task<LeaveRecord> PatchLeaveRecord(string id, Delta<LeaveRecord> patch)
        {
             return UpdateAsync(id, patch);
        }

        // POST tables/LeaveRecord
        public async Task<IHttpActionResult> PostLeaveRecord(LeaveRecord item)
        {
            LeaveRecord current = await InsertAsync(item);
            return CreatedAtRoute("Tables", new { id = current.Id }, current);
        }

        // DELETE tables/LeaveRecord/48D68C86-6EA6-4C25-AA33-223FC9A27959
        public Task DeleteLeaveRecord(string id)
        {
             return DeleteAsync(id);
        }
    }

加入 Migration

  • 點選功能表 工具 > NuGet封裝管理員 > 套件管理員主控台
  • 在 套件管理員主控台 中輸入底下指令
Add-Migration LeaveRecord
  • 按下 F5 ,在本地端執行這個專案
    您可以使用 PostMan 來檢查本地端執行與運作結果是否正常
  • 中斷除錯執行

發佈到 Azure 雲端上

PostMan 測試資料

本地端

Azure

Header 資訊

Key : ZUMO-API-VERSION
Value : 2.0.0

這個練習範例專案,請參考

XamarinAzureService_Custom
+


在 Xamarin.Forms 專案,修正使用 Azure 行動應用的離線存取資料表功能

在這裡,我們將要把我們寫好的 Xamarin.Forms 專案,加入使用 Azure 行動應用服務功能,並且要能夠使用離線存取功能。

第一次準備工作

在您原先做好的 Xamarin.Forms 專案中,需要加入相關 NuGet 套件與建立一個 MobileServiceClient 物件

加入 NuGet 套件

  • 使用 Visual Studio 2015 打開 XFDoggy.sln 專案
  • 滑鼠右擊 方案XFDoggy,選擇 管理方案的 NuGet套件
  • 在 瀏覽 標籤頁次的搜尋文字輸入盒內,填入 Microsoft.Azure.Mobile.Client.SQLiteStore 搜尋這個套件
  • 點選核心PCL、Android、iOS 專案都要安裝這個套件,並點選 安裝 按鈕
    UWP 專案,請先不要安裝這個套件

加入 MobileServiceClient 離線初始化方法

  • 在核心PCL專案的 Helpers 資料夾內,開啟 MainHelper.cs 檔案
  • 將 MainHelper 類別修改成底下的程式碼
    因為有些類別尚未新增進來,稍後再來修正相關命名空間錯誤
    這裡設定本地端的 SQLite 資料庫檔案名稱為 "localstore.db"
    要使用離線功能,需要在進行存取離線功能前,做些初始化動作,AzureMobileOfflineInit() 將是要做這些事情。
using Microsoft.WindowsAzure.MobileServices;
using Microsoft.WindowsAzure.MobileServices.SQLiteStore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using XFDoggy.Models;
using XFDoggy.Repositories;

namespace XFDoggy.Helpers
{
    public static class MainHelper
    {
        /// <summary>
        /// 指向 Azure Mobile App 服務的主要網址
        /// </summary>
        public static string MainURL = "https://xamarinazureday.azurewebsites.net";
        /// <summary>
        /// Azure Mobile App 線上版本的用戶端
        /// </summary>
        public static MobileServiceClient client = new MobileServiceClient(MainURL);
        /// <summary>
        /// 設定離線資料庫的檔案名稱
        /// </summary>
        public static string offlineDbPath = @"localstore.db";
        /// <summary>
        /// Azure Mobile App 離線版本的SQLite
        /// </summary>
        public static MobileServiceSQLiteStore store = new MobileServiceSQLiteStore(offlineDbPath);

        public static 請假紀錄Manager 請假紀錄Manager = new 請假紀錄Manager();

        /// <summary>
        /// 進行 Azure Mobile App 的離線資料庫初始化動作
        /// </summary>
        public static void AzureMobileOfflineInit()
        {
            //取得Azure Mobile App 線上版本的用戶端
            var store = MainHelper.store;
            // 定義要用到的離線資料表
            store.DefineTable<LeaveRecord>();
            // 進行離線資料庫初始化
            MainHelper.client.SyncContext.InitializeAsync(store);
        }
    }
}
關於建立 MobileServiceClient 物件的時候,需要傳遞的 URL 參數 ( 行動應用的主要 URL ),您可以從 Azure 儀表板中,找到 XamarinAzureDay 行動應用程式,在這個 XamarinAuzreDay App Service 刀鋒視窗中,預設會開啟 概觀 標籤頁次,您可以從右方看到 URL 欄位,這裡就是這個行動應用的主要 URL。
在這裡,請使用 Https 通訊協定

設定啟動應用程式的時候,要進行離線資料庫初始化

  • 在核心 PCL 專案中,找到並打開 App.xaml.cs 檔案
  • 將 OnInitialized 方法修改成為如下程式碼
    請修正相關命名空間錯誤
        protected override void OnInitialized()
        {
            InitializeComponent();

            //NavigationService.NavigateAsync($"xf:///MDPage?Menu={MenuItemEnum.關於.ToString()}/NaviPage/MainPage?title=多奇數位創意有限公司");

            #region 進行離線資料庫初始化
            MainHelper.AzureMobileOfflineInit();
            #endregion

            NavigationService.NavigateAsync($"LoginPage");
        }

修改 Xamarin.Forms 專案

在這裡,我們將要修正 我要請假 申請作業的相關功能;在這裡,我們需要修正兩個頁面的 ViewModel,分別是,顯示清單與紀錄新增或修改頁面。原則上,我們在這裡並不會修正原有這個頁面中 Xamarin.Forms 的程式處理邏輯。

建立資料表會用到的資料模型

  • 滑鼠右擊核心PCL專案的 Models 資料夾,點選 加入 > 類別
  • 在對話窗中,點選 Visual C# > 類別
  • 在名稱欄位輸入 LeaveRecord,最後點選 新增 按鈕
  • 將 LeaveRecord 類別修改成底下的程式碼
    這裡我們需要加入 Version 欄位,這是要用於判斷本地端與伺服器端的紀錄版本是否一致。
    public class LeaveRecord
    {
        public string Id { get; set; }
        public string 申請人 { get; set; }
        public DateTime 請假日期 { get; set; }
        public TimeSpan 開始時間 { get; set; }
        public int 請假時數 { get; set; }
        public string 假別 { get; set; }
        public string 請假理由 { get; set; }
        public string 職務代理人 { get; set; }
        [Version]
        public string Version { get; set; }
    }

建立 請假紀錄Manager

  • 滑鼠右擊核心PCL專案的 Repositories 資料夾,點選 加入 > 類別
  • 在對話窗中,點選 Visual C# > 類別
  • 在名稱欄位輸入 請假紀錄Manager,最後點選 新增 按鈕
  • 將 請假紀錄Manager 類別修改成底下的程式碼
    請修正相關命名空間錯誤
    /// <summary>
    /// 進行請假記錄的檔案存取功能
    /// </summary>
    public class 請假紀錄Manager
    {
        // 取得 Azure Mobile App 中的 請假紀錄 離線資料表物件
        public IMobileServiceSyncTable<LeaveRecord> 請假紀錄Table;
        // 進行同步工作的時候,產生的錯誤訊息
        public string ErrorMessage = "";

        public 請假紀錄Manager()
        {
            // 取得 請假記錄 離線資料表物件
            請假紀錄Table = MainHelper.client.GetSyncTable<LeaveRecord>();
        }

        /// <summary>
        /// 取得請假記錄清單
        /// </summary>
        /// <param name="是否要先進行同步工作"></param>
        /// <returns></returns>
        public async Task<ObservableCollection<LeaveRecord>> GetAsync(bool 是否要先進行同步工作 = false)
        {
            ErrorMessage = "";
            try
            {
                if (是否要先進行同步工作)
                {
                    // 將本地端的異動與伺服器端的紀錄進行同步處理
                    ErrorMessage = await SyncAsync();
                }

                // 取得所有的請假記錄集合
                IEnumerable<LeaveRecord> items = await 請假紀錄Table
                    .OrderByDescending(x => x.請假日期)
                    .ToEnumerableAsync();

                return new ObservableCollection<LeaveRecord>(items);
            }
            catch (MobileServiceInvalidOperationException msioe)
            {
                ErrorMessage = string.Format(@"Invalid sync operation: {0}", msioe.Message);
            }
            catch (Exception e)
            {
                ErrorMessage = string.Format(@"Sync error: {0}", e.Message);
            }
            return null;
        }

        /// <summary>
        /// 使用 Id 鍵值進行查詢出這筆紀錄
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public async Task<LeaveRecord> LookupAsync(string id)
        {
            var fooObj = await 請假紀錄Table.LookupAsync(id);
            return fooObj;
        }

        /// <summary>
        /// 進行記錄更新
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        public async Task UpdateAsync(LeaveRecord item)
        {
            await 請假紀錄Table.UpdateAsync(item);
        }

        /// <summary>
        /// 新增一筆紀錄
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        public async Task InsertAsync(LeaveRecord item)
        {
            await 請假紀錄Table.InsertAsync(item);
        }

        /// <summary>
        /// 刪除這筆紀錄
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        public async Task DeleteAsync(LeaveRecord item)
        {
            await 請假紀錄Table.DeleteAsync(item);
        }

        /// <summary>
        /// 進行資料同步,並且取得最新線上的紀錄
        /// </summary>
        /// <returns></returns>
        public async Task<string> SyncAsync()
        {
            var fooResult = "";
            ReadOnlyCollection<MobileServiceTableOperationError> syncErrors = null;
            try
            {
                var fooClient = MainHelper.client;
                await fooClient.SyncContext.PushAsync();

                // 這裡可以設定要取得那些資料
                var fooK = 請假紀錄Table.Where(x => x.請假日期 > DateTime.Now.AddMonths(-3));

                await 請假紀錄Table.PullAsync(
                    //The first parameter is a query name that is used internally by the client SDK to implement incremental sync.
                    //Use a different query name for each unique query in your program
                    "請假紀錄", fooK);
            }
            catch (MobileServicePushFailedException exc)
            {
                if (exc.PushResult != null)
                {
                    syncErrors = exc.PushResult.Errors;
                }
            }

            // Simple error/conflict handling. A real application would handle the various errors like network conditions,
            // server conflicts and others via the IMobileServiceSyncHandler.
            if (syncErrors != null)
            {
                foreach (var error in syncErrors)
                {
                    if (error.OperationKind == MobileServiceTableOperationKind.Update && error.Result != null)
                    {
                        //這裡要解決本地與伺服器端資料衝突的問題,底下是使用伺服器版本,蓋掉本機版本
                        await error.CancelAndUpdateItemAsync(error.Result);
                    }
                    else
                    {
                        // 取消這筆本地端的資料異動,該異動紀錄將會從本地端移除
                        //await error.CancelAndDiscardItemAsync();
                    }

                    fooResult = @"Error executing sync operation. Item: {error.TableName} (" + error.Item["id"] + "). Operation discarded.";
                }
            }
            return fooResult;
        }
    }

在 ViewMolde 加入這個資料表的 CRUD 行動應用服務功能

我要請假HomePage.xaml

  • 在核心PCL專案內,找到資料夾 Views 內的 我要請假HomePage.xaml
  • 請使用底下 XAML 進行替換這個檔案內容
  • 在這裡,我們需要在導航工具列中,加入一個 更新 按鈕,用來進行離線資料同步之用
    要這麼做的目的哪是,在這個頁面之中的所有紀錄異動,新增 修改 刪除 查詢,都將會使用離線資料庫來進行操作,當按下這個按鈕的時候,我們需要進行與遠端資料庫內的資料表進行同步更新動作。
<?xml version="1.0" encoding="utf-8" ?>
<!--NavigationPage.BackButtonTitle="上一頁" 這個設定,主要是針對 iOS 平台來使用到的-->
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
             xmlns:localControls="clr-namespace:XFDoggy.CustomControls"
             xmlns:behaviors="clr-namespace:XFDoggy.Behaviors"
             xmlns:converters="clr-namespace:XFDoggy.Converters"
             prism:ViewModelLocator.AutowireViewModel="True"
             x:Class="XFDoggy.Views.我要請假HomePage"
             Title="我要請假"
             x:Name="ThisPage"
             NavigationPage.BackButtonTitle="上一頁">

    <ContentPage.Resources>
        <!--ResourceDictionary 請參考:https://developer.xamarin.com/api/type/Xamarin.Forms.ResourceDictionary/-->
        <ResourceDictionary>
            <!--在這裡宣告一個數值轉換器 Converter,透過 x:Key 名字,可以在這個頁面中使用
            若這個數值轉換器在很多頁面都會用到,可以在 App.xaml 中來宣告-->
            <converters:假別Converter x:Key="假別Converter"/>
        </ResourceDictionary>
    </ContentPage.Resources>

    <!--在導航工具列上,顯示一個可以新增紀錄的按鈕-->
    <ContentPage.ToolbarItems>
        <!--新增按鈕-->
        <ToolbarItem Text="新增" Command="{Binding 點選新增請假紀錄項目Command}" />
        <ToolbarItem Text="更新" Command="{Binding 點選更新請假紀錄項目Command}" />
    </ContentPage.ToolbarItems>

    <!--這個頁面的版面配置-->
    <!--Data Binding 資料繫結請參考:https://developer.xamarin.com/guides/xamarin-forms/xaml/xaml-basics/data_bindings_to_mvvm/-->
    <Grid
        RowSpacing="0" ColumnSpacing="0"
        BackgroundColor="{StaticResource 頁面背景Color}"
        >
        <!--該頁面的主要背景顏色設定-->
        <Grid
            RowSpacing="0" ColumnSpacing="0"
            Margin="10"
            BackgroundColor="{StaticResource 頁面內容本文背景Color}"
            >
            <Grid.RowDefinitions>
                <!--這個部分為頁面標題與文字的空間規劃-->
                <RowDefinition Height="40"/>
                <!--這個部分是其他內容區域-->
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>

            <!--頁面標題與文字-->
            <Grid
                RowSpacing="0" ColumnSpacing="0"
                BackgroundColor="{StaticResource 頁面內容標題背景Color}"
                >
                <Grid.RowDefinitions>
                    <RowDefinition Height="40"/>
                </Grid.RowDefinitions>
                <Label
                    Text="請假記錄清單"                    
                    Style="{StaticResource 頁面內文標題文字Style}"
                    />
            </Grid>

            <!--清單資料-->
            <!--ListView 請參考:https://developer.xamarin.com/api/type/Xamarin.Forms.ListView/-->
            <ListView
                HorizontalOptions="Fill" VerticalOptions="Fill"
                Grid.Row="1"
                ItemsSource="{Binding 請假紀錄項目清單}"
                SelectedItem="{Binding 點選請假紀錄項目, Mode=TwoWay}"
                SeparatorVisibility="None"
                HasUnevenRows="True"
                >
                <!--這個部分為要使用 MVVM 綁定命令方式,將要透過 XAML Behavior來做到-->
                <!--Xamarin.Forms Behavior 請參考:https://developer.xamarin.com/guides/xamarin-forms/behaviors/-->
                <ListView.Behaviors>
                    <behaviors:EventHandlerBehavior EventName="ItemTapped">
                        <behaviors:InvokeCommandAction Command="{Binding 點選請假紀錄項目Command}"  />
                    </behaviors:EventHandlerBehavior>
                </ListView.Behaviors>
                <ListView.ItemTemplate>
                    <!--定義每筆紀錄要出現的樣貌-->
                    <DataTemplate>
                        <!--ViewCell 請參考:https://developer.xamarin.com/api/type/Xamarin.Forms.ViewCell/-->
                        <ViewCell>
                            <Grid
                                RowSpacing="0" ColumnSpacing="0"
                                >
                                <Grid.RowDefinitions>
                                    <RowDefinition Height="*" />
                                    <RowDefinition Height="*" />
                                </Grid.RowDefinitions>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="70" />
                                    <ColumnDefinition Width="*" />
                                </Grid.ColumnDefinitions>

                                <!--最左方的假別-->
                                <Grid
                                    RowSpacing="0" ColumnSpacing="0"
                                    Grid.Row="0" Grid.Column="0"
                                    Grid.RowSpan="2"
                                    >
                                    <BoxView
                                        HorizontalOptions="Fill" VerticalOptions="Fill"
                                        Color="Black"/>
                                    <localControls:FontAwesomeLabel 
                                        Text="{Binding 假別, Converter={StaticResource 假別Converter}}"
                                        Font="36"
                                        TextColor="White"
                                        HorizontalOptions="Center" VerticalOptions="Center"
                                        />
                                </Grid>

                                <!--請假日期-->
                                <Grid
                                    RowSpacing="0" ColumnSpacing="0"
                                    Grid.Row="0" Grid.Column="1"
                                    >
                                    <Label
                                        Margin="10,0"
                                        Text="{Binding 請假日期, StringFormat='{0:yyyy-MM-dd}'}"
                                        TextColor="Black"
                                        FontSize="24"
                                        HorizontalOptions="Start" VerticalOptions="Start"
                                        LineBreakMode="WordWrap">
                                    </Label>
                                </Grid>

                                <!--開始時間-->
                                <Grid
                                    RowSpacing="0" ColumnSpacing="0"
                                    Grid.Row="1" Grid.Column="1"
                                    >
                                    <StackLayout
                                        Orientation="Horizontal"
                                        Spacing="0"
                                        >
                                        <Label
                                            Margin="10,0"
                                            Text="{Binding 開始時間, StringFormat='{0}'}"
                                            TextColor="Black"
                                            FontSize="24"
                                            HorizontalOptions="Start" VerticalOptions="Start"
                                            >
                                        </Label>
                                        <Label
                                            Margin="10,0"
                                            Text="{Binding 請假時數, StringFormat='{0}小時'}}"
                                            TextColor="Black"
                                            FontSize="24"
                                            HorizontalOptions="Start" VerticalOptions="Start"
                                            >
                                        </Label>
                                    </StackLayout>
                                </Grid>

                                <!--分隔線-->
                                <BoxView 
                                    Grid.Row="0" Grid.Column="0"
                                    Grid.RowSpan="2" Grid.ColumnSpan="2"
                                    HorizontalOptions="Fill" VerticalOptions="End"
                                    HeightRequest="2"
                                    Color="#8c8c8c" />
                            </Grid>
                        </ViewCell>
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>

        </Grid>
    </Grid>

</ContentPage>

差旅費用申請HomePageViewModel.cs

  • 在核心PCL專案內,找到資料夾 ViewModels 內的 我要請假HomePageViewModel.cs
  • 請使用底下 C# 程式碼 進行替換這個檔案內容
  • 由於這個頁面功能,當初是使用靜態資料,因此,我們要修改 ViewModel ,讓它可以使用 Azure 行動應用服務中的離線資料庫功能。
  • 我們修正了 Init 方法,接收一個參數,用來判斷是否要執行同步更新
  • 由於在進行同步更新動作的時候,會有很多突發異常,因此,我們需要將 MainHelper.請假紀錄Manager.GetAsync 呼叫,使用 try ... catch 捕捉起來
  • 其他的修正則是要使用 MainHelper.請假紀錄Manager 這個物件,來進行資料存取動作
using Prism.Commands;
using Prism.Mvvm;
using System;
using System.Collections.Generic;
using System.Linq;
using Prism.Navigation;
using Prism.Events;
using XFDoggy.Repositories;
using XFDoggy.Models;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using XFDoggy.Helpers;
using Microsoft.WindowsAzure.MobileServices;
using Microsoft.WindowsAzure.MobileServices.Sync;
using Microsoft.WindowsAzure.MobileServices.SQLiteStore;
using Prism.Services;

namespace XFDoggy.ViewModels
{
    public class 我要請假HomePageViewModel : BindableBase, INavigationAware
    {

        #region Repositories (遠端或本地資料存取)
        //請假紀錄Repository foo請假紀錄Repository = new 請假紀錄Repository();
        #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>
        /// 點選請假紀錄項目
        /// </summary>
        public 請假紀錄項目ViewModel 點選請假紀錄項目
        {
            get { return this._點選請假紀錄項目; }
            set { this.SetProperty(ref this._點選請假紀錄項目, value); }
        }
        #endregion

        #endregion

        #region Field 欄位

        public DelegateCommand 點選請假紀錄項目Command { get; set; }
        public DelegateCommand 點選新增請假紀錄項目Command { get; set; }
        public DelegateCommand 點選更新請假紀錄項目Command { get; set; }

        public readonly IPageDialogService _dialogService;
        private readonly INavigationService _navigationService;
        private readonly IEventAggregator _eventAggregator;
        #endregion

        #region Constructor 建構式
        public 我要請假HomePageViewModel(INavigationService navigationService, IEventAggregator eventAggregator,
            IPageDialogService dialogService)
        {

            #region 相依性服務注入的物件

            _dialogService = dialogService;
            _eventAggregator = eventAggregator;
            _navigationService = navigationService;
            #endregion

            #region 頁面中綁定的命令
            點選請假紀錄項目Command = new DelegateCommand(async () =>
            {
                var fooID = 點選請假紀錄項目.ID;
                點選請假紀錄項目 = null;
                await _navigationService.NavigateAsync($"我要請假記錄修改Page?ID={fooID}");
            });
            點選新增請假紀錄項目Command = new DelegateCommand(async () =>
            {
                await _navigationService.NavigateAsync($"我要請假記錄修改Page");
            });
            點選更新請假紀錄項目Command = new DelegateCommand(async () =>
            {
                try
                {
                    await Init(true);
                }
                catch (Exception ex)
                {
                    await _dialogService.DisplayAlertAsync("錯誤", $"{ex.Message}", "確定");
                }
            });
            #endregion
        }

        public void OnNavigatedFrom(NavigationParameters parameters)
        {
        }

        public async void OnNavigatedTo(NavigationParameters parameters)
        {
            await Init();
        }
        #endregion

        #region Navigation Events (頁面導航事件)
        #endregion

        #region 設計時期或者執行時期的ViewModel初始化
        #endregion

        #region 相關事件
        #endregion

        #region 相關的Command定義
        #endregion

        #region 其他方法

        /// <summary>
        /// 進行要顯示資料的初始化
        /// </summary>
        private async Task Init(bool 是否要先進行同步工作 = false)
        {
            var foo請假紀錄Collection = await MainHelper.請假紀錄Manager.GetAsync(是否要先進行同步工作);

            #region 檢查進行同步更新的時候,是否有異常問題發生
            if (foo請假紀錄Collection == null)
            {
                if (string.IsNullOrEmpty(MainHelper.請假紀錄Manager.ErrorMessage) == false)
                {
                    await _dialogService.DisplayAlertAsync("錯誤", $"{MainHelper.請假紀錄Manager.ErrorMessage}", "確定");
                    return;
                }
            }
            #endregion

            請假紀錄項目清單.Clear();
            foreach (var item in foo請假紀錄Collection)
            {
                請假紀錄項目清單.Add(new 請假紀錄項目ViewModel
                {
                    ID = item.Id,
                    假別 = item.假別,
                    申請人 = item.申請人,
                    職務代理人 = item.職務代理人,
                    請假日期 = item.請假日期,
                    請假時數 = item.請假時數,
                    請假理由 = item.請假理由,
                    開始時間 = item.開始時間,
                });
            }
        }

        #endregion
    }
}

我要請假記錄修改Page.xaml

在檔案 我要請假記錄修改Page.xaml ,並沒有需要做任何變動

我要請假記錄修改PageViewModel.cs

  • 在核心PCL專案內,找到資料夾 ViewModels 內的 我要請假記錄修改PageViewModel.cs
  • 請使用底下 C# 程式碼 進行替換這個檔案內容
  • 由於這個頁面功能,當初是使用靜態資料,因此,我們要修改 ViewModel ,讓它可以使用 Azure 行動應用服務中的離線資料庫功能。
  • 其他的修正則是要使用 MainHelper.請假紀錄Manager 這個物件,來進行資料存取動作
using Prism.Commands;
using Prism.Mvvm;
using System;
using System.Collections.Generic;
using System.Linq;
using Prism.Navigation;
using Prism.Events;
using Prism.Services;
using XFDoggy.Models;
using XFDoggy.Repositories;
using System.Threading.Tasks;
using System.Collections.ObjectModel;
using XFDoggy.Helpers;

namespace XFDoggy.ViewModels
{
    public class 我要請假記錄修改PageViewModel : BindableBase, INavigationAware
    {
        #region Repositories (遠端或本地資料存取)
        請假紀錄Repository foo請假紀錄Repository = new 請假紀錄Repository();
        #endregion

        #region ViewModel Property (用於在 View 中作為綁定之用)

        #region 請假紀錄項目
        private 請假紀錄項目ViewModel _請假紀錄項目;
        /// <summary>
        /// 請假紀錄項目
        /// </summary>
        public 請假紀錄項目ViewModel 請假紀錄項目
        {
            get { return this._請假紀錄項目; }
            set { this.SetProperty(ref this._請假紀錄項目, value); }
        }
        #endregion

        #region 假別清單
        private ObservableCollection<string> _假別清單 = new ObservableCollection<string>();
        /// <summary>
        /// 假別清單
        /// </summary>
        public ObservableCollection<string> 假別清單
        {
            get { return _假別清單; }
            set { SetProperty(ref _假別清單, value); }
        }
        #endregion

        #region 點選假別
        private string _點選假別;
        /// <summary>
        /// 點選假別
        /// </summary>
        public string 點選假別
        {
            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 = "";
        請假紀錄項目 foo請假紀錄項目;
        LeaveRecord foo請假紀錄;

        public DelegateCommand 刪除按鈕Command { get; set; }
        public DelegateCommand 儲存按鈕Command { get; set; }
        public DelegateCommand 取消按鈕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 頁面中綁定的命令 
            刪除按鈕Command = new DelegateCommand(async () =>
            {
                var fooAnswer = await _dialogService.DisplayAlertAsync("警告", "您確定刪除這筆請假記錄", "確定", "取消");
                if (fooAnswer == true)
                {
                    foo請假紀錄 = await MainHelper.請假紀錄Manager.LookupAsync(請假紀錄項目.ID);
                    if (foo請假紀錄 != null)
                    {
                        await MainHelper.請假紀錄Manager.DeleteAsync(foo請假紀錄);
                    }
                    await _navigationService.GoBackAsync();
                }
            });
            儲存按鈕Command = new DelegateCommand(async () =>
            {
                var fooCheck = await Check資料完整性();
                if (fooCheck == false)
                {
                    return;
                }

                if (新增或修改 == 新增或修改Enum.新增)
                {
                    foo請假紀錄 = new LeaveRecord
                    {
                        假別 = 點選假別,
                        申請人 = 請假紀錄項目.申請人,
                        職務代理人 = 請假紀錄項目.職務代理人,
                        請假日期 = 請假紀錄項目.請假日期,
                        請假時數 = 請假紀錄項目.請假時數,
                        請假理由 = 請假紀錄項目.請假理由,
                        開始時間 = 請假紀錄項目.開始時間,
                    };
                    await MainHelper.請假紀錄Manager.InsertAsync(foo請假紀錄);

                    await _navigationService.GoBackAsync();
                }
                else
                {
                    foo請假紀錄 = await MainHelper.請假紀錄Manager.LookupAsync(請假紀錄項目.ID);
                    if (foo請假紀錄 != null)
                    {
                        foo請假紀錄.假別 = 點選假別;
                        foo請假紀錄.申請人 = 請假紀錄項目.申請人;
                        foo請假紀錄.職務代理人 = 請假紀錄項目.職務代理人;
                        foo請假紀錄.請假日期 = 請假紀錄項目.請假日期;
                        foo請假紀錄.請假時數 = 請假紀錄項目.請假時數;
                        foo請假紀錄.請假理由 = 請假紀錄項目.請假理由;
                        foo請假紀錄.開始時間 = 請假紀錄項目.開始時間;
                        await MainHelper.請假紀錄Manager.UpdateAsync(foo請假紀錄);
                    }
                    else
                    {
                        return;
                    }

                    await _navigationService.GoBackAsync();
                }

                await _navigationService.GoBackAsync();
            });

            取消按鈕Command = new DelegateCommand(async () =>
            {
                var fooAnswer = await _dialogService.DisplayAlertAsync("警告", "您確定取消這筆請假記錄", "確定", "取消");
                if (fooAnswer == true)
                {
                    await _navigationService.GoBackAsync();
                }
            });
            #endregion
        }

        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;
            }
            await Init();
        }
        #endregion

        #region Navigation Events (頁面導航事件)
        #endregion

        #region 設計時期或者執行時期的ViewModel初始化
        #endregion

        #region 相關事件
        #endregion

        #region 相關的Command定義
        #endregion

        #region 其他方法
        /// <summary>
        /// 進行要顯示資料的初始化
        /// </summary>
        private async Task Init()
        {
            #region 假別清單項目初始化
            假別清單.Clear();
            假別清單.Add("請選擇");
            假別清單.Add("特休假");
            假別清單.Add("事假");
            假別清單.Add("病假");
            #endregion

            await foo請假紀錄Repository.Read();

            if (string.IsNullOrEmpty(fooID) == true)
            {
                請假紀錄項目 = new 請假紀錄項目ViewModel();
                點選假別 = 假別清單[0];
            }
            else
            {
                foo請假紀錄 = await MainHelper.請假紀錄Manager.LookupAsync(fooID);

                //foo請假紀錄項目 = foo請假紀錄Repository.請假紀錄.FirstOrDefault(x => x.ID == fooID);
                if (foo請假紀錄 == null)
                {
                    請假紀錄項目 = new 請假紀錄項目ViewModel();
                    點選假別 = 假別清單[0];
                }
                else
                {
                    請假紀錄項目 = new 請假紀錄項目ViewModel
                    {
                        ID = foo請假紀錄.Id,
                        假別 = foo請假紀錄.假別,
                        申請人 = foo請假紀錄.申請人,
                        職務代理人 = foo請假紀錄.職務代理人,
                        請假日期 = foo請假紀錄.請假日期,
                        請假時數 = foo請假紀錄.請假時數,
                        請假理由 = foo請假紀錄.請假理由,
                        開始時間 = foo請假紀錄.開始時間,
                    };

                    var fooObj = 假別清單.FirstOrDefault(x => x == 請假紀錄項目.假別);
                    if (fooObj != null)
                    {
                        點選假別 = fooObj;
                    }
                    else
                    {
                        點選假別 = 假別清單[0];
                    }
                }
            }
        }

        private async Task<bool> Check資料完整性()
        {
            if (點選假別 == 假別清單[0])
            {
                await _dialogService.DisplayAlertAsync("警告", "您尚未選擇請假類別", "確定");
                return false;
            }

            return true;
        }
        #endregion

    }
}

實際執行結果

在這裡,我們使用 Android 平台做為測試標的
我們在這裡可以新增任意數量紀錄,也可以隨意修改與刪除

檢測後端資料庫儲存結果

您可以使用 PostMan 來取得後端SQL資料庫內的 請假紀錄 資料表,實際儲存的紀錄內容。
在這裡,我們將會使用底下 URL 來查詢(記得要加入適當的 Header)

在 Azure 行動應用後端專案,新增需要身分認證的才能夠存取的資料表

請接續進行 Azure行動應用服務的後端專案

建立資料傳輸物件 (DTO) 類別

接下來,我們需要修改這個後端 API 專案,讓這個專案可以提供更多的功能。
  • 為了要開始產生一個我們自訂的資料表,我們需要定義資料表控制器, 設定資料表控制器需要三個步驟︰
這裡,我們需要定義一個請假紀錄的申請紀錄表單,用來定義 SQL Database 內的資料表。
  • 請滑鼠右擊 DataObjects 資料夾,選擇 加入 > 類別
  • 在對話窗下方的 名稱 輸入 WorkLog
  • 點選 新增 按鈕
  • 請將剛剛產生的類別 WorkLog,修改成為如下程式碼:
    public class WorkLog : EntityData
    {
        public string 專案名稱 { get; set; }
        public DateTime 日期 { get; set; }
        public double 處理時間 { get; set; }
        public string 工作內容 { get; set; }
    }
若有找不到的類別或者命名空間,請點選燈泡來修正

建立資料表控制器。

如果已安裝 Azure SDK,您現在可以建立範本資料表控制器,請進行底下操作

使用 Azure 行動應用程式資料表控制器

  • 滑鼠右擊 Controller 資料夾,選擇 加入 > 控制器
  • 在對話窗中,選擇 Azure 行動應用程式資料表控制器
  • 點選新增按鈕
  • 在出現 新增控制器 對話窗,請輸入或者選擇如下圖結果
    模型類別:您剛剛定義的 SQL 資料表型別,可用下拉選單來選擇
    資料內容類別:,也就是這個專案內繼承 DbContext 的類別,可用下拉選單來選擇
    控制器名稱:可以使用自動產生的名稱,注意,最後面一定要有 Controller
  • 點選 新增 按鈕,完成這個資料表控制器產生動作。
  • 底下為產生後的 WorkLogController 資料控制器程式碼
    您可以看到,對於工作日報資料表的基本 CRUD API 程式碼,都已經幫您自動產生好了
    這裡請特別注意,在使用 Azure Mobile App 的資料表控制項類別中,必須要繼承 TableController 這個類別,而不是要使用 ApiController 類別
  • 請注意,在這裡我們需要在類別前,加入 [Authorize] 宣告,表示要使用這個資料表,必須要通過使用這身分驗證,也就是不允許匿名存取。
    [Authorize]
    public class WorkLogController : TableController<WorkLog>
    {
        protected override void Initialize(HttpControllerContext controllerContext)
        {
            base.Initialize(controllerContext);
            XamarinAzureDayContext context = new XamarinAzureDayContext();
            DomainManager = new EntityDomainManager<WorkLog>(context, Request);
        }

        // GET tables/WorkLog
        public IQueryable<WorkLog> GetAllWorkLog()
        {
            return Query(); 
        }

        // GET tables/WorkLog/48D68C86-6EA6-4C25-AA33-223FC9A27959
        public SingleResult<WorkLog> GetWorkLog(string id)
        {
            return Lookup(id);
        }

        // PATCH tables/WorkLog/48D68C86-6EA6-4C25-AA33-223FC9A27959
        public Task<WorkLog> PatchWorkLog(string id, Delta<WorkLog> patch)
        {
             return UpdateAsync(id, patch);
        }

        // POST tables/WorkLog
        public async Task<IHttpActionResult> PostWorkLog(WorkLog item)
        {
            WorkLog current = await InsertAsync(item);
            return CreatedAtRoute("Tables", new { id = current.Id }, current);
        }

        // DELETE tables/WorkLog/48D68C86-6EA6-4C25-AA33-223FC9A27959
        public Task DeleteWorkLog(string id)
        {
             return DeleteAsync(id);
        }
    }

加入 Migration

  • 點選功能表 工具 > NuGet封裝管理員 > 套件管理員主控台
  • 在 套件管理員主控台 中輸入底下指令
Add-Migration WorkLog
  • 按下 F5 ,在本地端執行這個專案
    您可以使用 PostMan 來檢查本地端執行與運作結果是否正常
  • 中斷除錯執行
    由於這組資料表的存取行為,需要使用者先成功登入到系統上,因此,您在 PostMan 上會得到底下訊息
{
  "message": "Authorization has been denied for this request."
}

發佈到 Azure 雲端上

PostMan 測試資料

本地端

Azure

Header 資訊

Key : ZUMO-API-VERSION
Value : 2.0.0

這個練習範例專案,請參考

XamarinAzureService_Custom

在 Azure 中,加入 Facebook / Google 身分驗證設定

申請 Facebook 身分驗證的ID與密鑰

  • 請開啟 Facebook for Developers https://developers.facebook.com/ 網頁
  • 將游標移動到右上方的 我的應用程式,在彈出視窗中,點選 新增應用程式 選項
  • 當出現了 新建立的應用程式編號 對話窗,請依序輸入
    顯示名稱 : 在此輸入 多奇數位創意有限公司
    聯絡電子郵件 : 在此輸入您的電子郵件信箱
    類別 : 在此選擇符合您的 Facebook 應用的類別,此處選擇的是 教育
  • 最後,點選 建立應用程式編號 按鈕
  • 在出現 安全驗證 對話窗出現後,請輸入畫面中的驗證碼文字,並且點選 送出 按鈕
  • 此時,會顯示 Facebook 產品設定 網頁,請點選 Facebook 登入 選項右方的 開始使用 按鈕
  • 當網頁顯示如下畫面,請依據底下畫面,設定 用戶端 OAuth 設定
    在此,您需要啟用 嵌入的瀏覽器 OAuth 登入
  • 點選 儲存變更 按鈕,完成這個 Facebook 應用程式建立工作。
  • 請點選左方功能表的 設定 > 基本資料 選項
  • 當網頁顯示如下畫面,請將 應用程式編號 與 應用程式密鑰 請妥善保存這兩個值,等下在專案內會使用到。
    應用程式編號 : 1144209035696864
    應用程式密鑰 : 5cb5c9154fd9d86ca2c4ff6d003849e5
  • 在這個頁面下方,請點選 + 新增平台 按鈕
    在彈出畫面中,請點選 網站
  • 請在下方的 網站網址 欄位中,輸入 xamarinazureday.azurewebsites.net
  • 點選右下方的 儲存變更
  • 請點選左方功能表的 應用程式審查
    請啟用 是否發佈多奇數位創意有限公司 這個應用程式
    在出現 公開發佈應用程式? 對話窗,請點選 確認 按鈕
  • 請點選左方功能表的 主控版,要能夠顯示如下圖的綠色燈號。

申請 Google 身分驗證的ID與密鑰

  • 請開啟 Google API Console https://console.developers.google.com/?hl=zh-tw 網頁
  • 在左方的 API 管理員 的下方,點選 憑證 >
  • 在 憑證 頁面,點選 建立憑證
  • 從下拉選單選擇 OAuth 用戶端ID
  • 選擇 網路應用程式 選項
  • 請在 名稱 欄位輸入這個 OAuth 認證要顯示的名稱,在這裡輸入: 多奇數位創意有限公司
    請在 已授權的重新導向 URI 欄位,輸入這個網址 https://mylabtw.blogspot.tw/oauth2callback
  • 最後,點選 建立 按鈕
  • 當 OAuth 用戶端憑證建立完成之後,將會得到 用戶端ID 與 用戶端密鑰,請妥善保存這兩個值,等下在專案內會使用到。
    用戶端 ID : 213736323010-o1gpeumve03vkbgkqshcuijg7nleqf63.apps.googleusercontent.com
    用戶端密鑰 : RLqD4AFvKAvhxXPYAgGM82DV

Azure Mobile 驗證/授權 設定

  • 請從 Azure 儀表板中開啟 xamarinazuremobile 行動 app
  • 點選 設定 > 驗證/授權
  • 從 XamarinAzureDay 驗證/授權 刀鋒視窗中,啟用 App Service 驗證

Facebook

  • 點選 Facebook 項目
  • 在 Facebook 驗證設定 刀鋒視窗中,輸入
    App 識別碼 : 1144209035696864
    App 密碼 : 5cb5c9154fd9d86ca2c4ff6d003849e5
    勾選這些範圍項目 : public_profile / email
  • 點選下方的 確定 按鈕

Google

  • 點選 Google 項目
  • 在 Google 驗證設定 刀鋒視窗中,輸入
    用戶端識別碼 : 213736323010-o1gpeumve03vkbgkqshcuijg7nleqf63.apps.googleusercontent.com
    用戶端密碼 : RLqD4AFvKAvhxXPYAgGM82DV
  • 點選下方的 確定 按鈕

完成設定

  • 最後,請在 XamarinAzureDay - 驗證/授權 刀鋒視窗上方,點選 儲存 按鈕

在 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=多奇數位創意有限公司");
                }
            });
        }

實際執行結果


在 Azure 中,加入訊息推播設定

建立通知中樞

  • 首先,開啟 Microsoft Azure 找到 XamarinAzureDay 行動App ,並且開啟它
  • 從 XamarinAzureDay - 推送 刀鋒視窗中,點選 推送
  • 在右邊橘色列中,點選 連結
  • 在 通知中樞 刀鋒視窗中,點選 + 通知中樞
  • 在 通知中樞 新增通知中樞 刀鋒視窗中的 通知中樞 欄位,輸入 DoggyEnterpriseHub
  • 在命名空間欄位下,點擊 或建立新的
  • 請在 新建立的命名空間 欄位中輸入 DoggyEnterpriseNamespace
  • 勾選底下的 釘到儀表板,並點選 確定 按鈕
  • 等候 通知中樞 部署完成

設定 Goolge Firebase Cloud Messaging (FCM) 的連結

  • 開啟 Firebase 網站 https://console.firebase.google.com/
  • 點選 建立專案
  • 請在建立專案對話窗中,輸入
    專案名稱 : DoggyEnterpriseHub
    國家/地區 : 台灣
  • 在 Firebase 專案建立完成後,點選 將Firebase加入您的Android應用程式
  • 請在 將Firebase加入您的Android應用程式對話窗中,輸入
    套件名稱 : com.miniasp.xfdoggyEnterprise
    應用程式暱稱 : DoggyEnterpriseHub
    偵錯簽署憑證 SHA-1 (選填) : EC:D3:A8:EA:DD:01:E3:58:5A:46:A5:76:5A:A6:85:13:AD:D3:60:B5
    其中,套件名稱 可以點選滑鼠雙擊 Android 原生專案的 Properties 節點,從 Android Manifest 頁次中的 Package Name來取得
    關於 偵錯簽署憑證 SHA-1 欄位,您可以開啟電腦中的 命令提示字元視窗,輸入:
    "C:\Program Files\Java\jdk1.8.0_102\bin\keytool" -exportcert -list -v -alias androiddebugkey -keystore "C:\Users\%username%\AppData\Local\Xamarin\Mono for Android\debug.keystore"
    其中 keytool 這個工具您可以從您電腦中的 Java JDK 安裝路徑中找到
    您的除錯用的程式碼簽名檔案,將位於 C:\Users\%username%\AppData\Local\Xamarin\Mono for Android\debug.keystore
    執行結果如下所示,請複製 SHA1 的欄位值,貼到網頁上即可
    另外,預設密碼為 android
  • 最後,請點選 新增應用程式 按鈕
  • 接著,點選 繼續 按鈕
  • 點選,完成 按鈕
  • 請在 DoggyEnterprise 專案頁面中,點選齒輪圖示
  • 在彈出功能表中,選擇 專案設定
  • 在設定頁面,切換到 CLOUD MESSENAGING 標籤頁次
    請將 伺服器金鑰 / 寄件者 ID 兩個欄位值複製起來,等下會用到

將 Firebase 設定 綁定到 Azure 推播中樞

  • 現在回到 Azure 頁面,點選 設定推播通知服務
  • 在 推播通知服務 DoggyEnterpriseHub 刀鋒視窗中,點選 `Google (GCM)
  • 在 Google (GCM) DoggyEnterpriseHub 刀鋒視窗內的 API 金鑰欄位,輸入在 Firebase 內取得的 伺服器金鑰
  • 接著,點選 儲存 按鈕

在 Xamarin.Forms 專案,修正進行 Azure 行動應用推播中樞註冊

在這裡,我們將要把我們寫好的 Xamarin.Forms 專案,設定使用 Azure 行動應用的推播服務功能,也就是要在應用程式啟動時候,與 Azure 行動應用的推播中樞進行註冊,並且可以接收與顯示推播訊息內容。

第一次準備工作

在您原先做好的 Xamarin.Forms 專案中,需要加入 Google Cloud Messaging Client 元件

加入 Google Cloud Messaging Client 元件

  • 使用 Visual Studio 2015 打開 XFDoggy.sln 專案
  • 滑鼠右擊 原生Android專案的 Components,選擇 Get More Components
  • 在搜尋文字輸入盒內,填入 Google Cloud Messaging Client 搜尋這個元件
  • 點選 Google Cloud Messaging Client 元件,接著點選 Add to App 按鈕

修改 原生 Android 專案

在這裡,我們僅需要修正 原生 Android 專案,讓 Android 應用程式可以與行動中樞溝通。

設計自己的 GcmServiceBase 類別

  • 滑鼠右擊 原生Android專案中的 Infrastructure 資料夾,選擇 加入 > 類別
  • 在對話窗中,點選 Visual C# > Class
  • 在名稱欄位中,輸入 GcmService
  • 最後,點選 新增 按鈕
  • 將底下程式碼置換這個 GcmService 類別
    請解析要加入的適當命名空間
  • 請將這行程式碼 public static string[] SENDER_IDS = new string[] { "300143732939" }; 的 300143732939 文字,置換成為您申請到的 FCM 寄件者 ID
using Android.App;
using Android.Content;
using Android.Media;
using Android.Support.V4.App;
using Android.Util;
using Gcm.Client;
using Microsoft.WindowsAzure.MobileServices;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using XFDoggy.Helpers;

[assembly: Permission(Name = "@PACKAGE_NAME@.permission.C2D_MESSAGE")]
[assembly: UsesPermission(Name = "@PACKAGE_NAME@.permission.C2D_MESSAGE")]
[assembly: UsesPermission(Name = "com.google.android.c2dm.permission.RECEIVE")]
[assembly: UsesPermission(Name = "android.permission.INTERNET")]
[assembly: UsesPermission(Name = "android.permission.WAKE_LOCK")]
//GET_ACCOUNTS is only needed for android versions 4.0.3 and below
[assembly: UsesPermission(Name = "android.permission.GET_ACCOUNTS")]

namespace XFDoggy.Droid.Infrastructure
{
    [Service]
    public class GcmService : GcmServiceBase
    {
        public static string RegistrationID { get; private set; }

        public GcmService()
            : base(PushHandlerBroadcastReceiver.SENDER_IDS) { }

        protected override void OnRegistered(Context context, string registrationId)
        {
            Log.Verbose("PushHandlerBroadcastReceiver", "GCM Registered: " + registrationId);
            RegistrationID = registrationId;

            var push = MainHelper.client.GetPush();

            MainActivity.CurrentActivity.RunOnUiThread(() => Register(push, null));
        }

        public async void Register(Microsoft.WindowsAzure.MobileServices.Push push, IEnumerable<string> tags)
        {
            try
            {
                const string templateBodyGCM = "{\"data\":{\"message\":\"$(messageParam)\"}}";

                JObject templates = new JObject();
                templates["genericMessage"] = new JObject
                {
                    { "body", templateBodyGCM}
                };

                await push.RegisterAsync(RegistrationID, templates);
                Log.Info("Push Installation Id", push.InstallationId.ToString());
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine(ex.Message);
                Debugger.Break();
            }
        }

        protected override void OnMessage(Context context, Intent intent)
        {
            Log.Info("PushHandlerBroadcastReceiver", "GCM Message Received!");

            var msg = new StringBuilder();

            if (intent != null && intent.Extras != null)
            {
                foreach (var key in intent.Extras.KeySet())
                    msg.AppendLine(key + "=" + intent.Extras.Get(key).ToString());
            }

            //Store the message
            var prefs = GetSharedPreferences(context.PackageName, FileCreationMode.Private);
            var edit = prefs.Edit();
            edit.PutString("last_msg", msg.ToString());
            edit.Commit();

            string message = intent.Extras.GetString("message");
            if (!string.IsNullOrEmpty(message))
            {
                createNotification("多奇數位創意通知", message);
                return;
            }

            string msg2 = intent.Extras.GetString("msg");
            if (!string.IsNullOrEmpty(msg2))
            {
                createNotification("New hub message!", msg2);
                return;
            }

            createNotification("Unknown message details", msg.ToString());
        }

        void createNotification(string title, string desc)
        {
            //Create notification
            var notificationManager = GetSystemService(Context.NotificationService) as NotificationManager;

            //Create an intent to show ui
            var uiIntent = new Intent(this, typeof(MainActivity));

            //Use Notification Builder
            NotificationCompat.Builder builder = new NotificationCompat.Builder(this);

            //Create the notification
            //we use the pending intent, passing our ui intent over which will get called
            //when the notification is tapped.
            var notification = builder.SetContentIntent(PendingIntent.GetActivity(this, 0, uiIntent, 0))
                    .SetSmallIcon(Android.Resource.Drawable.SymActionEmail)
                    .SetTicker(title)
                    .SetContentTitle(title)
                    .SetContentText(desc)

                    //Set the notification sound
                    .SetSound(RingtoneManager.GetDefaultUri(RingtoneType.Notification))

                    //Auto cancel will remove the notification once the user touches it
                    .SetAutoCancel(true).Build();

            //Show the notification
            notificationManager.Notify(1, notification);
        }

        protected override void OnUnRegistered(Context context, string registrationId)
        {
            Log.Error("PushHandlerBroadcastReceiver", "Unregistered RegisterationId : " + registrationId);
        }

        protected override void OnError(Context context, string errorId)
        {
            Log.Error("PushHandlerBroadcastReceiver", "GCM Error: " + errorId);
        }
    }

    [BroadcastReceiver(Permission = Gcm.Client.Constants.PERMISSION_GCM_INTENTS)]
    [IntentFilter(new string[] { Gcm.Client.Constants.INTENT_FROM_GCM_MESSAGE }, Categories = new string[] { "@PACKAGE_NAME@" })]
    [IntentFilter(new string[] { Gcm.Client.Constants.INTENT_FROM_GCM_REGISTRATION_CALLBACK }, Categories = new string[] { "@PACKAGE_NAME@" })]
    [IntentFilter(new string[] { Gcm.Client.Constants.INTENT_FROM_GCM_LIBRARY_RETRY }, Categories = new string[] { "@PACKAGE_NAME@" })]
    public class PushHandlerBroadcastReceiver : GcmBroadcastReceiverBase<GcmService>
    {
        public static string[] SENDER_IDS = new string[] { "300143732939" };
    }

}

修改 MainActivity

  • 在 原生Android專案中,開啟 MainActivity.cs
  • 將底下程式碼置換這個 GcmService 類別
    請解析要加入的適當命名空間
using System;

using Android.App;
using Android.Content.PM;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.OS;
using Prism.Unity;
using Microsoft.Practices.Unity;
using System.Threading.Tasks;
using Microsoft.WindowsAzure.MobileServices;
using XFDoggy.Helpers;

using Gcm.Client;
using XFDoggy.Droid.Infrastructure;

[assembly: UsesPermission(Android.Manifest.Permission.AccessNetworkState)]
[assembly: UsesPermission(Android.Manifest.Permission.ReadExternalStorage)]
[assembly: UsesPermission(Android.Manifest.Permission.WriteExternalStorage)]
[assembly: UsesPermission(Android.Manifest.Permission.CallPhone)]
namespace XFDoggy.Droid
{
    [Activity(Label = "多奇數位創意", Icon = "@drawable/icon", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
    public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity, IAuthenticate
    {
        #region Firebase 的推播設定用程式碼
        // Create a new instance field for this activity.
        static MainActivity instance = null;

        // Return the current activity instance.
        public static MainActivity CurrentActivity
        {
            get
            {
                return instance;
            }
        }

        #endregion

        #region Azure 行動應用之身分驗證用到程式碼

        // Define a authenticated user.
        private MobileServiceUser user;


        public async Task<bool> Authenticate(MobileServiceAuthenticationProvider p登入方式)
        {
            // 驗證結果
            var success = false;
            // 要顯示的訊息
            var message = string.Empty;
            try
            {
                // 呼叫 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

            #region Firebase 的推播設定用程式碼
            // Set the current instance of MainActivity.
            instance = this;
            #endregion

            LoadApplication(new App(new AndroidInitializer()));

            #region Firebase 的推播設定用程式碼
            try
            {
                // Check to ensure everything's set up right
                GcmClient.CheckDevice(this);
                GcmClient.CheckManifest(this);

                // Register for push notifications
                System.Diagnostics.Debug.WriteLine("Registering...");
                GcmClient.Register(this, PushHandlerBroadcastReceiver.SENDER_IDS);
            }
            catch (Java.Net.MalformedURLException)
            {
                CreateAndShowDialog("There was an error creating the client. Verify the URL.", "Error");
            }
            catch (Exception e)
            {
                CreateAndShowDialog(e.Message, "Error");
            }
            #endregion
        }

        #region Firebase 的推播設定用程式碼
        private void CreateAndShowDialog(String message, String title)
        {
            AlertDialog.Builder builder = new AlertDialog.Builder(this);

            builder.SetMessage(message);
            builder.SetTitle(title);
            builder.Create().Show();
        }
        #endregion

        #region Firebase 的推播設定用程式碼

        #endregion
    }

    public class AndroidInitializer : IPlatformInitializer
    {
        public void RegisterTypes(IUnityContainer container)
        {

        }
    }
}

執行與測試

  • 您需要有安裝 Google Play 服務的裝置,才能夠進行底下的測試