5/29-30 de:code セッション SV-007 “パワフル モバイル アプリ開発 ~ 最新 Microsoft Azure Mobile Services をフル活用しよう!~ ” フォローアップ (2)
皆様、こんにちは!多くの方に(1)をご参照いただき、感謝です。引き続き、(2)に入りたいと思います。今回は、Windows ストアアプリ側の CRUD 処理ですね。
これに関しては、参加者向けの事前公開資料の中にある、Windows 8.1 ビジネスストアアプリ開発 - ハンズオンラボ .zip のUI(XAML)をそのまま使うという話をさせていただきました。こちらは、参加者の皆様に限り、ダウンロードできるようになっていますので、参加された方は、メール等に記載されている URLを参照して戴き、ZIP(の中にあるPDFやプロジェクトファイル等)をダウンロードしてください。
というわけで、このフォローアップは、本 de:code イベント参加者向けのセッションフォローアップですので、ここでは、その(ストアアプリ内にある)XAMLがあることを前提に、 セッションでご紹介したクライアント側の CRUD の処理をご紹介したいと思います。ご了承くださいませ。
Mobile Services のエンドポイントの追加
まずは、当該 ストアアプリ部分を、ハンズオンラボの手順に従って追加して戴きます。その後、App.xaml.cs を開き、下記の部分をエンドポイントとして追加してください。これにより、CRUD(新規作成、読み取り、更新、削除)処理が可能となります。
1: ・・・
2: /// <summary>
3: /// 既定の Application クラスに対してアプリケーション独自の動作を実装します。
4: /// </summary>
5: sealed partial class App : Application
6: {
7: //Mobile Services エンドポイント
8: public static MobileServiceClient MobileService = new MobileServiceClient(
9: "https://instphotoapp.azure-mobile.net/",
10: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
11: /// <summary>
12: /// 単一アプリケーション オブジェクトを初期化します。これは、実行される作成したコードの
13: /// 最初の行であり、main() または WinMain() と論理的に等価です。
14: /// </summary>
15: public App()
16: {
17: ・・・
この xxx 部分がアプリケーション用に発行されたキーとなりますので、前回の(1)で最後に発行した際に作成された Mobile Services のサイトを開き、コピーボタンを使って、こちらからコピーしてください。
Mobile Services の CRUD 処理の追加
ここからは、MainPage.xaml.cs を開き、CRUD 処理を追加していきます。
変数の宣言
それぞれ、商品のコレクション、テーブルコントローラーへの接続、削除用のID(文字列型)、そして更新用のフライアウト用の変数、 となります。
1: public sealed partial class MainPage : Page
2: {
3: private MobileServiceCollection<ProductWithPrice, ProductWithPrice>
4: productwithprices;
5: private IMobileServiceTable<ProductWithPrice>
6: ProdutwithPriceTable =
7: App.MobileService.GetTable<ProductWithPrice>();
8: public string IdToDelete;
9: private Popup UpdateProduct = null;
商品取得処理
全商品を取得するメソッド RefreshProducts() を作ります。これだけで全商品の参照が可能です。セッションでもご紹介した通り、LINQ のクエリーの中で、StoreName ではなく、ProductName 等でソートすることも可能です。変更して、実行してみてください。
1: public async void RefreshProducts()
2: {
3: MobileServiceInvalidOperationException exception = null;
4: try
5: {
6: productwithprices = await ProdutwithPriceTable
7: .OrderBy (ProdWithPrice => ProdWithPrice.StoreName)
8: .ToCollectionAsync();
9:
10: }
11: catch (MobileServiceInvalidOperationException e)
12: {
13: exception = e;
14: }
15:
16: if (exception != null)
17: {
18: await new MessageDialog(exception.Message, "商品一覧をロードできません!").ShowAsync();
19: }
20: else
21: {
22: ProductGridView.ItemsSource = productwithprices;
23: }
24: }
25: #endregion
クライアント側から呼び出されるのは、サービス側のコントローラーにあるこちらのメソッドになります。
1: // GET tables/ProductWithPrice
2: public IQueryable<ProductWithPrice> GetAllProductWithPrice()
3: {
4: return Query();
5: }
実行結果は、こんな感じになります。
商品削除処理
次に、削除の処理を書きます。こちらは、Id を含む商品データ全体を、選択したグリッドのアイテムから取得して、削除する処理にしてあります。
前半部分は、Bottom AppBar に追加された OnRemoveButtonClick のイベントハンドラの処理です。
前半部分は、Bottom AppBar に追加された OnRemoveButtonClick のイベントハンドラの処理です。
1: private async void OnRemoveButtonClick(object sender, RoutedEventArgs e)
2: {
3: var productwithprice =
4: (ProductWithPrice)ProductGridView.SelectedItem;
5: try
6: {
7:
8: // 商品を削除
9: string message = "商品データを削除します。";
10: var dialog = new MessageDialog(message);
11: dialog.Commands.Add(new UICommand("OK"));
12: await dialog.ShowAsync();
13:
14: IdToDelete = productwithprice.Id;
15: await DeleteProduct(IdToDelete);
16:
17: RefreshProducts();
18: var messageDialog =
19: new Windows.UI.Popups.
20: MessageDialog("商品データを削除しました。", "商品削除完了");
21: messageDialog.ShowAsync();
22:
23: }
24: catch
25: {
26: var messageDialog =
27: new Windows.UI.Popups.
28: MessageDialog("商品データが見つかりません。",
29: "エラー");
30: messageDialog.ShowAsync();
31: }
32: }
特定のグリッド内のアイテムを選択して実行すると、このようなイメージになります。
後半部分は、そこから呼び出される DeleteProduct (Id) メソッドです。セッションでは、Id で商品データを検索した結果である、results の LINQ クエリーの箇所にブレイクポイントを設定して、実際にどういうデータが渡されたかを見て戴きました。これにより、当該データがテーブルの DeleteAsync メソッドの引数として渡され、削除されます。
1: public async Task DeleteProduct(string id)
2: {
3: try
4: {
5: var results = (await ProdutwithPriceTable
6: .Where(p => p.Id == id)
7: .ToEnumerableAsync())
8: .Single();
9: await ProdutwithPriceTable.DeleteAsync(results);
10:
11: }
12: catch (MobileServiceInvalidOperationException e)
13: {
14: Debug.WriteLine(e.Message);
15: throw;
16: }
17: catch (Exception ex)
18: {
19: Debug.WriteLine(ex.Message);
20: throw;
21: }
22:
23:
24: }
OK を押すと、削除処理がされ、RefreshProducts() が呼ばれて、データを取得し直して ItemsSource に設定されます。
対応するサービス側のコントローラーにあるメソッドはこちらです。
1: // DELETE tables/ProductWithPrice/48D68C86-6EA6-4C25-AA33-223FC9A27959
2: public Task DeleteProductWithPrice(string id)
3: {
4: return DeleteAsync(id);
5: }
削除されたかど うかを、Visual Studio や、SQL Management Studio で SQL Azure Database に接続して、確認してみてください。
商品追加処理
次に、商品の追加処理を書きます。
前半部分は、Bottom AppBar に追加された OnAddButtonClick のイベントハンドラの処理です。こちらで、別途作成してある、Update 用の Dialog(UserControl)を開きます。
1: private void OnAddButtonClick(object sender, RoutedEventArgs e)
2: {
3: // ダイアログの作成
4: var addProductDialog = new AddProductDialog();
5:
6: addProductDialog.ProductAddEnded += OnProductAddEnded;
7:
8: // ポップアップの作成
9: var popup = new Windows.UI.Xaml.Controls.Primitives.Popup
10: {
11: Child = addProductDialog,
12: IsLightDismissEnabled = true
13: };
14:
15: // サイズ変更時の調整
16: addProductDialog.SizeChanged += (dialogSender, dialogArgs) =>
17: {
18: popup.VerticalOffset = this.ActualHeight
19: - addProductDialog.ActualHeight
20: - BottomAppBar.ActualHeight - 48;
21: popup.HorizontalOffset = 48;
22: };
23: //Popup 開く
24: popup.IsOpen = true;
25: }
1: public sealed partial class AddProductDialog : UserControl
2: {
3:
4: public AddProductDialog()
5: {
6: this.InitializeComponent();
7: }
8:
9: //追加
10: public event Action<AddProductDialog, ProductWithPrice> ProductAddEnded;
11:
12: private void addButton_Click(object sender, RoutedEventArgs e)
13: {
14: if (ProductAddEnded != null)
15: // 新しい Product を作ってイベント ハンドラーに渡す
16: ProductAddEnded(this, new ProductWithPrice
17: {
18: StoreName = codeTextBox.Text,
19: ProductName = nameTextBox.Text,
20: RetailPrice = sellPriceTextBox.Text,
21: ReasonforChangePrice = priceChangeReason.Text,
22: PriceFixedDate = DateTime.Now.ToString(),
23: Contact = Contact.Text
24: });
25: }
26:
27: private void cancelButton_Click(object sender, RoutedEventArgs e)
28: {
29: if (ProductAddEnded != null)
30: // キャンセルの場合はイベント ハンドラーに null を渡す
31: ProductAddEnded(this, null);
32: }
33:
34: }
その中で、ダイアログに入力されたデータを、新しい商品データのインスタンスとして、このイベントハンドラ―に渡します。データを入力して追加ボタンを押すと、下記の処理が MainPage.xaml.cs の中で実行されます。この時、テーブルの InsertAsync メソッドにこの新しい商品のデータのインスタンスを引数として渡します。
1: async void OnProductAddEnded(AddProductDialog dialog,
2: ProductWithPrice productwithprice)
3: {
4: if (productwithprice != null)
5: {
6: try
7: {
8: // 新しい商品を追加
9: await ProdutwithPriceTable.InsertAsync(productwithprice);
10: // データを取得し直して ItemsSource に設定
11: RefreshProducts();
12: var messageDialog =
13: new Windows.UI.Popups.
14: MessageDialog("商品データを追加しました。", "商品追加完了");
15: messageDialog.ShowAsync();
16: }
17: catch (Exception ex)
18: {
19: var messageDialog =
20: new Windows.UI.Popups.
21: MessageDialog("商品データを入力してください。", "商品入力");
22: messageDialog.ShowAsync();
23: }
24: }
25: // ポップアップを閉じる
26: ((Windows.UI.Xaml.Controls.Primitives.Popup)dialog.Parent).IsOpen = false;
27: }
OK を押すと追加処理がされ、RefreshProducts() が呼ばれて、データを取得し直して ItemsSource に設定されます。実行するとこのようなイメージになります。
対応するサービス側のコントローラーはこちらです。
1: // POST tables/ProductWithPrice/48D68C86-6EA6-4C25-AA33-223FC9A27959
2: public async Task<IHttpActionResult> PostProductWithPrice(ProductWithPrice item)
3:
4: ProductWithPrice current = await InsertAsync(item);
5: return CreatedAtRoute("Tables", new { id = current.Id }, current);
商品更新処理
更新処理については、XAML で、Flyout を作成するか、追加と同じようなダイアログボックスを UserControl として追加してください。Flyout はこんな感じです。
このシナリオでは、 担当者(Contact)の TextBox の LostFocus イベントで、元の値と入力された値との間に変更があったかどうかを判定し、変更があれば、代入して更新するという処理を書きます。こちらも商品データを _targetItem として渡し、テーブルの UpdateAsync メソッドで更新します。
1: public ProductWithPrice _targetItem;
2:
3: public ProductWithPrice TargetItem
4: {
5: get { return _targetItem; }
6: set
7: {
8: if (_targetItem == null)
9: {
10: try
11: {
12: // 新しい商品を追加
13: _targetItem = value;
14: codeTextBox.Text = value.StoreName;
15: nameTextBox.Text = value.ProductName;
16: sellPriceTextBox.Text = value.RetailPrice;
17: priceChangeReason.Text = value.ReasonforChangePrice;
18: priceFixedDate.Text = DateTime.Now.ToString();
19: Contact.Text = value.Contact;
20:
21: }
22: catch (Exception ex)
23: {
24: var messageDialog =
25: new Windows.UI.Popups.
26: MessageDialog("商品データを選択してください。", "商品選択");
27: messageDialog.ShowAsync();
28: }
29: }
30:
31: }
32: }
33:
34: private void cancelButton_Click(object sender, RoutedEventArgs e)
35: {
36:
37: this.Hide();
38:
39: }
40:
41: private async void Contact_LostFocus(object sender, RoutedEventArgs e)
42: {
43: TextBox tb = (TextBox)sender;
44: //担当者の変更があったかどうかを判定
45: if (_targetItem.Contact != tb.Text)
46: {
47: _targetItem.Contact = tb.Text;
48:
49: await ProdutwithPriceTable.UpdateAsync(_targetItem);
50:
51: }
52: }
対応するサービス側のコントローラーはこちらです。
1: // PATCH tables/ProductWithPrice/48D68C86-6EA6-4C25-AA33-223FC9A27959
2: public Task<ProductWithPrice> PatchProductWithPrice(string id, Delta<ProductWithPrice> patch)
3: {
4: return UpdateAsync(id, patch);
5: }
以上です。
いかがでしょう?非常に速く LOB アプリの主要な処理が完成できたことがわかります。これこそ、パワフルなモバイルアプリ開発といえるのではないでしょうか?
この他に、LOB アプリに必要なものとしては、サービス側バリデーションの処理、クライアント側バリデーションの処理、 そして、認証、といったところはないでしょうか?
またオフライン対応も、アプリによっては重要な要素となります。
そこで、続いて、オフラインデータ処理について、サンプルをもとに、ご紹介していきます。こちらは、ソースコード全体を公開します。
鈴木 章太郎