年 9 月 2015

ボリューム 30 番号 9

データ ポイント - JavaScript データ バンド再考 (Aurelia 編)

Julie Lerman

Julie Lerman私は経験豊富なフロント エンド開発者ではありませんが、理由があれば UI を扱うこともあります。2012 年 6 月には、Knockout.js に関するユーザー グループのプレゼンテーションに参加後、、Knockout を使用して Web サイトで OData をデータ バインドすることをコラムに書きました (msdn.microsoft.com/magazine/jj133816)。さらに数か月後、Breeze.js を追加して Knockout.js でのデータ バインドを簡単にすることを取り上げました (msdn.microsoft.com/magazine/jj863129、英語)。2014 年には、ASP.NET 2.0 の Web フォーム アプリケーションに新しい命を吹き込むことについてのコラムを書き、そのコラムで再度 Knockout を使用したところ、一部の友人から Knockout は「2012 年に流行った技術だよ」とからかわれたこともありました。確かに、Angular などの新しいフレームワークを使用すれば、データ バインドが可能になるだけでなく、もっと多くの処理も可能になります。でも、私は「もっと多くの処理」にはあまり興味がなく、Knockout で十分と考えていました。

2015 年になっても、Knockout は依然として JavaScript のデータ バインドに利用でき、関係も深く、輝きを失っていません。ですが、今回は新しいフレームワークに挑戦してみようと考え、多くの Web 開発者が注目する新しいフレームワークの 1 つ、Aurelia (Aurelia.io、英語) を取り上げることにしました。Aurelia の開発を始めたのは Rob Eisenberg です。彼はそれまで Durandal という別の JavaScript クライアント フレームワークに取り組んでいましたが、その製作を中止して、Google の Angular チームに加わりました。しかし、最終的には Angular チームを去ることになり、Durandal の開発には戻らず、土台から Aurelia を作成しました。Aurelia には興味深い点がたくさんあります。多くの点を学習すべきであることは自覚していますが、今回は、データ バインドのテクニックと、(2015 年に標準となった) JavaScript 最新バージョンの EcmaScript 6 (ES6) を使用するちょっと巧妙な処理を取り上げます。

Web サイトにデータを提供する ASP.NET Web API

今回は、Entity Framework 6 を使って保持しているデータを公開するために、自身でビルドした ASP.NET Web API を使用しています。この Web API には、HTTP 経由で呼び出されるシンプルなメソッドをいくつか用意しています。

Get メソッド (図 1 参照) は、クエリとページ切り替えに関するパラメーターを受け取り、このパラメーターをリポジトリ メソッドに渡します。リポジトリ メソッドは、Entity Framework の DbContext を使用して、Ninja オブジェクトとその関連 Clan オブジェクトのリストを取得します。Get メソッドは、その後、リポジトリ メソッドから得られた結果を、別の場所で定義した一連の ViewListNinja データ転送オブジェクト (DTO) に変換します。JSON をシリアル化する方法が原因で、この変換が重要になります。JSON をシリアル化する場合、Clan から他の Ninja に戻る循環参照を厳密に処理します。DTO に変換しておけば、循環参照を処理する無駄なやり取りを回避でき、クライアント側の形式にぴったりと合うように結果の形式を整えることができます。

図 1 Web API の Get メソッド

public IEnumerable<ViewListNinja> Get(string query = "",
  int page = 0, int pageSize = 20)
  {
    var ninjas = _repo.GetQueryableNinjasWithClan(query, page, pageSize);
    return ninjas.Select(n => new ViewListNinja
                              {
                                ClanName = n.Clan.ClanName,
                                DateOfBirth = n.DateOfBirth,
                                Id = n.Id,
                                Name = n.Name,
                                ServedInOniwaban = n.ServedInOniwaban
                              });
    }

図 2 に、クエリに基づいて Get メソッドが取得した 2 つの Ninja オブジェクトのビューを示します。

Ninja オブジェクトのリストに対する Web API 要求の JSON 結果
図 2 Ninja オブジェクトのリストに対する Web API 要求の JSON 結果

Aurelia フレームワークを使用した Web API のクエリ

Aurelia のパラダイムでは、1 つのビュー モデル (JavaScript クラス) と 1 つのビュー (HTML ファイル) をペアにして、この 2 つの間でデータ バインドを実行します。そのため、今回は ninjas.js ファイルと ninjas.html ファイルを用意します。Ninjas ビュー モデルは、Ninjas の配列と Ninja オブジェクトを含むように定義します。

export class Ninja {
  searchEntry = '';
  ninjas = [];
  ninjaId = '';
  ninja = '';
  currentPage = 1;
  textShowAll = 'Show All';
  constructor(http) {
    this.http = http;
  }

ninjas.js で最も重要なメソッドは、Web API を呼び出す retrieveNinjas です。

retrieveNinjas() {
  return this.http.createRequest(
    "/ninjas/?page=" + this.currentPage +
    "&pageSize=100&query=" + this.searchEntry)
    .asGet().send().then(response => {
      this.ninjas = response.content;
    });
  }

Aurelia が検索して、要求の URL に組み込むベース URL は、Web アプリの別の場所でセットアップします。

x.withBaseUrl('http://localhost:46534/api');

ninjas.js ファイルは単なる JavaScript にすぎません。Knockout を使用したことがあれば、オブジェクトをマークアップにバインドする際に Knockout が操作内容を認識できるように、Knockout 表記を使用してビュー モデルをセットアップする必要があったことを思い出すかもしれません。Aurelia の場合は異なります。

応答には Ninja オブジェクトのリストが含まれるようになるため、要求をトリガーした ninjas.html ページに返されるビュー モデルの ninjas 配列に、これらの Ninja オブジェクトを設定します。マークアップにはモデルを識別するものがありません。この識別は、モデルを HTML とペアにすることで行います。実際、ほとんどのページには、標準の HTML となんらかの JavaScript、および Aurelia が検索して処理する特別なコマンドが少しあるだけです。

データ バインド、文字列の補間と書式設定

ninjas.html で最も興味深い部分は、ninja オブジェクト リストの表示に使用する div 要素です。

<div class="row">
  <div  repeat.for="ninja of ninjas">
    <a href="#/ninjas/${ninja.Id}" class="btn btn-default btn-sm" >
      <span class="glyphicon glyphicon-pencil" />  </a>
    <a click.delegate="$parent.deleteView(ninja)" class="btn btn-default btn-sm">
      <span class="glyphicon glyphicon-trash" />  </a>
    ${ninja.Name}  ${ninja.ServedInOniwaban ? '[Oniwaban]':''}
    Birthdate:${ninja.DateOfBirth | dateFormat}
  </div>
</div>

このコードで最初の Aurelia 固有のマークアップは、「repeat.for="ninja of ninjas"」です。これは、ループの ES6 パラダイムに従います。Aurelia はビュー モデルを理解するため、"ninjas" が配列として定義されたプロパティであることがわかります。変数 "ninja" には、"foo" など任意の名前を付けることができます。名前は単純に、ninjas 配列の各項目を表現するだけです。ここでは、ninjas 配列の全項目を反復処理することにすぎません。次に、プロパティが表示されているマークアップ ("${ninja.Name}" など) に注目します。これは、文字列の補間と呼ばれる Aurelia で利用可能な ES6 の機能です。文字列の補間を使用すると、変数を埋め込んだ文字列を、連結などよりも簡単に構成できます。したがって、変数として名前 "Julie" を使用する場合は、JavaScript で以下のように記述します。

`Hi, ${name}!`

すると、「Hi, Julie!」と処理されます。Aurelia では、このような構文に直面した場合、ES6 の文字列補間機能を利用して、一方向のデータ バインドを推測します。したがって、${ninja.Name} で始まる最後のコード行は、残りの HTML テキストと一緒に ninja のプロパティを出力します。C# や Visual Basic でコーディングしている開発者にとっては、文字列補間は、C# 6.0 でも Visual Basic 14 でも新しい機能になります。

この方法で進めていくには、ServedInOniwaban ブール式の条件評価など、JavaScript の構文についてもう少し知識が必要だと感じ、少し調べてみたところ、この条件評価は、C# の「(条件式) ? (true の処理: false の処理)」と同じ構文であることがわかりました。

DateOfBirth プロパティに適用した日付の書式も Aurelia の機能ですが、XAML を使用したことがある開発者にはなじみのあるものです。Aurelia は値コンバーターを使用します。ここでは、日付と時刻の書式設定に役立つ moment という JavaScript ライブラリを使用する以下の date-format.js クラスを用意しました。

import moment from 'moment';
export class dateFormatValueConverter {
  toView(value) {
  return moment(value).format('M/D/YYYY');
  }
}

クラス名に "ValueConverter" という文字列を含める必要があります。

HTML ページの先頭にある最初の <template> 要素のすぐ下に、このファイルへの参照を設定します。

<template>
  <require from="./date-format"></require>

これで、文字列補間機能では、マークアップ内で dateFormat­[ValueConverter] を検索して、日付の書式を出力に適用できるようになります (図 3 参照)。

Aurelia の文字列補間を利用して一方向にバインドしてすべての Ninja をプロパティと共に表示
図 3 Aurelia の文字列補間を利用して一方向にバインドしてすべての Ninja をプロパティと共に表示

div 要素のもう 1 つのバインドの例は、データ バインドではなく、イベント バインドです。最初のハイパーリンク タグは一般的な構文を使用して、href 属性に URL を埋め込んでいます。しかし、2 つ目のタグは href を使用していません。代わりに、click.delegate を使用しています。delegate は新しいコマンドではありませんが、Aurelia では、標準の onclick イベント ハンドラーよりも強力な特別な方法で delegate コマンドを処理します。詳細については、bit.ly/1Jvj38Z (英語) を参照してください。ここからは引き続き、データ関連のバインドに注目します。

編集用のアイコンをクリックすると、ninja の ID を含む URL に移動します。Edit.html というページにルーティングされるように、Aurelia のルーティング メカニズムに指示します。Edit.html は、Edit.js という名前のクラスのビュー モデルに結び付けられています。

Edit.js で最も重要なメソッドは、選択した ninja オブジェクトを取得および保存するメソッドです。まず、retrieveNinja について見ていきます。

retrieveNinja(id) {
  return this.http.createRequest("/ninjas/" + id)
    .asGet().send().then(response => {
      this.ninja = response.content;
    });
  }

これは、先ほどと同様の Web API に対する要求をビルドしますが、今回は要求に id を付加します。

ninjas.js クラスでは、ビュー モデルの ninjas 配列プロパティに結果をバインドします。ここでは、現在のビュー モデルの ninja プロパティに、結果を単一のオブジェクトとして設定します。

URI に付加した ID により、以下の Web API メソッドが呼び出されます。

public Ninja Get(int id)
  {
    return _repo.GetNinjaWithEquipmentAndClan(id);
  }

このメソッドの結果は、ninja リストにに返される結果よりも多くの内容を備えています。図 4 に、要求の 1 つから返される JSON を示します。

1 つの Ninja に対する WebAPI 要求の JSON 結果
図 4. 1 つの Ninja に対する WebAPI 要求の JSON 結果

結果をビュー モデルにプッシュすると、HTML ページの要素に ninja のプロパティをバインドできます。今回は .bind コマンドを使用します。Aurelia は、バインドの方法を一方向にするか、双方向にするか、または他の方法で行うかを推測します。実際、ninjas.html でわかるように、文字列補間を使用して表示されるときに、基盤のバインド ワークフローを使用します。そこで、一回限りの一方向バインドを使用します。今回は、.bind コマンドを使用して入力要素にバインドするため、Aurelia は、開発者が双方向バインドを望んでいると推測します。.bind コマンドの既定の選択肢は双方向バインドですが、.bind の代わりに、.one-way または別のコマンドを使用してオーバーライドすることもできます。

説明を簡単にするために、周囲の要素を抽出するのではなく、関連マークアップのみを抽出します。

modelview クラスから返すモデルの ninja プロパティの Name プロパティにバインドされる input 要素を以下に示します。

<input value.bind="ninja.Name" />

また、DateOfBirth フィールドにバインドされる別の input 要素を以下に示します。既に学習した構文を使用して、このコンテキストでも date-format 値コンバーターを簡単に再利用できます。

<input  value.bind="ninja.DateOfBirth | dateFormat"  />

ninjas をリストしたのと同じ方法で編集と削除を行えるように、同じページに装備 (Equpment) のリストも表示します。デモが目的なので、文字列としてリストするところまで説明しましたが、編集機能と削除機能、および装備を追加する方法については実装してみてください。

<div repeat.for="equip of ninja.EquipmentOwned">
  ${equip.Name} ${equip.Type}
</div>

図 5 に、データ バインドを備えたフォームを示します。

装備のリストを表示する編集ページ
装備のリストを表示する編集ページ

Aurelia にはアダプティブ バインドという機能もあります。この機能により、ブラウザーの利用可能な機能や渡されたオブジェクトに応じて適合するバインド機能が選択されます。これはかなり優れた機能で、ブラウザーやライブラリの進化に合わせて進化できるように設計されています。アダプティブ バインドの詳細については、bit.ly/1GhDCDB (英語) を参照してください。

現時点では、忍者の名前、誕生日、および御庭番インジケーターしか編集できません。ユーザーが [Served in Oniwaban] (御庭番としての任務) チェック ボックスをオフにして [Save] (保存) をクリックすると、Web API にデータが返される前に興味深いなんらかの処理を行うビュー モデルの save メソッドが呼び出されます。現時点では、ninja オブジェクトは深さのあるグラフです (図 4 参照)。保存のためにすべての情報を送信する必要はなく、関連プロパティだけを送信します。受信側では EF を使用しているため、編集しなかったプロパティも返すようにして、データベースの内容が null 値に置き換えられないようにします。そこで、ninjaRoot という 実行時 DTO を作成します。既に、ビュー モデルのプロパティとして ninjaRoot を宣言しています。ただし、ninjaRoot の定義は、Save メソッドでのビルド方法によって示されます (図 6 参照)。WebAPI が想定するのと同じプロパティ名 (大文字と小文字を区別) を使用するように注意を払ったため、ninjaRoot では、これを API の既知の Ninja 型へとシリアル化解除できます。

図 6 編集モデル ビューの Save メソッド

save() {
        this.ninjaRoot = {
          Id: this.ninja.Id,
          ServedInOniwaban: this.ninja.ServedInOniwaban,
          ClanId: this.ninja.ClanId,
          Name: this.ninja.Name,
          DateOfBirth: this.ninja.DateOfBirth,
          DateCreated: this.ninja.DateCreated,
          DateModified: this.ninja.DateModified
        };
        this.http.createRequest("/ninjas/")
          .asPost()
          .withHeader('Content-Type', 'application/json; charset=utf-8')
          .withContent(this.ninjaRoot).send()
          .then(response => {
            this.myRouter.navigate('ninjas');
          }).catch(err => {
            console.log(err);
          });
    }

このクラスでは "asPost" メソッドを使っています。これは、要求が Web API の Post メソッドに確実に渡されるようにします。

public void Post([FromBody] object ninja)
{
  var asNinja =
    JsonConvert.DeserializeObject<Ninja>
    (ninja.ToString());
  _repo.SaveUpdatedNinja(asNinja);
}

JSON オブジェクトはローカルの Ninja オブジェクトへとシリアル化解除された後、データベース内のオブジェクトを更新する方法を把握しているリポジトリのメソッドに渡されます。

Web サイトの Ninjaリストに戻ると、変更が出力に反映されているのがわかります。

単なるデータ バインドではない

Aurelia はデータ バインドを行えるだけでなく、豊富な機能を備えたなフレームワークです。今回は、Aurelia というツールについて説明する第一歩として、個人的な好みから、データに注目しました。詳細については、Aurelia の Web サイトや、人気のコミュニティ (gitter.im/Aurelia/Discuss、英語) を参照してください。

今回は、ASP.NET Web API と Aurelia を組み合わせることに関するブログ連載記事を掲載する、tutaurelia.net の Web サイトを参考にしました。ninja オブジェクトのリストを初めて表示する際に、Bart Van Hoey が執筆した第 6 部の記事を参照しました。このコラムが公開される頃には、ブログ連載記事に新たな記事が追加されていることでしょう。

本稿付属のサンプル コードのダウンロードには、Web API ソリューションと Aurelia Web サイトの両方を含めました。Web API ソリューションは Visual Studio で使用できます。今回は、Entity Framework 6 と Microsoft .NET Framework 4.6 を使用して、Visual Studio 2015 で作成しました。Web サイトを実行する場合は、Aurelia.io (英語) サイトに移動して、Aurelia のインストール方法と Web サイトの実行方法を調べてください。Pluralsight のコース「Getting Started with Entity Framework 6」(Entity Framework 6 の概要 bit.ly/PS-EF6Start、英語) では、このアプリケーションのデモを視聴できます。


Julie Lermanは、バーモント ヒルズ在住の Microsoft MVP、.NET の指導者、およびコンサルタントです。世界中のユーザー グループやカンファレンスで、データ アクセスなどの .NET トピックについてプレゼンテーションを行っています。彼女のブログは thedatafarm.com/blog (英語) で、彼女は O'Reilly Media から出版されている『Programming Entity Framework』(2010 年) および『Code First』版 (2011 年)、『DbContext』版 (2012 年) を執筆しています。彼女の Twitter (twitter.com/julielerman、英語) をフォローして、juliel.me/PS-Videos (英語) で彼女の Pluralsight コースをご覧ください。


この記事のレビューに協力してくれた技術スタッフの Rob Eisenberg (Durandal Inc.) に心より感謝いたします。
Rob Eisenberg は、ユーザー インターフェイスのアーキテクチャとエンジニアリングを中心に 10 年の経験があります。彼は、世界中の多数の開発者によって使用されている、Caliburn.Micro や Durandal などの複数の UI フレームワークの作成者です。以前は Angular 2.0 チームの一員でした。現在は、モバイル、デスクトップ、および Web 用の次世代の JavaScript クライアント フレームワークであり、シンプルな規約を利用して創作意欲を向上させる Aurelia プロジェクトを率いています。詳細については、http://aurelia.io/ (英語) をご覧ください。