RIA フレームワーク

ASP.NET MVC と Ext JS を使用してデータ中心の Web アプリケーションを構築する

Juan Carlos Carlos

コード サンプルのダウンロード

リッチ インターネット アプリケーション (RIA) は、デスクトップ アプリケーションの使いやすさと、Web ベースで配置および改訂できる柔軟性を兼ね備えています。RIA の構築方法は主に 2 種類あります。1 つは、Flash、Java、Silverlight など、実行環境をホストするブラウザー プラグインです。もう 1 つは、Dojo、Ext JS、jQuery、MooTools、Prototype、YUI など、JavaScript ベースの拡張ライブラリです。どちらの方法にも、メリットとデメリットがあります。

JavaScript ライブラリは RIA の構築によく使用されます。これは、JavaScript がすべての主要ブラウザーでサポートされ、プラグインやランタイム環境をインストールする必要がないためです。私が研究してきたのは、もう 1 種類のライブラリである Ext JS です。研究の結果、Ext JS では Web アプリケーションの実装について興味深い選択を行うことがわかりました。Ext JS は実装しやすく、ドキュメントが充実していて、テスト用の Selenium と互換性もあります。また、Web アプリケーションの UI 作成が容易になる、定義済みのコントロールも用意されています。

残念ながら、Ext JS の例はほとんどがサーバー側の PHP、Python、および Ruby on Rails で記述されたコードで示されています。しかし、だからと言ってマイクロソフトのテクノロジを使用する開発者が Ext JS を使用できないわけではありません。(ステートフルなコントロール ベースのモデルを提供するために Web の要求/応答ベースの性質をカプセル化する抽象層により) Ext JS と Web フォーム開発を統合するのが難しくても、ASP.NET MVC を使用すれば、Microsoft .NET Framework と Ext JS の両方を同じアプリケーションで利用できます。

この記事では、これまでのドキュメントに不足していたチュートリアルを提供し、ASP.NET MVC と Ext JS を使用してバックエンド データベースの読み取りと書き込みを行う実際の Web ソリューションの開発について説明します。

Ext JS フォームの基礎

Ext JS を使用するには、まず extjs.co.jp からダウンロードする必要があります (ここではバージョン 3.2.1 を使用していますが、最新バージョンを入手してください)。無償のオープン ソース版の Ext JS は、オープン ソース プロジェクト、非営利団体、および教育目的に使用できることに注意してください。他の目的で使用する場合は、ライセンスを購入しなければならないこともあります。詳細については、extjs.co.jp/products/license.php を参照してください。

ダウンロードしたパッケージをファイル システムのディレクトリに展開します。このパッケージには、Ext JS を使用して Web ソリューションを開発するために必要なすべての要素、特にメインの ext-all.js ファイルが含まれています (エラーを見つけやすいようデバッグ バージョンもあります)。依存関係、ドキュメント、およびサンプル コードはすべてパッケージに含まれています。

プロジェクトに必要なフォルダーは、\adapter フォルダーと \resources フォルダーです。adapter フォルダーは、Ext JS と共に他のライブラリを使用できるようにします。resources フォルダーには、CSS や画像などの依存ファイルを格納します。

Ext JS を正しく使用するには、次の 3 つの重要なファイル参照をページに含める必要があります。

ext-3.2.1/adapter/ext/ext-base.js
ext-3.2.1/ext-all.js
ext-3.2.1/resources/css/ext-all.css

ext-base.js ファイルには、Ext JS の中核機能が含まれています。ext-all.js ファイルにはウィジェットの定義が含まれ、ext-all.css ファイルにはウィジェットのスタイルシートが含まれています。

まず Ext JS を静的な HTML ページで使用する、基本的な使い方から説明します。次のコード行をページの head セクションに含め、Ext JS ソリューションを正常に開発するために必要なファイルをリンクします (ここでは、Ext JS のダウンロード パッケージに含まれるサンプル ウィジェットと共に JavaScript モジュールも含めています)。

<link rel="stylesheet" type="text/css" 
  href="ext-3.2.1/resources/css/ext-all.css" />
<script type="text/javascript" language="javascript" 
  src="ext-3.2.1/adapter/ext/ext-base.js"></script>
<script type="text/javascript" language="javascript" 
  src="ext-3.2.1/ext-all.js"></script>
<script type="text/javascript" language="javascript" 
  src="extjs-example.js"></script>

ファイルの本文に、メインの Ext JS フォームをレンダリングする div 要素を挿入します。

<div id="frame">
</div>

extjs-example.js ファイルを調べると、Ext JS アプリケーションの作成方法がわかります。Ext JS アプリケーションのテンプレートでは、必ず次のような Ext.ns ステートメント、Ext.BLANK_IMAGE_URL ステートメント、および Ext.onReady ステートメントを使用します。

Ext.ns('formextjs.tutorial');
Ext.BLANK_IMAGE_URL = 'ext-3.2.1/resources/images/default/s.gif';
formextjs.tutorial.FormTutorial = {
  ...
}
Ext.onReady(formextjs.tutorial.FormTutorial.init, 
  formextjs.tutorial.FormTutorial);

Ext.ns ステートメントでは、コードを論理的に名前空間で整理して、名前の競合やスコープの問題を回避できるようにします。

Ext.BLANK_IMAGE_URL ステートメントは、ウィジェットのレンダリングに重要なステートメントです。これはスペーサー イメージ (透明な 1 x 1 ピクセルの画像) と呼ばれ、主に空白を生成したりアイコンや区切りを配置するために使用します。

Ext.onReady ステートメントは、Ext JS のコードで定義する最初のメソッドです。このメソッドは DOM が完全に読み込まれると呼び出され、参照するすべての HTML 要素をスクリプトの実行時に使用できるようにします。extjs-example.js の場合、スクリプト本体は次のようになります。

formextjs.tutorial.FormTutorial = {
  init: function () {
    this.form = new Ext.FormPanel({
      title: 'Getting started form',
      renderTo: 'frame',
      width: 400,
      url: 'remoteurl',
      defaults: { xtype: 'textfield' },
      bodyStyle: 'padding: 10px',
      html: 'This form is empty!'
    });
  }
}

Ext.FormPanel クラスのインスタンスを、フィールドのコンテナーとして作成します。renderTo プロパティは、フォームをレンダリングする div 要素を指し、defaults プロパティは、フォーム既定のコンポーネントの種類を指定します。url プロパティは、フォーム要求の送信先 URI を指定します。最後に、html プロパティは、既定の出力値としてテキスト (任意の HTML 形式) を指定します。

フィールドを追加するには、次のように html プロパティを items プロパティに置き換える必要があります。

items: [ nameTextField, ageNumberField ]

最初に追加する 2 つの項目は、テキスト フィールドと数値フィールドです。

var nameTextField = new Ext.form.TextField({
  fieldLabel: 'Name',
  emptyText: 'Please, enter a name',
  name: 'name'
});
var ageNumberField = new Ext.form.NumberField({
  fieldLabel: 'Age',
  value: '25',
  name: 'age'
});

必須のプロパティは、フォームのコンポーネントに添える説明メッセージを設定する fieldLabel プロパティと、要求パラメーターの名前を設定する name プロパティです。emptyText プロパティは、フィールドが空の場合に表示する透かしテキストを定義します。value プロパティは、コントロールの既定値です。

次のように、項目の追加時にコントロールを宣言することもできます。

items: [
  { fieldLabel: 'Name', emptyText: 'Please, enter a name', name: 'name' },
  { xtype: 'numberfield', fieldLabel: 'Age', value: '25', name: 'age' }
]

ご覧のように、名前フィールドの場合はフォームの既定のプロパティから型を取得するため、型を指定する必要がありません。

フォームに他の要素を追加して、図 1 のような外観にします。

image: The Completed Form

図 1 完成したフォーム

ここまでは、ユーザーからデータを受け取るフォームを Ext JS を使用して構築しました。ここで、データをサーバーに送信しましょう。送信処理を制御して結果を表示するボタンを追加します (図 2 参照)。

図 2 フォームのボタン

buttons: [{
  text: 'Save', 
  handler: function () {
    form.getForm().submit({
      success: function (form, action) {
        Ext.Msg.alert('Success', 'ok');
      },
      failure: function (form, action) {
        Ext.Msg.alert('Failure', action.result.error);
      }
    });
  }
},
{
  text: 'Reset',
  handler: function () {
    form.getForm().reset();
  }
}]

buttons プロパティは、実行される可能性があるあらゆるアクションをフォームから管理できるようにします。各ボタンには、text プロパティと handler プロパティがあります。handler プロパティは、ボタンで実行するアクションを関連付けるロジックを含みます。この例では、[Save] と [Reset] という 2 つのボタンを配置します。[Save] ボタンのハンドラーは、フォームの送信アクションを実行して、成功または失敗を示すメッセージを表示します。[Reset] ボタンのハンドラーでは、フォーム上のフィールド値をリセットします。

フォーム作成の最後の段階 (ただし重要な段階) は検証です。必須フィールドを指定するために、allowBlank プロパティを false に設定し、blankText プロパティを必須フィールドの検証が失敗したときに表示されるエラー メッセージに設定する必要があります。たとえば、フォームの名前フィールドを次のようにします。

{ fieldLabel: 'Name', emptyText: 'Please, enter a name', name: 'name', allowBlank: false }

アプリケーションを実行し、[Name] フィールドと [Age] フィールドにデータを入力しないまま [Save] をクリックすると、エラー メッセージが表示され、必須フィールドに赤い下線が表示されます。

フィールドのエラー メッセージをカスタマイズするには、Ext.onReady 関数の直後に次のコード行を追加します。

Ext.QuickTips.init();

今度は、ユーザーがマウス ポインターでフィールドをポイントすると、エラーを示すメッセージのバルーンが表示されます。

ここでは、フィールドに複数の検証規則を設定しました。たとえば、許可されている最短と最長の長さを指定し、フォームを送信するまでフィールドの検証を遅延させ、URL や電子メール アドレスなどの種類のデータを対象とする検証関数を作成しました。このような検証の詳細については、コード ダウンロードを参照してください。

Web アプリケーションを構築する

ここからは、Ext JS と ASP.NET MVC を使用して Web ソリューションを開発しましょう。ここでは ASP.NET MVC 2 を使用しましたが、ASP.NET MVC 3 にもこのソリューションを適用できます。この例で対処するシナリオは、人事管理システムへの従業員の追加です。

"従業員の追加" ユース ケースを説明すると、次のとおりです。画面に、新しい従業員に関する有効な情報 (従業員の ID、フル ネーム、住所、年齢、給与、配属部門など) を入力するようメッセージが表示されます。部門フィールドは、選択式の部門の一覧です。

このソリューションの実装の主な方針は、先ほど説明したような Ext JS フォームをクライアント側で作成し、ASP.NET MVC を使用してデータを処理することです。永続化層では、LINQ を使用してビジネス エンティティを表し、データをデータベース システムに保存します。バックエンド データベースは、Microsoft SQL Server 2008 です。

まず、Visual Studio 2010 を開き、ASP.NET MVC 2 Web アプリケーション テンプレートを使って新しいプロジェクトを作成します。

次に、データベース スキーマを作成します。この例では、スキーマには employee と department の 2 つのエンティティが含まれます。図 3 に、Human Resources データベースと、基盤となるテーブルと制約の作成方法を示します。

図 3 Human Resources データベースの作成

create table department(
  deptno varchar(20) primary key,
  deptname varchar(50) not null,
  location varchar(50)
);

create unique index undx_department_deptname on department(deptname);

insert into department
  values('HQ-200','Headquarter-NY','New York');
insert into department
  values('HR-200','Human Resources-NY','New York');
insert into department
  values('OP-200','Operations-NY','New York');
insert into department
  values('SL-200','Sales-NY','New York');
insert into department
  values('HR-300','Human Resources-MD','Maryland');
insert into department
  values('OP-300','Operations-MD','Maryland');
insert into department
  values('SL-300','Sales-MD','Maryland');

create table employee(
  empno varchar(20) primary key,
  fullname varchar(50) not null,
  address varchar(120),
  age int,
  salary numeric(8,2) not null,
  deptno varchar(20) not null,
  constraint fk_employee_department_belong_rltn foreign key(deptno)
    references department(deptno)
);
create unique index undx_employee_fullname on employee(fullname);

では、LINQ to SQL を使用して、エンティティの構造と保存のメカニズムを定義しましょう。まず、EmployeeRepository クラスを作成して、employee テーブルへのデータ アクセス ロジックを管理します。この例で実装する必要があるのは、作成操作だけです。

public class EmployeeRepository {
  private HumanResourcesDataContext _ctxHumanResources = 
    new HumanResourcesDataContext();

  public void Create(employee employee) {
    this._ctxHumanResources.employees.InsertOnSubmit(employee);
    this._ctxHumanResources.SubmitChanges();
  }
}

また、DepartmentRepository クラスも作成して、department テーブルへのデータ アクセス ロジックを管理する必要があります。このテーブルについても、この単純な例で実装する必要があるのは、部門の一覧を取得する読み取り操作だけです。

public class DepartmentRepository {
  private HumanResourcesDataContext _ctxHumanResources = 
    new HumanResourcesDataContext();

  public IQueryable<department> FindAll() {
    return from dept in this._ctxHumanResources.departments
           orderby dept.deptname
           select dept;
  }
}

ここで、アーキテクチャのもう 1 つの重要な要素であるコントローラーを定義しましょう。コントローラーを定義するには、ソリューション エクスプローラーで [Controllers] フォルダーを右クリックし、[追加] をポイントして [コントローラー] をクリックします。ここでは、コントローラーの名前を「HumanResourcesController」としました。

Ext JS フォームのプレゼンテーション層

今度は Ext JS に戻り、このフレームワークを使用してアプリケーションのプレゼンテーション層を構築します。このソリューションの場合、インポートする必要があるのは ext-all.js ファイル、\adapter フォルダー、および \resources フォルダーだけです。

Site.Master ページを開いて、head 要素内に各種 Ext JS ファイルへの参照を追加し、<asp:ContentPlaceHolder> タグ要素を各ページのカスタマイズした JavaScript コードと CSS のコンテナーとして追加します (図 4 参照)。

図 4 Site.Master

<head runat="server">
  <title><asp:ContentPlaceHolder ID="TitleContent" 
    runat="server" /></title>
  <link href="../../Content/Site.css" rel="stylesheet" 
    type="text/css" />

  <!-- Include the Ext JS framework -->
  <link href="<%= Url.Content("~/Scripts/ext-3.2.1/resources/css/ext-all.css") %>" 
    rel="stylesheet" type="text/css" />
  <script type="text/javascript" 
    src="<%= Url.Content("~/Scripts/ext-3.2.1/adapter/ext/ext-base.js") %>">
  </script>
  <script type="text/javascript" 
    src="<%= Url.Content("~/Scripts/ext-3.2.1/ext-all.js") %>">
  </script>    
  <!-- Placeholder for custom JS and CSS and JS files 
    for each page -->
  <asp:ContentPlaceHolder ID="Scripts" runat="server" />
</head>

ここで、アーキテクチャの最後の重要な要素であるビューを追加しましょう。ビューは、1 人の従業員に関するデータを取得するフォームを表します。HumanResourcesController を開き、Index アクション メソッドを右クリックして、[ビューの追加] をクリックします。[ビューの追加] ダイアログ ボックスで、[追加] をクリックします。

この記事の前半で作成した Ext JS フォームを実装するには、Scripts ディレクトリに JavaScript ファイルを追加し、ビューにこの JavaScript ファイルへの参照を追加します。続いて、Index.aspx ビューに employee_form.js ファイルへの参照を含め、div 要素を追加します (図 5 参照)。

図 5 従業員のフォームの追加

<%@ Page Title="" Language="C#" 
  MasterPageFile="~/Views/Shared/Site.Master" 
  Inherits="System.Web.Mvc.ViewPage" %>

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" 
  runat="server">
Index
</asp:Content>

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" 
  runat="server">
  <h2>Add a New Employee</h2>
  <div id="employeeform"></div>
</asp:Content>

<asp:Content ID="Content3" ContentPlaceHolderID="Scripts" 
  runat="server">
  <script type="text/javascript" 
    src="<%= Url.Content("~/Scripts/employee_form.js") %>">
  </script>
</asp:Content>

employee_form.js ファイルを開き、ExtJS フォームとそのフォームが基づいているウィジェットを構成するコードを追加します。まず、Ext.data.JsonStore クラスのインスタンスを定義して、部門の一覧を取得します。

var departmentStore = new Ext.data.JsonStore({
  url: 'humanresources/departments',
  root: 'departments',
  fields: ['deptno', 'deptname']
});

url プロパティは、HumanResourceController コントローラーの departments アクション メソッドを参照します。このメソッドには HTTP POST 動詞を使用してアクセスします。root プロパティは、部門の一覧のルート要素です。fields プロパティは、データ フィールドを指定します。次に、フォームを定義します。各プロパティの機能は、名前のとおりです。

var form = new Ext.FormPanel({
  title: 'Add Employee Form',
  renderTo: 'employeeform',
  width: 400,
  url: 'humanresources/addemployee',
  defaults: { xtype: 'textfield' },
  bodyStyle: 'padding: 10px',

この例では、url プロパティは HumanResourceController コントローラーの AddEmployee アクション メソッドを指します。このメソッドにも、HTTP POST 動詞を使用してアクセスします。

items プロパティは、フォームのフィールドを表すウィジェットの一覧を提供します (図 6 参照)。このフォームの既定のウィジェットは、テキスト フィールドです (defaults プロパティで指定)。最初のフィールドは従業員番号で、必須です (allowBlank プロパティで指定)。次のフィールドはフルネームで、これも必須のテキスト フィールドです。住所フィールドは、省略可能なテキスト フィールドです。年齢フィールドは、省略可能な数値フィールドです。給与フィールドは、必須の数値フィールドです。最後に、部門番号フィールドは ID の文字列で、部門の一覧から選択します。

図 6 フォームのフィールドを表すウィジェット

items: [
  { fieldLabel: 'Employee ID', name: 'empno', allowBlank: false },
  { fieldLabel: 'Fullname', name: 'fullname', allowBlank: false },
  { xtype: 'textarea', fieldLabel: 'Address', name: 'address', 
    multiline: true },
  { xtype: 'numberfield', fieldLabel: 'Age', name: 'age' },
  { xtype: 'numberfield', fieldLabel: 'Salary', name: 'salary', 
    allowBlank: false },
  { xtype: 'combo', fieldLabel: 'Department', name: 'deptno', 
    store: departmentStore, hiddenName: 'deptno', 
    displayField: 'deptname', valueField: 'deptno', typeAhead: true,
    mode: 'remote', forceSelection: true, triggerAction: 'all', 
    emptyText: 'Please, select a department...', editable: false }
],

最後に、buttons プロパティを定義して、フォーム上でのアクションを処理します。このボタンの構成は 図 2 と同じですが、text プロパティの値を "Add" に設定します。

これで employee_form.js ファイルは完成です (ここでは、このファイルのほとんどの要素について説明しています。このファイルの完全なソース コードについては、コード ダウンロードを参照してください)。

今度は、HumanResourceController コントローラーを開いて、対応するアクション メソッドを実装します (図 7 参照)。

図 7 HumanResourceController

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using HumanResources_ExtJS_ASPNETMVC.Models;

namespace HumanResources_ExtJSASPNETMVC.Models.BusinessObjects {
  public class HumanResourcesController : Controller {
    DepartmentRepository _repoDepartment = new DepartmentRepository();
    EmployeeRepository _repoEmployee = new EmployeeRepository();

    // GET: /HumanResources/
    public ActionResult Index() {
      return View();
    }

    // POST: /HumanResource/Departments
    [HttpPost]
    public ActionResult Departments() {
      var arrDepartment = this._repoDepartment.FindAll();
      var results = (new {
        departments = arrDepartment
      });
      return Json(results);
    }

    // POST: /HumanResource/AddEmployee
    [HttpPost]
    public ActionResult AddEmployee(employee employee) {
      string strResponse = String.Empty;
      try {
        this._repoEmployee.Create(employee);
        strResponse = "{success: true}";
      }
      catch {
        strResponse = "{success: false, error: \"An error occurred\"}";
      }
      return Content(strResponse);
    }
  }
}

作業の完了

ソリューションを実行すると、図 8 のような Web ページが表示されます。フォームにデータを入力して [Add] をクリックすると、確認のメッセージ ボックスが表示されます。また、データベースの dbo.employee テーブルに挿入された行も表示されます。

image: Running the Application

図 8 アプリケーションの実行

単純な RIA の構築に必要な作業は、これで全部です。使用する機能によっては、ASP.NET MVC を使用しながら他の一般的な JavaScript フレームワークを使用して、単純なアプリケーションを作成することもできます。データ層の代わりに Entity Framework を使用したり、バックエンド データ ストアとして Windows Azure ストレージや SQL Azure を使用したりするのも簡単です。このようにシンプルなビルディング ブロックから構成されているため、基本的なデータ中心の RIA をすばやく簡単に構築できます。

Juan Carlos Olamendy は、シニア アーキテクト、開発者、およびコンサルタントです。彼はこれまでに何度も Microsoft Most Valuable Professional (MVP) と Oracle ACE に認定されています。また、Windows Communication Foundation に関するマイクロソフト認定テクノロジー スペシャリストにも認定されています。Olamendy の連絡先は johnx_olam@fastmail (英語のみ) です。

この記事のレビューに協力してくれた技術スタッフの Scott HanselmanEilon Lipton に心より感謝いたします。