在 Azure 行動應用內,使用 Azure 儲存體
在 Azure 行動應用 綁定 Azure 儲存體
- 點選
資料連接
,當出現Data Connections
刀鋒頁面,點選+ Add
連結 - 在
Add data conne...
刀鋒頁面,點選標題Type
欄位下方的下拉選單,選擇Storage
- 接著點選下方的
Storage ma8be62c4718c34f >
- 在
儲存體帳戶
刀鋒視窗下,點選+ 新建
- 在 剛剛出現的
儲存體帳戶
刀鋒視窗,請點選下方的確定
按鈕
- 當回到
Add data conne...
刀鋒視窗下方,點選確定
按鈕 - 當 Data Connection 建立完成後,會在
Data Connections
刀鋒視窗內,看到MS_AzureStorageAccountConnectionString
名稱出現。- 當回到 Azure 儀表板,點選
所有資源
,在所有資源
刀鋒視窗內,會看到這個ma8be62c4718c34f
之Azure 儲存體
已經建立完成了。
- 當回到 Azure 儀表板,點選
修改 Azure Mobile App後端服務專案
在這裡,將會使用完成 附件 Azure Mobile App 後端服務建立說明 實作內容後,得到的 Azure Mobile App後端服務專案 來繼續實作這個範例。
- 使用 Visual Studio 2015,開啟 Azure Mobile App後端服務專案 `DoggyMobileBE.sln
- 將這個 NuGet 套件加入
Microsoft.Azure.Mobile.Server.Files
到專案內請記得要勾選包括搶鮮版
- 使用滑鼠右擊
DoggyMobileBEService
>Controllers
選擇加入
>控制器
- 當出現
新增 Scaffold
對話窗出現後,點選Web API 2 控制器 - 空白
,然後點選新增
按鈕 - 在
加入控制器
對話窗內,把TodoItemStorageController
填入到控制器名稱
欄位內 - 當
TodoItemStorageController.cs
檔案建立完成之後,請在該檔案內加入底下using
敘述
using Microsoft.Azure.Mobile.Server.Files;
using Microsoft.Azure.Mobile.Server.Files.Controllers;
using DoggyMobileBEService.DataObjects;
using System.Threading.Tasks;
- 變更
StorageController
類別的基礎類別為StorageController<TodoItem>
public class TodoItemStorageController : StorageController<TodoItem>
- 加入底下方法到這個類別內
[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);
}
- 更新 Web API 的設定,請開啟
App_Start
資料夾內的Startup.MobileApp.cs
檔案;將下列程式碼加入到config
變數定義之後。
config.MapHttpAttributeRoutes();
- 請重新發行這個專案,使用滑鼠右擊
DoggyMobileBEService
,並選擇發行
選項。
修正 Xamarin.Forms 可以使用 Azure 儲存體
- 使用 Visual Studio 2015,開啟 Azure Mobile App 前端專案
DoggyMobileBE.sln
- 滑鼠右擊方案
DoggyMobileBE
,選擇管理方案的 NuGet 套件
,請依序安裝底下套件到所有專案內。請記得要勾選包括搶鮮版
- Microsoft.Azure.Mobile.Client.Files
- Microsoft.Azure.Mobile.Client.SQLiteStore
- 在核心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);
}
- 在核心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();
}
}
}
- 在核心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);
}
}
}
- 展開核心PCL
DoggyMobileBE
專案,滑鼠雙擊Properties
節點- 當在 Visual Studio 2015 內,出現了
DoggyMobileBE
視窗,請點選建置
- 在標題
條件式編譯的符號
欄位內,填入OFFLINE_SYNC_ENABLED
- 在核心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);
}
- 在核心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));
}
}
- 在核心PCL內,打開
App.cs
- 將檔案內的
MainPage
使用底下程式碼來替換
- 將檔案內的
MainPage = new NavigationPage(new TodoList());
- 在這個 App 類別內,加入底下靜態屬性
public static object UIContext { get; set; }
- 在核心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);
}
}
}
- 在核心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 專案內容
- 在 DoggyMobileBE.Droid 專案,滑鼠右擊專案內的
Components
節點,接著,選擇Get More Components...
- 當出現
All Components
對話視窗,請在左上方輸入Xamarin.Mobile
,進行搜尋這個元件,當搜尋到之後,點選右半部的那個元件。
- 當
Xamarin.Mobile
元件出現之後,請點選Add to App
按鈕,安裝這個元件到 Android 專案內。
- 當出現
- 在核心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);
}
}
}
- 打開
MainActivity.cs
檔案,在OnCreate
方法內,在LoadApplication()
方法之前,加入底下程式碼。
App.UIContext = this;
- 滑鼠右擊專案節點
DoggyMobileBE.Droid
選擇設定為起始專案
,並且按下 F5 開始執行- 因為採用離線存取方式,因此,就算當時裝置沒有連上網路,也可以看的到資料。
- 使用 Add image 按鈕,可以透過裝置新增照片,並且該這片將會儲存到遠端的 Azure 儲存體中
- 另外,在 Azure 的
Blob服務
網頁中,也可以看到這些圖片已經儲存到 Azure 儲存體上了。
修正 DoggyMobileBE.iOS 專案內容
- 在 DoggyMobileBE.iOS 專案,滑鼠右擊專案內的
Components
節點,接著,選擇Get More Components...
- 當出現
All Components
對話視窗,請在左上方輸入Xamarin.Mobile
,進行搜尋這個元件,當搜尋到之後,點選右半部的那個元件。
- 當
Xamarin.Mobile
元件出現之後,請點選Add to App
按鈕,安裝這個元件到 Android 專案內。
- 當出現
- 在核心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);
}
}
}
- 打開
AppDelegate.cs
檔案,解除的註解SQLitePCL.CurrentPlatform.Init()
。
App.UIContext = this;
- 滑鼠右擊專案節點
DoggyMobileBE.Droid
選擇設定為起始專案
,並且按下 F5 開始執行- 因為採用離線存取方式,因此,就算當時裝置沒有連上網路,也可以看的到資料。
- 剛剛在 Android 平台下,已經在 Second item 項目內新增了三個照片,所以,當開啟 iOS 應用程式的時候,就可以直接看到這些圖片了。也可以從圖檔編號是一樣來確認。
doggymobilebe 行動 App
圖示,進入到doggymobilebe 行動 App
設定刀鋒頁面。