XAML in Xamarin.Forms 基礎篇 電子書

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

Xamarin.Forms 快速入門 電子書

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

2017/04/17

Xamarin.Forms 與 Azure Mobile App Lab 8

在 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)

Xamarin.Forms 與 Azure Mobile App Lab 7

在 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