- Android
- コルドバ
- JavaScript
- iOS
- マネージド (Windows/Xamarin)
概要
このガイドでは、Windows および Xamarin アプリ用の Azure App Service Mobile Apps 用のマネージド クライアント ライブラリを使用して、一般的なシナリオを実行する方法について説明します。 Mobile Apps を初めて使用する場合は、最初に Azure Mobile Apps クイックスタート チュートリアル 完了することを検討する必要があります。 このガイドでは、クライアント側のマネージド SDK に焦点を当てます。 Mobile Apps 用のサーバー側 SDK の詳細については、.NET Server SDK または Node.js Server SDKのドキュメントを参照してください。
参照ドキュメント
クライアント SDK のリファレンス ドキュメントは、Azure Mobile Apps .NET クライアント リファレンス にあります。 また、Azure-Samples GitHub リポジトリで、いくつかのクライアント サンプルを見つけることもできます。
サポートされているプラットフォーム
.NET プラットフォームでは、次のプラットフォームがサポートされています。
- API 19 から 24 の Xamarin Android リリース (KitKat から Nougat)
- iOS バージョン 8.0 以降の Xamarin iOS リリース
- ユニバーサル Windows プラットフォーム
- Windows Phone 8.1
- Silverlight アプリケーションを除く Windows Phone 8.0
"サーバー フロー" 認証では、表示される UI に WebView が使用されます。 デバイスが WebView UI を表示できない場合は、他の認証方法が必要です。 したがって、この SDK は、ウォッチタイプまたは同様に制限されたデバイスには適していません。
セットアップと前提条件
少なくとも 1 つのテーブルを含むモバイル アプリ バックエンド プロジェクトが既に作成および発行されていることを前提としています。 このトピックで使用するコードでは、テーブルには TodoItem
という名前が付けられ、Id
、Text
、および Complete
の各列があります。 このテーブルは、Azure Mobile Apps クイック スタートを完了したときに作成されたテーブルと同じです。
C# の対応するクライアント側の型は、次のクラスです。
public class TodoItem
{
public string Id { get; set; }
[JsonProperty(PropertyName = "text")]
public string Text { get; set; }
[JsonProperty(PropertyName = "complete")]
public bool Complete { get; set; }
}
JsonPropertyAttribute は、クライアント フィールドとテーブル フィールドの間の PropertyName マッピングを定義するために使用されます。
Mobile Apps バックエンドでテーブルを作成する方法については、.NET Server SDK のトピック または Node.js Server SDK のトピックを参照してください。 クイック スタートを使用して Azure portal でモバイル アプリ バックエンドを作成した場合は、Azure portalで Easy テーブル 設定を使用することもできます。
方法: マネージド クライアント SDK パッケージをインストールする
次のいずれかの方法を使用して、NuGet から Mobile Apps 用のマネージド クライアント SDK パッケージインストールします。
- Visual Studio プロジェクトを右クリック、[NuGet パッケージ 管理] をクリックし、
Microsoft.Azure.Mobile.Client
パッケージを検索して、[のインストール]クリックします。 - Xamarin Studio プロジェクトを右クリック、[Add>Add NuGet Packages] をクリックし、
Microsoft.Azure.Mobile.Client
パッケージを検索して、[パッケージの追加] クリックします。
メイン アクティビティ ファイルで、ステートメントを使用して、次の を必ず追加してください。
using Microsoft.WindowsAzure.MobileServices;
注
Android プロジェクトで参照されるすべてのサポート パッケージのバージョンが同じである必要があることに注意してください。 SDK には Android プラットフォーム Xamarin.Android.Support.CustomTabs
依存関係があるため、プロジェクトで新しいサポート パッケージを使用する場合は、競合を回避するために、必要なバージョンでこのパッケージを直接インストールする必要があります。
方法: Visual Studio でデバッグ シンボルを操作する
Microsoft.Azure.Mobile 名前空間のシンボルは、SymbolSourceで使用できます。 SymbolSource と Visual Studio を統合する SymbolSource の手順を参照してください。
Mobile Apps クライアントを作成する
次のコードでは、モバイル アプリ バックエンドへのアクセスに使用される MobileServiceClient オブジェクトを作成します。
var client = new MobileServiceClient("MOBILE_APP_URL");
前のコードでは、MOBILE_APP_URL
をモバイル アプリ バックエンドの URL に置き換えます。これは、Azure portalのモバイル アプリ バックエンドのブレードにあります。 MobileServiceClient オブジェクトはシングルトンである必要があります。
テーブルの操作
次のセクションでは、レコードを検索および取得し、テーブル内のデータを変更する方法について詳しく説明します。 次のトピックについて説明します。
- テーブル参照 を作成する
- データのクエリ
- 返されたデータ をフィルター処理する
- 返されたデータの並べ替え
- ページ単位でデータを返す
- 特定の列を選択
- ID でレコードを検索する
- 型指定されていないクエリの処理
- データ の挿入
- データの更新
- データ の削除
- 競合の解決とオプティミスティック コンカレンシーの
- Windows ユーザー インターフェイスへのバインド
- ページ サイズの変更
方法: テーブル参照を作成する
バックエンド テーブル内のデータにアクセスまたは変更するすべてのコードは、MobileServiceTable
オブジェクトの関数を呼び出します。 次のように、GetTable メソッドを呼び出して、テーブルへの参照を取得します。
IMobileServiceTable<TodoItem> todoTable = client.GetTable<TodoItem>();
返されるオブジェクトは、型指定されたシリアル化モデルを使用します。 型指定されていないシリアル化モデルもサポートされています。 次の例 、型指定されていないテーブルへの参照を作成します。
// Get an untyped table reference
IMobileServiceTable untypedTodoTable = client.GetTable("TodoItem");
型指定されていないクエリでは、基になる OData クエリ文字列を指定する必要があります。
方法: モバイル アプリからデータを照会する
このセクションでは、次の機能を含むモバイル アプリ バックエンドにクエリを発行する方法について説明します。
- 返されたデータ をフィルター処理する
- 返されたデータの並べ替え
- ページ単位でデータを返す
- 特定の列を選択
- ID でデータを検索する
注
すべての行が返されないように、サーバー駆動型のページ サイズが適用されます。 ページングにより、大規模なデータセットに対する既定の要求がサービスに悪影響を与えるのを防ぎます。 50 行を超える行を返すには、「ページ Skip
」の説明に従って、Take
および メソッドを使用します。
方法: 返されたデータをフィルター処理する
次のコードは、クエリに Where
句を含めることでデータをフィルター処理する方法を示しています。 todoTable
プロパティが Complete
と等しい false
からすべての項目を返します。 Where 関数は、テーブルに対するクエリに行フィルター述語を適用します。
// This query filters out completed TodoItems and items without a timestamp.
List<TodoItem> items = await todoTable
.Where(todoItem => todoItem.Complete == false)
.ToListAsync();
ブラウザー開発者ツールや Fiddler などのメッセージ検査ソフトウェアを使用して、バックエンドに送信された要求の URIを表示できます。 要求 URI を確認すると、クエリ文字列が変更されていることに注意してください。
GET /tables/todoitem?$filter=(complete+eq+false) HTTP/1.1
この OData 要求は、サーバー SDK によって SQL クエリに変換されます。
SELECT *
FROM TodoItem
WHERE ISNULL(complete, 0) = 0
Where
メソッドに渡される関数は、任意の数の条件を持つことができます。
// This query filters out completed TodoItems where Text isn't null
List<TodoItem> items = await todoTable
.Where(todoItem => todoItem.Complete == false && todoItem.Text != null)
.ToListAsync();
この例は、サーバー SDK によって SQL クエリに変換されます。
SELECT *
FROM TodoItem
WHERE ISNULL(complete, 0) = 0
AND ISNULL(text, 0) = 0
このクエリは、複数の句に分割することもできます。
List<TodoItem> items = await todoTable
.Where(todoItem => todoItem.Complete == false)
.Where(todoItem => todoItem.Text != null)
.ToListAsync();
2 つのメソッドは同等であり、同じ意味で使用できます。 前者のオプション (1 つのクエリで複数の述語を連結する) は、よりコンパクトであり、推奨されます。
Where
句は、OData サブセットに変換される操作をサポートします。 操作には次のものが含まれます。
- 関係演算子 (==、!=、<、<=、>、>=)
- 算術演算子 (+、-、/、*、%)
- 数値の精度 (Math.Floor、Math.Ceiling)
- 文字列関数 (Length、Substring、Replace、IndexOf、StartsWith、EndsWith)
- 日付のプロパティ (年、月、日、時、分、秒)
- オブジェクトのプロパティにアクセスし、
- これらの操作のいずれかを組み合わせた式。
Server SDK でサポートされる内容を検討するときは、OData v3 ドキュメントを検討できます。
方法: 返されたデータを並べ替える
次のコードは、クエリに OrderBy や OrderByDescending 関数を含めてデータを並べ替える方法を示しています。 todoTable
フィールドで昇順に並べ替えられた Text
から項目を返します。
// Sort items in ascending order by Text field
MobileServiceTableQuery<TodoItem> query = todoTable
.OrderBy(todoItem => todoItem.Text)
List<TodoItem> items = await query.ToListAsync();
// Sort items in descending order by Text field
MobileServiceTableQuery<TodoItem> query = todoTable
.OrderByDescending(todoItem => todoItem.Text)
List<TodoItem> items = await query.ToListAsync();
方法: ページ内のデータを返す
既定では、バックエンドは最初の 50 行のみを返します。 Take メソッドを呼び出すことで、返される行の数を増やすことができます。 Take
を Skip メソッドと共に使用して、クエリによって返されたデータセットの合計の特定の "ページ" を要求します。 次のクエリを実行すると、テーブル内の上位 3 つの項目が返されます。
// Define a filtered query that returns the top 3 items.
MobileServiceTableQuery<TodoItem> query = todoTable.Take(3);
List<TodoItem> items = await query.ToListAsync();
次の変更後のクエリは、最初の 3 つの結果をスキップし、次の 3 つの結果を返します。 このクエリでは、ページ サイズが 3 つの項目であるデータの 2 番目の "ページ" が生成されます。
// Define a filtered query that skips the top 3 items and returns the next 3 items.
MobileServiceTableQuery<TodoItem> query = todoTable.Skip(3).Take(3);
List<TodoItem> items = await query.ToListAsync();
IncludeTotalCount メソッドは、返されたレコードのすべての の合計数を要求し、指定されたページング/制限句は無視します。
query = query.IncludeTotalCount();
実際のアプリでは、前の例のようなクエリをポケットベル コントロールまたは同等の UI と共に使用して、ページ間を移動できます。
注
モバイル アプリ バックエンドで 50 行の制限をオーバーライドするには、EnableQueryAttribute をパブリック GET メソッドに適用し、ページング動作を指定する必要もあります。 メソッドに適用すると、返される最大行数を 1000 に設定します。
[EnableQuery(MaxTop=1000)]
方法: 特定の列を選択する
結果に含めるプロパティのセットを指定するには、クエリに Select 句を追加します。 たとえば、次のコードは、1 つのフィールドのみを選択する方法と、複数のフィールドを選択して書式設定する方法を示しています。
// Select one field -- just the Text
MobileServiceTableQuery<TodoItem> query = todoTable
.Select(todoItem => todoItem.Text);
List<string> items = await query.ToListAsync();
// Select multiple fields -- both Complete and Text info
MobileServiceTableQuery<TodoItem> query = todoTable
.Select(todoItem => string.Format("{0} -- {1}",
todoItem.Text.PadRight(30), todoItem.Complete ?
"Now complete!" : "Incomplete!"));
List<string> items = await query.ToListAsync();
これまでに説明したすべての関数は加法であるため、それらを連結し続けることができます。 各チェーン呼び出しは、より多くのクエリに影響します。 もう 1 つの例:
MobileServiceTableQuery<TodoItem> query = todoTable
.Where(todoItem => todoItem.Complete == false)
.Select(todoItem => todoItem.Text)
.Skip(3).
.Take(3);
List<string> items = await query.ToListAsync();
方法: ID でデータを検索する
LookupAsync 関数を使用して、特定の ID を持つデータベースからオブジェクトを検索できます。
// This query filters out the item with the ID of 37BBF396-11F0-4B39-85C8-B319C729AF6D
TodoItem item = await todoTable.LookupAsync("37BBF396-11F0-4B39-85C8-B319C729AF6D");
方法: 型指定されていないクエリを実行する
型指定されていないテーブル オブジェクトを使用してクエリを実行する場合は、次の例のように、ReadAsyncを呼び出して OData クエリ文字列を明示的に指定する必要があります。
// Lookup untyped data using OData
JToken untypedItems = await untypedTodoTable.ReadAsync("$filter=complete eq 0&$orderby=text");
プロパティ バッグのように使用できる JSON 値が返されます。 JToken と Newtonsoft Json.NET の詳細については、Json.NET サイトを参照してください。
方法: モバイル アプリ バックエンドにデータを挿入する
すべてのクライアント型には、IDという名前のメンバーが含まれている必要があります。既定では文字列です。 CRUD 操作とオフライン同期を実行するには、この ID が必要です。次のコードは、InsertAsync メソッドを使用して新しい行をテーブルに挿入する方法を示しています。 パラメーターには、.NET オブジェクトとして挿入するデータが含まれています。
await todoTable.InsertAsync(todoItem);
挿入中に一意のカスタム ID 値が todoItem
に含まれていない場合は、サーバーによって GUID が生成されます。
生成された ID は、呼び出しが返された後にオブジェクトを調べることで取得できます。
型指定されていないデータを挿入するには、Json.NET を利用できます。
JObject jo = new JObject();
jo.Add("Text", "Hello World");
jo.Add("Complete", false);
var inserted = await table.InsertAsync(jo);
電子メール アドレスを一意の文字列 ID として使用する例を次に示します。
JObject jo = new JObject();
jo.Add("id", "myemail@emaildomain.com");
jo.Add("Text", "Hello World");
jo.Add("Complete", false);
var inserted = await table.InsertAsync(jo);
ID 値の操作
Mobile Apps では、テーブルの ID 列に対する一意のカスタム文字列値がサポートされています。 文字列値を使用すると、アプリケーションは、電子メール アドレスやユーザー名などのカスタム値を ID に使用できます。 文字列 ID には、次の利点があります。
- ID は、データベースへのラウンド トリップを行わずに生成されます。
- レコードは、さまざまなテーブルまたはデータベースから簡単にマージできます。
- ID 値は、アプリケーションのロジックとより適切に統合できます。
挿入されたレコードに文字列 ID 値が設定されていない場合、モバイル アプリ バックエンドによって ID の一意の値が生成されます。 Guid.NewGuid メソッドを使用して、クライアントまたはバックエンドで独自の ID 値を生成できます。
JObject jo = new JObject();
jo.Add("id", Guid.NewGuid().ToString("N"));
方法: モバイル アプリ バックエンドのデータを変更する
次のコードは、UpdateAsync メソッドを使用して、同じ ID の既存のレコードを新しい情報で更新する方法を示しています。 このパラメーターには、.NET オブジェクトとして更新するデータが含まれています。
await todoTable.UpdateAsync(todoItem);
型指定されていないデータを更新するには、次のように Json.NET を利用できます。
JObject jo = new JObject();
jo.Add("id", "37BBF396-11F0-4B39-85C8-B319C729AF6D");
jo.Add("Text", "Hello World");
jo.Add("Complete", false);
var inserted = await table.UpdateAsync(jo);
更新を行うときは、id
フィールドを指定する必要があります。 バックエンドは、id
フィールドを使用して、更新する行を識別します。 id
フィールドは、InsertAsync
呼び出しの結果から取得できます。 ArgumentException
値を指定せずに項目を更新しようとすると、id
が発生します。
方法: モバイル アプリ バックエンドのデータを削除する
次のコードは、DeleteAsync メソッドを使用して既存のインスタンスを削除する方法を示しています。 インスタンスは、id
に設定された todoItem
フィールドによって識別されます。
await todoTable.DeleteAsync(todoItem);
型指定されていないデータを削除するには、次のように Json.NET を利用できます。
JObject jo = new JObject();
jo.Add("id", "37BBF396-11F0-4B39-85C8-B319C729AF6D");
await table.DeleteAsync(jo);
削除要求を行うときは、ID を指定する必要があります。 その他のプロパティはサービスに渡されず、サービスでは無視されます。 通常、DeleteAsync
呼び出しの結果は null
。 渡す ID は、InsertAsync
呼び出しの結果として得られる値から取得できます。 MobileServiceInvalidOperationException
フィールドを指定せずにアイテムを削除しようとすると、id
がスローされます。
方法: 競合解決にオプティミスティック コンカレンシーを使用する
2 つ以上のクライアントが、同じ項目に同時に変更を書き込む場合があります。 競合の検出が行われない場合、最後の書き込みが以前の更新を上書きしてしまうことになります。 オプティミスティック コンカレンシー制御 は、各トランザクションがコミットできるため、リソース ロックを使用しないことを前提としています。 トランザクションをコミットする前に、オプティミスティック コンカレンシー制御によって、他のトランザクションによってデータが変更されていないことが確認されます。 データが変更された場合、コミットトランザクションはロールバックされます。
Mobile Apps では、モバイル アプリ バックエンドの各テーブルに対して定義されている version
システム プロパティ列を使用して各項目の変更を追跡することで、オプティミスティック コンカレンシー制御をサポートしています。 レコードが更新されるたびに、Mobile Apps はそのレコードの version
プロパティを新しい値に設定します。 更新要求のたびに、要求に含まれるレコードの version
プロパティが、サーバー上のレコードの同じプロパティと比較されます。 要求で渡されたバージョンがバックエンドと一致しない場合、クライアント ライブラリは MobileServicePreconditionFailedException<T>
例外を発生させます。 例外に含まれる型は、サーバー バージョンのレコードを含むバックエンドからのレコードです。 その後、アプリケーションはこの情報を使用して、バックエンドから正しい version
値を使用して更新要求を再実行して変更をコミットするかどうかを決定できます。
version
システム プロパティのテーブル クラスに列を定義して、オプティミスティック コンカレンシーを有効にします。 次に例を示します。
public class TodoItem
{
public string Id { get; set; }
[JsonProperty(PropertyName = "text")]
public string Text { get; set; }
[JsonProperty(PropertyName = "complete")]
public bool Complete { get; set; }
// *** Enable Optimistic Concurrency *** //
[JsonProperty(PropertyName = "version")]
public string Version { set; get; }
}
型指定されていないテーブルを使用するアプリケーションでは、次のようにテーブルの Version
に SystemProperties
フラグを設定することで、オプティミスティック コンカレンシーを有効にします。
//Enable optimistic concurrency by retrieving version
todoTable.SystemProperties |= MobileServiceSystemProperties.Version;
オプティミスティックコンカレンシーを有効にするだけでなく、MobileServicePreconditionFailedException<T>
メソッドで、 例外をコード内でキャッチする必要もあります。 更新されたレコードに正しい version
を適用して競合を解決し、解決されたレコード UpdateAsync を呼び出します。 次のコードは、一度検出された書き込み競合を解決する方法を示しています。
private async void UpdateToDoItem(TodoItem item)
{
MobileServicePreconditionFailedException<TodoItem> exception = null;
try
{
//update at the remote table
await todoTable.UpdateAsync(item);
}
catch (MobileServicePreconditionFailedException<TodoItem> writeException)
{
exception = writeException;
}
if (exception != null)
{
// Conflict detected, the item has changed since the last query
// Resolve the conflict between the local and server item
await ResolveConflict(item, exception.Item);
}
}
private async Task ResolveConflict(TodoItem localItem, TodoItem serverItem)
{
//Ask user to choose the resolution between versions
MessageDialog msgDialog = new MessageDialog(
String.Format("Server Text: \"{0}\" \nLocal Text: \"{1}\"\n",
serverItem.Text, localItem.Text),
"CONFLICT DETECTED - Select a resolution:");
UICommand localBtn = new UICommand("Commit Local Text");
UICommand ServerBtn = new UICommand("Leave Server Text");
msgDialog.Commands.Add(localBtn);
msgDialog.Commands.Add(ServerBtn);
localBtn.Invoked = async (IUICommand command) =>
{
// To resolve the conflict, update the version of the item being committed. Otherwise, you will keep
// catching a MobileServicePreConditionFailedException.
localItem.Version = serverItem.Version;
// Updating recursively here just in case another change happened while the user was making a decision
UpdateToDoItem(localItem);
};
ServerBtn.Invoked = async (IUICommand command) =>
{
RefreshTodoItems();
};
await msgDialog.ShowAsync();
}
詳細については、「Azure Mobile Apps でのオフライン データ同期の 」 トピックを参照してください。
方法: Mobile Apps データを Windows ユーザー インターフェイスにバインドする
このセクションでは、Windows アプリの UI 要素を使用して返されたデータ オブジェクトを表示する方法について説明します。 次のコード例では、不完全な項目のクエリを使用してリストのソースにバインドします。 MobileServiceCollection は、Mobile Apps 対応のバインド コレクションを作成します。
// This query filters out completed TodoItems.
MobileServiceCollection<TodoItem, TodoItem> items = await todoTable
.Where(todoItem => todoItem.Complete == false)
.ToCollectionAsync();
// itemsControl is an IEnumerable that could be bound to a UI list control
IEnumerable itemsControl = items;
// Bind this to a ListBox
ListBox lb = new ListBox();
lb.ItemsSource = items;
マネージド ランタイムの一部のコントロールでは、ISupportIncrementalLoadingと呼ばれるインターフェイスがサポートされています。 このインターフェイスを使用すると、コントロールは、ユーザーがスクロールするときに追加のデータを要求できます。 MobileServiceIncrementalLoadingCollectionを介してユニバーサル Windows アプリに対するこのインターフェイスのサポートが組み込まれています。これにより、コントロールからの呼び出しが自動的に処理されます。 次のように Windows アプリで MobileServiceIncrementalLoadingCollection
を使用します。
MobileServiceIncrementalLoadingCollection<TodoItem,TodoItem> items;
items = todoTable.Where(todoItem => todoItem.Complete == false).ToIncrementalLoadingCollection();
ListBox lb = new ListBox();
lb.ItemsSource = items;
Windows Phone 8 および "Silverlight" アプリで新しいコレクションを使用するには、ToCollection
と IMobileServiceTableQuery<T>
で IMobileServiceTable<T>
拡張メソッドを使用します。 データを読み込むには、LoadMoreItemsAsync()
を呼び出します。
MobileServiceCollection<TodoItem, TodoItem> items = todoTable.Where(todoItem => todoItem.Complete==false).ToCollection();
await items.LoadMoreItemsAsync();
ToCollectionAsync
または ToCollection
を呼び出して作成されたコレクションを使用すると、UI コントロールにバインドできるコレクションが取得されます。 このコレクションはページングに対応しています。 コレクションはネットワークからデータを読み込むため、読み込みに失敗することがあります。 このようなエラーを処理するには、OnException
の MobileServiceIncrementalLoadingCollection
メソッドをオーバーライドして、LoadMoreItemsAsync
の呼び出しによって発生する例外を処理します。
テーブルに多数のフィールドがあるが、その一部のみをコントロールに表示することを検討してください。 前のセクション「特定の列を選択」のガイダンスを使用して、UI に表示する特定の列を選択できます。
ページ サイズを変更する
Azure Mobile Apps は、既定で要求ごとに最大 50 個の項目を返します。 ページング サイズは、クライアントとサーバーの両方で最大ページ サイズを大きくすることで変更できます。 要求されたページ サイズを大きくするには、PullOptions
を使用するときに PullAsync()
を指定します。
PullOptions pullOptions = new PullOptions
{
MaxPageSize = 100
};
PageSize
がサーバー内で 100 以上であると仮定すると、要求は最大 100 個の項目を返します。
オフライン テーブルでの作業
オフライン テーブルでは、ローカル SQLite ストアを使用して、オフライン時に使用するデータを格納します。 すべてのテーブル操作は、リモート サーバー ストアではなくローカル SQLite ストアに対して実行されます。 オフライン テーブルを作成するには、まずプロジェクトを準備します。
Visual Studio で、[ソリューションの NuGet パッケージの管理] >ソリューションを右クリックし、ソリューション内のすべてのプロジェクトの Microsoft.Azure.Mobile.Client.SQLiteStore NuGet パッケージを検索してインストールします。
(省略可能)Windows デバイスをサポートするには、次のいずれかの SQLite ランタイム パッケージをインストールします。
- Windows 8.1 ランタイム: SQLite を Windows 8.1用にインストールします。
- Windows Phone 8.1: Windows Phone 8.1 用 SQLiteインストールします。
- ユニバーサル Windows プラットフォーム ユニバーサル Windows 用の SQLiteインストールします。
(省略可能)。 Windows デバイスの場合は、[参照>参照の追加]をクリックし、Windows フォルダーを展開して >拡張機能を開き、適切な SQLite for Windows SDK と Visual C++ 2013 Runtime for Windows SDK を有効にします。 SQLite SDK の名前は、Windows プラットフォームごとに若干異なります。
テーブル参照を作成する前に、ローカル ストアを準備する必要があります。
var store = new MobileServiceSQLiteStore(Constants.OfflineDbPath);
store.DefineTable<TodoItem>();
//Initializes the SyncContext using the default IMobileServiceSyncHandler.
await this.client.SyncContext.InitializeAsync(store);
通常、ストアの初期化は、クライアントが作成された直後に行われます。 OfflineDbPath は、サポートするすべてのプラットフォームでの使用に適したファイル名にする必要があります。 パスが完全修飾パス (つまり、スラッシュで始まる) の場合は、そのパスが使用されます。 パスが完全修飾されていない場合、ファイルはプラットフォーム固有の場所に配置されます。
- iOS および Android デバイスの場合、既定のパスは "個人用ファイル" フォルダーです。
- Windows デバイスの場合、既定のパスはアプリケーション固有の "AppData" フォルダーです。
テーブル参照は、GetSyncTable<>
メソッドを使用して取得できます。
var table = client.GetSyncTable<TodoItem>();
オフライン テーブルを使用するために認証する必要はありません。 認証が必要なのは、バックエンド サービスと通信している場合のみです。
オフライン テーブルの同期
オフライン テーブルは、既定ではバックエンドと同期されません。 同期は 2 つの部分に分割されます。 新しいアイテムのダウンロードとは別に変更をプッシュできます。 一般的な同期方法を次に示します。
public async Task SyncAsync()
{
ReadOnlyCollection<MobileServiceTableOperationError> syncErrors = null;
try
{
await this.client.SyncContext.PushAsync();
await this.todoTable.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
"allTodoItems",
this.todoTable.CreateQuery());
}
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)
{
//Update failed, reverting to server's copy.
await error.CancelAndUpdateItemAsync(error.Result);
}
else
{
// Discard local change.
await error.CancelAndDiscardItemAsync();
}
Debug.WriteLine(@"Error executing sync operation. Item: {0} ({1}). Operation discarded.", error.TableName, error.Item["id"]);
}
}
}
PullAsync
する最初の引数が null の場合、増分同期は使用されません。 各同期操作では、すべてのレコードが取得されます。
SDK は、レコードをプルする前に暗黙的な PushAsync()
を実行します。
競合処理は、PullAsync()
メソッドで発生します。 オンライン テーブルと同じ方法で競合に対処できます。 競合は、挿入、更新、または削除中ではなく、PullAsync()
が呼び出されたときに生成されます。 複数の競合が発生した場合、それらは 1 つの MobileServicePushFailedException にバンドルされます。 各エラーを個別に処理します。
カスタム API を操作する
カスタム API を使用すると、挿入、更新、削除、または読み取り操作にマップされないサーバー機能を公開するカスタム エンドポイントを定義できます。 カスタム API を使用すると、HTTP メッセージ ヘッダーの読み取りと設定、JSON 以外のメッセージ本文形式の定義など、メッセージングをより詳細に制御できます。
カスタム API を呼び出すには、クライアントで InvokeApiAsync メソッドのいずれかを呼び出します。 たとえば、次のコード行は、バックエンドの completeAll API に POST 要求を送信します。
var result = await client.InvokeApiAsync<MarkAllResult>("completeAll", System.Net.Http.HttpMethod.Post, null);
このフォームは型指定されたメソッド呼び出しであり、MarkAllResult 戻り値の型が定義されている必要があります。 型指定されたメソッドと型指定されていないメソッドの両方がサポートされています。
InvokeApiAsync() メソッドは、API が '/' で始まる場合を除き、呼び出す API の先頭に '/api/' を付加します。 次に例を示します。
- バックエンドで /api/completeAll を呼び出す
InvokeApiAsync("completeAll",...)
- バックエンドで /.auth/me を呼び出す
InvokeApiAsync("/.auth/me",...)
InvokeApiAsync を使用して、Azure Mobile Apps で定義されていない Web API を含む任意の WebAPI を呼び出すことができます。 InvokeApiAsync() を使用すると、認証ヘッダーを含む適切なヘッダーが要求と共に送信されます。
ユーザーの認証
Mobile Apps では、Facebook、Google、Microsoft アカウント、Twitter、Azure Active Directory など、さまざまな外部 ID プロバイダーを使用したアプリ ユーザーの認証と承認がサポートされています。 特定の操作のアクセスを認証済みユーザーのみに制限するように、テーブルに対するアクセス許可を設定できます。 認証されたユーザーの ID を使用して、サーバー スクリプトに承認規則を実装することもできます。 詳細については、チュートリアル「アプリに認証を追加する」を参照してください。
2 つの認証フローがサポートされています: クライアントで管理されるフロー と、サーバーで管理されるフロー。 サーバー管理フローは、プロバイダーの Web 認証インターフェイスに依存するため、最も単純な認証エクスペリエンスを提供します。 クライアント管理フローでは、プロバイダー固有のデバイス固有の SDK に依存するため、デバイス固有の機能とのより深い統合が可能になります。
注
運用アプリでは、クライアントで管理されるフローを使用することをお勧めします。
認証を設定するには、アプリを 1 つ以上の ID プロバイダーに登録する必要があります。 ID プロバイダーは、アプリのクライアント ID とクライアント シークレットを生成します。 これらの値は、Azure App Service の認証/承認を有効にするためにバックエンドで設定されます。 詳細については、「アプリ に認証を追加する」チュートリアルの詳細な手順に従ってください。
このセクションでは、次のトピックについて説明します。
- クライアント管理の認証
- サーバー管理の認証
- 認証トークン のキャッシュ
クライアントで管理される認証
アプリは個別に ID プロバイダーに連絡し、バックエンドへのログイン時に返されたトークンを提供できます。 このクライアント フローを使用すると、ユーザーにシングル サインオン エクスペリエンスを提供したり、ID プロバイダーから追加のユーザー データを取得したりできます。 ID プロバイダー SDK は、よりネイティブな UX の操作性を提供し、追加のカスタマイズが可能であるため、クライアント フロー認証はサーバー フローを使用することをお勧めします。
次のクライアント フロー認証パターンの例を示します。
Active Directory 認証ライブラリを使用してユーザーを認証する
Active Directory 認証ライブラリ (ADAL) を使用して、Azure Active Directory 認証を使用してクライアントからユーザー認証を開始できます。
Active Directory ログイン用に App Service を構成する方法に関するチュートリアルに従って、AAD サインオン用のモバイル アプリ バックエンド 構成します。 ネイティブ クライアント アプリケーションを登録するオプションの手順を必ず完了してください。
Visual Studio または Xamarin Studio で、プロジェクトを開き、
Microsoft.IdentityModel.Clients.ActiveDirectory
NuGet パッケージへの参照を追加します。 検索する場合は、プレリリース バージョンを含めます。使用しているプラットフォームに応じて、次のコードをアプリケーションに追加します。 それぞれで、次の置換を行います。
INSERT-AUTHORITY-HERE を、アプリケーションをプロビジョニングしたテナントの名前に置き換えます。 形式は
https://login.microsoftonline.com/contoso.onmicrosoft.com
する必要があります。 この値は、Azure portalの Azure Active Directory の [ドメイン] タブからコピーできます。INSERT-RESOURCE-ID-HERE をモバイル アプリ バックエンドのクライアント ID に置き換えます。 クライアント ID は、ポータルの [Azure Active Directory 設定] の [詳細設定] タブから取得できます。
INSERT-CLIENT-ID-HERE を、ネイティブ クライアント アプリケーションからコピーしたクライアント ID に置き換えます。
INSERT-REDIRECT-URI-HERE を、HTTPS スキームを使用して、サイトの /.auth/login/done エンドポイントに置き換えます。 この値は、
https://contoso.azurewebsites.net/.auth/login/done
のようになります。各プラットフォームに必要なコードは次のとおりです。
ウィンドウズ:
private MobileServiceUser user; private async Task AuthenticateAsync() { string authority = "INSERT-AUTHORITY-HERE"; string resourceId = "INSERT-RESOURCE-ID-HERE"; string clientId = "INSERT-CLIENT-ID-HERE"; string redirectUri = "INSERT-REDIRECT-URI-HERE"; while (user == null) { string message; try { AuthenticationContext ac = new AuthenticationContext(authority); AuthenticationResult ar = await ac.AcquireTokenAsync(resourceId, clientId, new Uri(redirectUri), new PlatformParameters(PromptBehavior.Auto, false) ); JObject payload = new JObject(); payload["access_token"] = ar.AccessToken; user = await App.MobileService.LoginAsync( MobileServiceAuthenticationProvider.WindowsAzureActiveDirectory, payload); message = string.Format("You are now logged in - {0}", user.UserId); } catch (InvalidOperationException) { message = "You must log in. Login Required"; } var dialog = new MessageDialog(message); dialog.Commands.Add(new UICommand("OK")); await dialog.ShowAsync(); } }
Xamarin.iOS
private MobileServiceUser user; private async Task AuthenticateAsync(UIViewController view) { string authority = "INSERT-AUTHORITY-HERE"; string resourceId = "INSERT-RESOURCE-ID-HERE"; string clientId = "INSERT-CLIENT-ID-HERE"; string redirectUri = "INSERT-REDIRECT-URI-HERE"; try { AuthenticationContext ac = new AuthenticationContext(authority); AuthenticationResult ar = await ac.AcquireTokenAsync(resourceId, clientId, new Uri(redirectUri), new PlatformParameters(view)); JObject payload = new JObject(); payload["access_token"] = ar.AccessToken; user = await client.LoginAsync( MobileServiceAuthenticationProvider.WindowsAzureActiveDirectory, payload); } catch (Exception ex) { Console.Error.WriteLine(@"ERROR - AUTHENTICATION FAILED {0}", ex.Message); } }
Xamarin.Android
private MobileServiceUser user; private async Task AuthenticateAsync() { string authority = "INSERT-AUTHORITY-HERE"; string resourceId = "INSERT-RESOURCE-ID-HERE"; string clientId = "INSERT-CLIENT-ID-HERE"; string redirectUri = "INSERT-REDIRECT-URI-HERE"; try { AuthenticationContext ac = new AuthenticationContext(authority); AuthenticationResult ar = await ac.AcquireTokenAsync(resourceId, clientId, new Uri(redirectUri), new PlatformParameters(this)); JObject payload = new JObject(); payload["access_token"] = ar.AccessToken; user = await client.LoginAsync( MobileServiceAuthenticationProvider.WindowsAzureActiveDirectory, payload); } catch (Exception ex) { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.SetMessage(ex.Message); builder.SetTitle("You must log in. Login Required"); builder.Create().Show(); } } protected override void OnActivityResult(int requestCode, Result resultCode, Intent data) { base.OnActivityResult(requestCode, resultCode, data); AuthenticationAgentContinuationHelper.SetAuthenticationAgentContinuationEventArgs(requestCode, resultCode, data); }
Google または Facebook のトークンを用いた単独の Sign-On
このスニペットに示すように、Facebook または Google のクライアント フローを使用できます。
var token = new JObject();
// Replace access_token_value with actual value of your access token obtained
// using the Facebook or Google SDK.
token.Add("access_token", "access_token_value");
private MobileServiceUser user;
private async Task AuthenticateAsync()
{
while (user == null)
{
string message;
try
{
// Change MobileServiceAuthenticationProvider.Facebook
// to MobileServiceAuthenticationProvider.Google if using Google auth.
user = await client.LoginAsync(MobileServiceAuthenticationProvider.Facebook, token);
message = string.Format("You are now logged in - {0}", user.UserId);
}
catch (InvalidOperationException)
{
message = "You must log in. Login Required";
}
var dialog = new MessageDialog(message);
dialog.Commands.Add(new UICommand("OK"));
await dialog.ShowAsync();
}
}
サーバー管理認証
ID プロバイダーを登録したら、[MobileServiceClient] の LoginAsync メソッドを、プロバイダーの MobileServiceAuthenticationProvider 値と共に呼び出します。 たとえば、次のコードでは、Facebook を使用してサーバー フローのサインインを開始します。
private MobileServiceUser user;
private async System.Threading.Tasks.Task Authenticate()
{
while (user == null)
{
string message;
try
{
user = await client
.LoginAsync(MobileServiceAuthenticationProvider.Facebook);
message =
string.Format("You are now logged in - {0}", user.UserId);
}
catch (InvalidOperationException)
{
message = "You must log in. Login Required";
}
var dialog = new MessageDialog(message);
dialog.Commands.Add(new UICommand("OK"));
await dialog.ShowAsync();
}
}
Facebook 以外の ID プロバイダーを使用している場合は、MobileServiceAuthenticationProvider の値をプロバイダーの値に変更します。
サーバー フローでは、Azure App Service によって、選択したプロバイダーのサインイン ページが表示され、OAuth 認証フローが管理されます。 ID プロバイダーが返されると、Azure App Service によって App Service 認証トークンが生成されます。 LoginAsync メソッドは、認証されたユーザーの UserId と MobileServiceAuthenticationTokenの両方を JSON Web トークン (JWT) として提供する、MobileServiceUserを返します。 このトークンは、有効期限が切れるまでキャッシュして再利用できます。 詳細については、「認証トークン のキャッシュをする」を参照してください。
Xamarin (Android または iOS) を使用する場合は、Xamarin.Essentials WebAuthenticator が使用されます。 既定のコンテキスト (Android) または UIViewController (iOS) を LoginAsync
メソッドに渡す必要があります。 さらに、Web 認証システムからのリターンを処理する必要があります。 Android では、これは MainActivity.cs
内で処理されます。
public override void OnResume()
{
base.OnResume();
Xamarin.Essentials.Platform.OnResume();
}
iOS では、これはAppDelegate.cs内で処理されます。
public override bool OpenUrl(UIApplication app, NSUrl url, NSDictionary options)
{
if (client.ResumeWithURL(app, url, options))
return true;
return base.OpenUrl(app, url, options);
}
認証トークンのキャッシュ
場合によっては、プロバイダーからの認証トークンを格納することで、最初の認証が成功した後にログイン メソッドの呼び出しを回避できます。 Microsoft Store アプリと UWP アプリでは、次のように、PasswordVault を使用して、サインインが成功した後に現在の認証トークンをキャッシュできます。
await client.LoginAsync(MobileServiceAuthenticationProvider.Facebook);
PasswordVault vault = new PasswordVault();
vault.Add(new PasswordCredential("Facebook", client.currentUser.UserId,
client.currentUser.MobileServiceAuthenticationToken));
UserId 値は資格情報の UserName として格納され、トークンはパスワードとして格納されます。 以降の起動時に、キャッシュされた資格情報の PasswordVault を確認できます。 次の例では、キャッシュされた資格情報が見つかったときに資格情報を使用し、それ以外の場合はバックエンドで再認証を試みます。
// Try to retrieve stored credentials.
var creds = vault.FindAllByResource("Facebook").FirstOrDefault();
if (creds != null)
{
// Create the current user from the stored credentials.
client.currentUser = new MobileServiceUser(creds.UserName);
client.currentUser.MobileServiceAuthenticationToken =
vault.Retrieve("Facebook", creds.UserName).Password;
}
else
{
// Regular login flow and cache the token as shown above.
}
ユーザーをサインアウトする場合は、次のように、保存されている資格情報も削除する必要があります。
client.Logout();
vault.Remove(vault.Retrieve("Facebook", client.currentUser.UserId));
Xamarin アプリでは、Xamarin.Auth API を使用して、資格情報を アカウント オブジェクトに安全に格納します。 これらの API の使用例については、ContosoMoments 写真共有サンプルの AuthStore.cs コード ファイルを参照してください。
クライアント管理認証を使用する場合は、Facebook や Twitter などのプロバイダーから取得したアクセス トークンをキャッシュすることもできます。 このトークンは、次のようにバックエンドから新しい認証トークンを要求するために指定できます。
var token = new JObject();
// Replace <your_access_token_value> with actual value of your access token
token.Add("access_token", "<your_access_token_value>");
// Authenticate using the access token.
await client.LoginAsync(MobileServiceAuthenticationProvider.Facebook, token);
プッシュ通知
次のトピックでは、プッシュ通知について説明します。
方法: プッシュ通知に登録する
Mobile Apps クライアントを使用すると、Azure Notification Hubs にプッシュ通知を登録できます。 登録時に、プラットフォーム固有のプッシュ通知サービス (PNS) から取得したハンドルを取得します。 その後、登録を作成するときに、この値を任意のタグと共に指定します。 次のコードは、プッシュ通知用の Windows アプリを Windows Notification Service (WNS) に登録します。
private async void InitNotificationsAsync()
{
// Request a push notification channel.
var channel = await PushNotificationChannelManager.CreatePushNotificationChannelForApplicationAsync();
// Register for notifications using the new channel.
await MobileService.GetPush().RegisterNativeAsync(channel.Uri, null);
}
WNS にプッシュする場合は、Microsoft Store パッケージ SID を取得必要があります。 テンプレート登録の登録方法など、Windows アプリの詳細については、「プッシュ通知をアプリに追加する」を参照してください。
クライアントからのタグの要求はサポートされていません。 タグ要求は登録から自動的に削除されます。
デバイスをタグに登録する場合は、Notification Hubs API を使用してユーザーに代わって登録を実行するカスタム API を作成します。 RegisterNativeAsync()
メソッドの代わりにカスタム API を呼び出します。
方法: Microsoft Store パッケージ SID を取得する
Microsoft Store アプリでプッシュ通知を有効にするには、パッケージ SID が必要です。 パッケージ SID を受け取るために、Microsoft Store にアプリケーションを登録します。
この値を取得するには:
- Visual Studio ソリューション エクスプローラーで、Microsoft Store アプリ プロジェクトを右クリック 、[ストア]>[アプリをストアに関連付ける]をクリックします。..。
- ウィザードで [次] をクリックし、Microsoft アカウントでサインインして、[新しいアプリ名の予約]でアプリの名前を入力し、[予約] をクリックします。
- アプリの登録が正常に作成されたら、アプリ名を選択し、[次] をクリックし、[関連付け] をクリックします。
- Microsoft アカウントを使用して、Windows デベロッパー センター にログインします。 [マイ アプリ で、作成したアプリの登録をクリックします。
- アプリ管理>アプリのアイデンティティをクリックし、下にスクロールして パッケージ SIDを見つけます。
パッケージ SID の多くの使用では URI として扱われます。その場合は、ms-app:// をスキームとして使用する必要があります。 この値をプレフィックスとして連結して形成されたパッケージ SID のバージョンを書き留めます。
Xamarin アプリでは、iOS または Android プラットフォームで実行されているアプリを登録できるようにするために、追加のコードが必要です。 詳細については、プラットフォームのトピックを参照してください。
- Xamarin.Android
- Xamarin.iOS
方法: クロスプラットフォーム通知を送信するプッシュ テンプレートを登録する
テンプレートを登録するには、次のようにテンプレートで RegisterAsync()
メソッドを使用します。
JObject templates = myTemplates();
MobileService.GetPush().RegisterAsync(channel.Uri, templates);
テンプレートは JObject
型にする必要があり、次の JSON 形式で複数のテンプレートを含めることができます。
public JObject myTemplates()
{
// single template for Windows Notification Service toast
var template = "<toast><visual><binding template=\"ToastText01\"><text id=\"1\">$(message)</text></binding></visual></toast>";
var templates = new JObject
{
["generic-message"] = new JObject
{
["body"] = template,
["headers"] = new JObject
{
["X-WNS-Type"] = "wns/toast"
},
["tags"] = new JArray()
},
["more-templates"] = new JObject {...}
};
return templates;
}
RegisterAsync() メソッドは、セカンダリ タイルも受け入れます。
MobileService.GetPush().RegisterAsync(string channelUri, JObject templates, JObject secondaryTiles);
すべてのタグは、セキュリティのために登録中に削除されます。 インストール内のインストールまたはテンプレートにタグを追加するには、「.NET バックエンド サーバー SDK for Azure Mobile Apps の操作」を参照してください。
これらの登録済みテンプレートを使用して通知を送信するには、Notification Hubs APIを参照してください。
その他のトピック
方法: エラーを処理する
バックエンドでエラーが発生すると、クライアントSDKがMobileServiceInvalidOperationException
イベントを発生させます。 次の例は、バックエンドによって返される例外を処理する方法を示しています。
private async void InsertTodoItem(TodoItem todoItem)
{
// This code inserts a new TodoItem into the database. When the operation completes
// and App Service has assigned an Id, the item is added to the CollectionView
try
{
await todoTable.InsertAsync(todoItem);
items.Add(todoItem);
}
catch (MobileServiceInvalidOperationException e)
{
// Handle error
}
}
エラー状態を処理するもう 1 つの例は、Mobile Apps ファイルのサンプルにあります。 LoggingHandler 例では、バックエンドに対して行われた要求をログに記録するログ デリゲート ハンドラーを提供します。
方法: 要求ヘッダーをカスタマイズする
特定のアプリ シナリオをサポートするには、モバイル アプリ バックエンドとの通信をカスタマイズすることが必要になる場合があります。 たとえば、すべての送信要求にカスタム ヘッダーを追加したり、応答の状態コードを変更したりできます。 次の例のように、カスタム DelegatingHandlerを使用できます。
public async Task CallClientWithHandler()
{
MobileServiceClient client = new MobileServiceClient("AppUrl", new MyHandler());
IMobileServiceTable<TodoItem> todoTable = client.GetTable<TodoItem>();
var newItem = new TodoItem { Text = "Hello world", Complete = false };
await todoTable.InsertAsync(newItem);
}
public class MyHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage>
SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
// Change the request-side here based on the HttpRequestMessage
request.Headers.Add("x-my-header", "my value");
// Do the request
var response = await base.SendAsync(request, cancellationToken);
// Change the response-side here based on the HttpResponseMessage
// Return the modified response
return response;
}
}