XAML in Xamarin.Forms 基礎篇 電子書

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

Xamarin.Forms 快速入門 電子書

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

2016/07/19

Xamarin.Forms 在 Azure 行動應用內,使用 Azure 儲存體

在 Azure 行動應用內,使用 Azure 儲存體

在 Azure 行動應用 綁定 Azure 儲存體

  1. 進入到 Microsoft Azure 儀表板,接著點選 doggymobilebe 行動 App 圖示,進入到 doggymobilebe 行動 App 設定刀鋒頁面。
  2. 點選 資料連接 ,當出現 Data Connections 刀鋒頁面,點選 + Add 連結
  3. 在 Add data conne... 刀鋒頁面,點選標題 Type 欄位下方的下拉選單,選擇 Storage
    • 接著點選下方的 Storage ma8be62c4718c34f >
    • 在 儲存體帳戶 刀鋒視窗下,點選 + 新建
    • 在 剛剛出現的 儲存體帳戶 刀鋒視窗,請點選下方的 確定 按鈕
      Adddataconnection
  4. 當回到 Add data conne... 刀鋒視窗下方,點選 確定 按鈕
  5. 當 Data Connection 建立完成後,會在 Data Connections 刀鋒視窗內,看到MS_AzureStorageAccountConnectionString 名稱出現。
    產生MS_AzureStorageAccountConnectionString
    • 當回到 Azure 儀表板,點選 所有資源,在 所有資源 刀鋒視窗內,會看到這個 ma8be62c4718c34f之 Azure 儲存體 已經建立完成了。
    ma8be62c4718c34f

修改 Azure Mobile App後端服務專案

在這裡,將會使用完成 附件 Azure Mobile App 後端服務建立說明 實作內容後,得到的 Azure Mobile App後端服務專案 來繼續實作這個範例。
  1. 使用 Visual Studio 2015,開啟 Azure Mobile App後端服務專案 `DoggyMobileBE.sln
  2. 將這個 NuGet 套件加入 Microsoft.Azure.Mobile.Server.Files 到專案內
    請記得要勾選 包括搶鮮版
    Microsoft.Azure.Mobile.Server.Files
  3. 使用滑鼠右擊 DoggyMobileBEService > Controllers 選擇 加入 > 控制器
  4. 當出現 新增 Scaffold 對話窗出現後,點選 Web API 2 控制器 - 空白 ,然後點選 新增 按鈕
    空白控制器
  5. 在 加入控制器 對話窗內,把 TodoItemStorageController 填入到 控制器名稱 欄位內
    加入控制器
  6. 當 TodoItemStorageController.cs 檔案建立完成之後,請在該檔案內加入底下 using 敘述
using Microsoft.Azure.Mobile.Server.Files;
using Microsoft.Azure.Mobile.Server.Files.Controllers;
using DoggyMobileBEService.DataObjects;
using System.Threading.Tasks;
  1. 變更 StorageController 類別的基礎類別為 StorageController<TodoItem>
public class TodoItemStorageController : StorageController<TodoItem>
  1. 加入底下方法到這個類別內
[HttpPost]
[Route("tables/TodoItem/{id}/StorageToken")]
public async Task<HttpResponseMessage> PostStorageTokenRequest(string id, StorageTokenRequest value)
{
    StorageToken token = await GetStorageTokenAsync(id, value);

    return Request.CreateResponse(token);
}

// Get the files associated with this record
[HttpGet]
[Route("tables/TodoItem/{id}/MobileServiceFiles")]
public async Task<HttpResponseMessage> GetFiles(string id)
{
    IEnumerable<MobileServiceFile> files = await GetRecordFilesAsync(id);

    return Request.CreateResponse(files);
}

[HttpDelete]
[Route("tables/TodoItem/{id}/MobileServiceFiles/{name}")]
public Task Delete(string id, string name)
{
    return base.DeleteFileAsync(id, name);
}
  1. 更新 Web API 的設定,請開啟 App_Start 資料夾內的 Startup.MobileApp.cs 檔案;將下列程式碼加入到 config 變數定義之後。
config.MapHttpAttributeRoutes();
  1. 請重新發行這個專案,使用滑鼠右擊 DoggyMobileBEService ,並選擇 發行 選項。

修正 Xamarin.Forms 可以使用 Azure 儲存體

  1. 使用 Visual Studio 2015,開啟 Azure Mobile App 前端專案 DoggyMobileBE.sln
  2. 滑鼠右擊方案 DoggyMobileBE,選擇 管理方案的 NuGet 套件,請依序安裝底下套件到所有專案內。
    請記得要勾選 包括搶鮮版
    • Microsoft.Azure.Mobile.Client.Files
    • Microsoft.Azure.Mobile.Client.SQLiteStore
  3. 在核心PCL專案,滑鼠右擊節點 DoggyMobileBE,接著選擇 加入 > 新增項目
    • 在 加入新項目 - DoggyMobileBE 對話窗內,點選 Visual C# > 介面,在下方 名稱 欄位中,輸入 IPlatform,接著,點選 新增 按鈕
    • 在該檔案內,加入底下 using 敘述
using Microsoft.WindowsAzure.MobileServices.Files;
using Microsoft.WindowsAzure.MobileServices.Files.Metadata;
using Microsoft.WindowsAzure.MobileServices.Sync;
  • 將該介面的定義使用底下程式碼替換
public interface IPlatform
{
    Task <string> GetTodoFilesPathAsync();

    Task<IMobileServiceFileDataSource> GetFileDataSource(MobileServiceFileMetadata metadata);

    Task<string> TakePhotoAsync(object context);

    Task DownloadFileAsync<T>(IMobileServiceSyncTable<T> table, MobileServiceFile file, string filename);
}
  1. 在核心PCL專案,滑鼠右擊節點 DoggyMobileBE,接著選擇 加入 > 類別
    • 在 加入新項目 - DoggyMobileBE 對話窗內,點選 Visual C# > 類別,在下方 名稱 欄位中,輸入 FileHelper,接著,點選 新增 按鈕
    • 在該檔案內,加入底下 using 敘述
using System.IO;
using PCLStorage;
using System.Threading.Tasks;
using Xamarin.Forms;
* 將該類別的定義使用底下程式碼替換
public class FileHelper
{
    public static async Task<string> CopyTodoItemFileAsync(string itemId, string filePath)
    {
        IFolder localStorage = FileSystem.Current.LocalStorage;

        string fileName = Path.GetFileName(filePath);
        string targetPath = await GetLocalFilePathAsync(itemId, fileName);

        var sourceFile = await localStorage.GetFileAsync(filePath);
        var sourceStream = await sourceFile.OpenAsync(FileAccess.Read);

        var targetFile = await localStorage.CreateFileAsync(targetPath, CreationCollisionOption.ReplaceExisting);

        using (var targetStream = await targetFile.OpenAsync(FileAccess.ReadAndWrite)) {
            await sourceStream.CopyToAsync(targetStream);
        }

        return targetPath;
    }

    public static async Task<string> GetLocalFilePathAsync(string itemId, string fileName)
    {
        IPlatform platform = DependencyService.Get<IPlatform>();

        string recordFilesPath = Path.Combine(await platform.GetTodoFilesPathAsync(), itemId);

            var checkExists = await FileSystem.Current.LocalStorage.CheckExistsAsync(recordFilesPath);
            if (checkExists == ExistenceCheckResult.NotFound) {
                await FileSystem.Current.LocalStorage.CreateFolderAsync(recordFilesPath, CreationCollisionOption.ReplaceExisting);
            }

        return Path.Combine(recordFilesPath, fileName);
    }

    public static async Task DeleteLocalFileAsync(Microsoft.WindowsAzure.MobileServices.Files.MobileServiceFile fileName)
    {
        string localPath = await GetLocalFilePathAsync(fileName.ParentId, fileName.Name);
        var checkExists = await FileSystem.Current.LocalStorage.CheckExistsAsync(localPath);

        if (checkExists == ExistenceCheckResult.FileExists) {
            var file = await FileSystem.Current.LocalStorage.GetFileAsync(localPath);
            await file.DeleteAsync();
        }
    }
}
  1. 在核心PCL專案,滑鼠右擊節點 DoggyMobileBE,接著選擇 加入 > 類別
    • 在 加入新項目 - DoggyMobileBE 對話窗內,點選 Visual C# > 類別,在下方 名稱 欄位中,輸入 TodoItemFileSyncHandler,接著,點選 新增 按鈕
    • 在該檔案內,加入底下 using 敘述
using System.Threading.Tasks;
using Microsoft.WindowsAzure.MobileServices.Files.Sync;
using Microsoft.WindowsAzure.MobileServices.Files;
using Microsoft.WindowsAzure.MobileServices.Files.Metadata;
using Xamarin.Forms;
* 將該類別的定義使用底下程式碼替換
public class TodoItemFileSyncHandler : IFileSyncHandler
{
    private readonly TodoItemManager todoItemManager;

    public TodoItemFileSyncHandler(TodoItemManager itemManager)
    {
        this.todoItemManager = itemManager;
    }

    public Task<IMobileServiceFileDataSource> GetDataSource(MobileServiceFileMetadata metadata)
    {
        IPlatform platform = DependencyService.Get<IPlatform>();
        return platform.GetFileDataSource(metadata);
    }

    public async Task ProcessFileSynchronizationAction(MobileServiceFile file, FileSynchronizationAction action)
    {
        if (action == FileSynchronizationAction.Delete) {
            await FileHelper.DeleteLocalFileAsync(file);
        }
        else { // Create or update. We're aggressively downloading all files.
            await this.todoItemManager.DownloadFileAsync(file);
        }
    }
}
  1. 展開核心PCL DoggyMobileBE 專案,滑鼠雙擊 Properties 節點
    • 當在 Visual Studio 2015 內,出現了 DoggyMobileBE 視窗,請點選 建置
    • 在標題 條件式編譯的符號 欄位內,填入 OFFLINE_SYNC_ENABLED
    OFFLINE_SYNC_ENABLED
  2. 在核心PCL 專案內,開啟 TodoItemManager.cs 檔案,
    • 在該檔案內,加入底下 using 敘述
using System.IO;
using Xamarin.Forms;
using Microsoft.WindowsAzure.MobileServices.Files;
using Microsoft.WindowsAzure.MobileServices.Files.Sync;
using Microsoft.WindowsAzure.MobileServices.Eventing;
  • 在 TodoItemManager 類別建構式內,在 DefineTable() 方法之後,加入底下程式碼
// Initialize file sync
this.client.InitializeFileSyncContext(new TodoItemFileSyncHandler(this), store);
  • 在建構式內,將 InitializeAsync 方法使用底下程式碼置換
this.client.SyncContext.InitializeAsync(store, StoreTrackingOptions.NotifyLocalAndServerOperations);
  • 在 SyncAsync() 方法內,將底下程式碼加入到 PushAsync() 之後
await this.todoTable.PushFileChangesAsync();
  • 加入底下方法到 TodoItemManager 類別內
internal async Task DownloadFileAsync(MobileServiceFile file)
{
    var todoItem = await todoTable.LookupAsync(file.ParentId);
    IPlatform platform = DependencyService.Get<IPlatform>();

    string filePath = await FileHelper.GetLocalFilePathAsync(file.ParentId, file.Name); 
    await platform.DownloadFileAsync(this.todoTable, file, filePath);
}

internal async Task<MobileServiceFile> AddImage(TodoItem todoItem, string imagePath)
{
    string targetPath = await FileHelper.CopyTodoItemFileAsync(todoItem.Id, imagePath);
    return await this.todoTable.AddFileAsync(todoItem, Path.GetFileName(targetPath));
}

internal async Task DeleteImage(TodoItem todoItem, MobileServiceFile file)
{
    await this.todoTable.DeleteFileAsync(file);
}

internal async Task<IEnumerable<MobileServiceFile>> GetImageFilesAsync(TodoItem todoItem)
{
    return await this.todoTable.GetFilesAsync(todoItem);
}
  1. 在核心PCL專案,滑鼠右擊節點 DoggyMobileBE,接著選擇 加入 > 類別
    • 在 加入新項目 - DoggyMobileBE 對話窗內,點選 Visual C# > 類別,在下方 名稱 欄位中,輸入 TodoItemImage,接著,點選 新增 按鈕
    • 在該檔案內,使用底下類別定義替換掉 TodoItemImage 的定義
public class TodoItemImage : INotifyPropertyChanged
{
    private string name;
    private string uri;

    public MobileServiceFile File { get; private set; }

    public string Name
    {
        get { return name; }
        set
        {
            name = value;
            OnPropertyChanged(nameof(Name));
        }
    }

    public string Uri
    {
        get { return uri; }      
        set
        {
            uri = value;
            OnPropertyChanged(nameof(Uri));
        }
    }

    public TodoItemImage(MobileServiceFile file, TodoItem todoItem)
    {
        Name = file.Name;
        File = file;

        FileHelper.GetLocalFilePathAsync(todoItem.Id, file.Name).ContinueWith(x => this.Uri = x.Result);
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}
  1. 在核心PCL內,打開 App.cs
    • 將檔案內的 MainPage 使用底下程式碼來替換
MainPage = new NavigationPage(new TodoList());
  • 在這個 App 類別內,加入底下靜態屬性
public static object UIContext { get; set; }
  1. 在核心PCL專案,滑鼠右擊節點 DoggyMobileBE,接著選擇 加入 > 新增項目
    • 在 加入新項目 - DoggyMobileBE 對話窗內,點選 Cross-Platform > Forms Xaml Page,在下方名稱 欄位中,輸入 TodoItemDetailsView,接著,點選 新增 按鈕
    • 打開 TodoItemDetailsView.xaml 檔案,使用底下定義替換掉這個檔案內的 ContentPage 內的宣告
  <Grid>
    <Grid.RowDefinitions>
      <RowDefinition Height="Auto"/>
      <RowDefinition Height="Auto"/>
      <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <Button Clicked="OnAdd" Text="Add image"></Button>
    <ListView x:Name="imagesList"
              ItemsSource="{Binding Images}"
              IsPullToRefreshEnabled="false"
              Grid.Row="2">
      <ListView.ItemTemplate>
        <DataTemplate>
          <ImageCell ImageSource="{Binding Uri}"
                     Text="{Binding Name}">
          </ImageCell>
        </DataTemplate>
      </ListView.ItemTemplate>
    </ListView>
  </Grid>
* 打開 `TodoItemDetailsView.xaml.cs` 檔案,將底下的 using 定義加入到這個檔案內
using System.Collections.ObjectModel;
using Microsoft.WindowsAzure.MobileServices.Files;
* 使用底下程式碼替換掉 `TodoItemDetailsView` 類別定義
public partial class TodoItemDetailsView : ContentPage
{
    private TodoItemManager manager;

    public TodoItem TodoItem { get; set; }        
    public ObservableCollection<TodoItemImage> Images { get; set; }

    public TodoItemDetailsView(TodoItem todoItem, TodoItemManager manager)
    {
        InitializeComponent();
        this.Title = todoItem.Name;

        this.TodoItem = todoItem;
        this.manager = manager;

        this.Images = new ObservableCollection<TodoItemImage>();
        this.BindingContext = this;
    }

    public async Task LoadImagesAsync()
    {
        IEnumerable<MobileServiceFile> files = await this.manager.GetImageFilesAsync(TodoItem);
        this.Images.Clear();

        foreach (var f in files) {
            var todoImage = new TodoItemImage(f, this.TodoItem);
            this.Images.Add(todoImage);
        }
    }

    public async void OnAdd(object sender, EventArgs e)
    {
        IPlatform mediaProvider = DependencyService.Get<IPlatform>();
        string sourceImagePath = await mediaProvider.TakePhotoAsync(App.UIContext);

        if (sourceImagePath != null) {
            MobileServiceFile file = await this.manager.AddImage(this.TodoItem, sourceImagePath);

            var image = new TodoItemImage(file, this.TodoItem);
            this.Images.Add(image);
        }
    }
}
  1. 在核心PCL 專案內,打開 TodoList.xaml.cs 檔案
    • 請將 OnSelected 方法,使用底下程式碼替換掉
public async void OnSelected(object sender, SelectedItemChangedEventArgs e)
{
    var todo = e.SelectedItem as TodoItem;

    if (todo != null) {
        var detailsView = new TodoItemDetailsView(todo, manager);
        await detailsView.LoadImagesAsync();
        await Navigation.PushAsync(detailsView);
    }

    todoList.SelectedItem = null;
}

修正 DoggyMobileBE.Droid 專案內容

  1. 在 DoggyMobileBE.Droid 專案,滑鼠右擊專案內的 Components 節點,接著,選擇 Get More Components...
    • 當出現 All Components 對話視窗,請在左上方輸入 Xamarin.Mobile ,進行搜尋這個元件,當搜尋到之後,點選右半部的那個元件。
    AllComponents
    • 當 Xamarin.Mobile 元件出現之後,請點選 Add to App 按鈕,安裝這個元件到 Android 專案內。
    XamarinMobileComponent
  2. 在核心PCL專案,滑鼠右擊節點 DoggyMobileBE.Droid,接著選擇 加入 > 類別
    • 在 加入新項目 - DoggyMobileBE.Droid 對話窗內,點選 Visual C# > Class,在下方 名稱 欄位中,輸入 DroidPlatform,接著,點選 新增 按鈕
    • 在該檔案內,使用底下類別定義替換掉 TodoItemImage 的定義
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using System.Threading.Tasks;
using Microsoft.WindowsAzure.MobileServices.Sync;
using Microsoft.WindowsAzure.MobileServices.Files.Metadata;
using Microsoft.WindowsAzure.MobileServices.Files;
using Microsoft.WindowsAzure.MobileServices.Files.Sync;
using System.IO;
using Xamarin.Media;

[assembly: Xamarin.Forms.Dependency(typeof(DoggyMobileBE.Droid.DroidPlatform))]
namespace DoggyMobileBE.Droid
{
    public class DroidPlatform : IPlatform
    {
        public async Task DownloadFileAsync<T>(IMobileServiceSyncTable<T> table, MobileServiceFile file, string filename)
        {
            var path = await FileHelper.GetLocalFilePathAsync(file.ParentId, file.Name);
            await table.DownloadFileAsync(file, path);
        }

        public async Task<IMobileServiceFileDataSource> GetFileDataSource(MobileServiceFileMetadata metadata)
        {
            var filePath = await FileHelper.GetLocalFilePathAsync(metadata.ParentDataItemId, metadata.FileName);
            return new PathMobileServiceFileDataSource(filePath);
        }

        public async Task<string> TakePhotoAsync(object context)
        {
            try
            {
                var uiContext = context as Context;
                if (uiContext != null)
                {
                    var mediaPicker = new MediaPicker(uiContext);
                    var photo = await mediaPicker.TakePhotoAsync(new StoreCameraMediaOptions());

                    return photo.Path;
                }
            }
            catch (TaskCanceledException)
            {
            }

            return null;
        }

        public Task<string> GetTodoFilesPathAsync()
        {
            string appData = System.Environment.GetFolderPath(System.Environment.SpecialFolder.MyDocuments);
            string filesPath = Path.Combine(appData, "TodoItemFiles");

            if (!Directory.Exists(filesPath))
            {
                Directory.CreateDirectory(filesPath);
            }

            return Task.FromResult(filesPath);
        }
    }
}
  1. 打開 MainActivity.cs 檔案,在 OnCreate 方法內,在 LoadApplication() 方法之前,加入底下程式碼。
App.UIContext = this;
  1. 滑鼠右擊專案節點 DoggyMobileBE.Droid 選擇 設定為起始專案,並且按下 F5 開始執行
    • 因為採用離線存取方式,因此,就算當時裝置沒有連上網路,也可以看的到資料。
      AndroidStorage1
    • 使用 Add image 按鈕,可以透過裝置新增照片,並且該這片將會儲存到遠端的 Azure 儲存體中
      AndroidStorage2
    • 另外,在 Azure 的 Blob服務 網頁中,也可以看到這些圖片已經儲存到 Azure 儲存體上了。
      Blog服務明細

修正 DoggyMobileBE.iOS 專案內容

  1. 在 DoggyMobileBE.iOS 專案,滑鼠右擊專案內的 Components 節點,接著,選擇 Get More Components...
    • 當出現 All Components 對話視窗,請在左上方輸入 Xamarin.Mobile ,進行搜尋這個元件,當搜尋到之後,點選右半部的那個元件。
    AllComponents
    • 當 Xamarin.Mobile 元件出現之後,請點選 Add to App 按鈕,安裝這個元件到 Android 專案內。
    XamarinMobileComponent
  2. 在核心PCL專案,滑鼠右擊節點 DoggyMobileBE.Droid,接著選擇 加入 > 類別
    • 在 加入新項目 - DoggyMobileBE.Droid 對話窗內,點選 Apple > 類別,在下方 名稱 欄位中,輸入 TouchPlatform,接著,點選 新增 按鈕
    • 在該檔案內,使用底下類別定義替換掉 TodoItemImage 的定義
using Microsoft.WindowsAzure.MobileServices.Files;
using Microsoft.WindowsAzure.MobileServices.Files.Metadata;
using Microsoft.WindowsAzure.MobileServices.Files.Sync;
using Microsoft.WindowsAzure.MobileServices.Sync;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Media;

[assembly: Xamarin.Forms.Dependency(typeof(DoggyMobileBE.iOS.TouchPlatform))]
namespace DoggyMobileBE.iOS
{
    class TouchPlatform : IPlatform
    {
        public async Task DownloadFileAsync<T>(IMobileServiceSyncTable<T> table, MobileServiceFile file, string filename)
        {
            var path = await FileHelper.GetLocalFilePathAsync(file.ParentId, file.Name);
            await table.DownloadFileAsync(file, path);
        }

        public async Task<IMobileServiceFileDataSource> GetFileDataSource(MobileServiceFileMetadata metadata)
        {
            var filePath = await FileHelper.GetLocalFilePathAsync(metadata.ParentDataItemId, metadata.FileName);
            return new PathMobileServiceFileDataSource(filePath);
        }

        public async Task<string> TakePhotoAsync(object context)
        {
            try
            {
                var mediaPicker = new MediaPicker();
                var mediaFile = await mediaPicker.PickPhotoAsync();
                return mediaFile.Path;
            }
            catch (TaskCanceledException)
            {
                return null;
            }
        }

        public Task<string> GetTodoFilesPathAsync()
        {
            string filesPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "TodoItemFiles");

            if (!Directory.Exists(filesPath))
            {
                Directory.CreateDirectory(filesPath);
            }

            return Task.FromResult(filesPath);
        }
    }
}
  1. 打開 AppDelegate.cs 檔案,解除的註解 SQLitePCL.CurrentPlatform.Init()
App.UIContext = this;
  1. 滑鼠右擊專案節點 DoggyMobileBE.Droid 選擇 設定為起始專案,並且按下 F5 開始執行
    • 因為採用離線存取方式,因此,就算當時裝置沒有連上網路,也可以看的到資料。
      iOSStorage1
    • 剛剛在 Android 平台下,已經在 Second item 項目內新增了三個照片,所以,當開啟 iOS 應用程式的時候,就可以直接看到這些圖片了。也可以從圖檔編號是一樣來確認。
      iOSStorage2

沒有留言:

張貼留言