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 のサイトを開き、コピーボタンを使って、こちらからコピーしてください。

mobscvs

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: }

実行結果は、こんな感じになります。

image

商品削除処理

次に、削除の処理を書きます。こちらは、Id を含む商品データ全体を、選択したグリッドのアイテムから取得して、削除する処理にしてあります。

前半部分は、Bottom AppBar に追加された OnRemoveButtonClick のイベントハンドラの処理です。

screenshot_06012014_182939

前半部分は、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:         }

特定のグリッド内のアイテムを選択して実行すると、このようなイメージになります。

screenshot_06012014_183441

後半部分は、そこから呼び出される DeleteProduct (Id) メソッドです。セッションでは、Id で商品データを検索した結果である、resultsLINQ クエリーの箇所にブレイクポイントを設定して、実際にどういうデータが渡されたかを見て戴きました。これにより、当該データがテーブルの 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 用の DialogUserControl)を開きます。

    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: }

screenshot_06012014_192816

    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 に設定されます。実行するとこのようなイメージになります。

screenshot_06012014_192821 

screenshot_06012014_192842

対応するサービス側のコントローラーはこちらです。

    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 はこんな感じです。

image

このシナリオでは、 担当者Contact)の TextBoxLostFocus イベントで、元の値と入力された値との間に変更があったかどうかを判定し、変更があれば、代入して更新するという処理を書きます。こちらも商品データを _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 アプリに必要なものとしては、サービス側バリデーションの処理、クライアント側バリデーションの処理、 そして、認証、といったところはないでしょうか?

またオフライン対応も、アプリによっては重要な要素となります。

そこで、続いて、オフラインデータ処理について、サンプルをもとに、ご紹介していきます。こちらは、ソースコード全体を公開します。

鈴木 章太郎