背景
本稿では、Microsoft .NETプラットフォームの持つ2つの強力な要素、「カスタムサーバーコントロール」と「継承」を組み合わせる方法を説明します。ここではASP.NETで提供されている標準的なドロップダウンリストのサーバーコントロールを継承した、カスタムWebサーバーコントロールを開発します。今回作成するカスタムサーバーコントロールは、指定のパラメータに基づいてデータをフェッチし、自分自身に割り当てます。このコントロールを利用すれば、もう実際のデータアクセスコードを書く必要はなくなります。
.NETのドロップダウンリストコントロールの威力は、DataSets
やDataReaders
などのADO.NETオブジェクトにバインドできる点にあります。一度バインドしてしまえば、DataSet
やDataReader
オブジェクトのデータ要素を物理的にコントロールに配置するコードを書く必要はなくなり、コントロールそのものがHTMLのselect
要素となります)。開発者にとって必要なのは、ただデータソースと、データソースの中の使用する列を値として指定し、select
要素の属性を表示することだけです。コントロール自体は、ブラウザに送信する上で必要となる実際のHTMLの材料や表現を実装しています。しかし、DataSet
やDataReader
をデータベースからフェッチする「グルーコード(glue code)」だけは書く必要があります。これはVisual Studio .NET IDEのウィザードでも実行できますが、それでも完了するには時間のかかる作業です。本稿では、リストコントロールにデータを割り当てる際のフェッチコードを書く手間を省くために、既定のドロップダウンリストを利用して、自身のプロパティの値に基づいてDataReader
をフェッチするカスタムサーバーコントロールを作成します。
継承
「フェッチ機能付きドロップダウンリストコントロールを開発する」という目標を達成するためには、継承を利用します。「継承」とは、「IS-Aリレーションシップ」を表す、オブジェクト指向では一般的な概念です。オブジェクト指向の文献でしばしば使われる一般的な例では、「人」と「従業員」が引き合いに出されます。「"従業員(Employee
)"は"人(Person
)"の一種である」というリレーションシップは、IS-Aリレーションシップです。ここで、C#のPerson
というクラスがあると仮定しましょう。このクラスは、「Name
(名前)」および「SSN
(Social Security Number:社会保障番号)」という2つの属性を公開しています。さらに、C#のEmployee
というクラスも存在し、「Manager
(マネージャ)」と「Salary
(給与)」という2つのパブリック属性があるとしましょう。Person
クラスとEmployee
クラスの間に継承を設定(つまり、Employee
クラスがPerson
クラスを継承するよう設定)すると、Employee
クラスはPerson
クラスが持つパブリック属性とパブリックメソッドもすべて持つことになります。この例では、Employee
というクラスはPerson
というクラスから「派生した」とも言うことができ、Employee
クラスにはName
、SSN
、Manager
、Salary
という4つのプロパティを持つことになります。また、この例では、Person
クラスは「ベースクラス」と呼ばれ、Employee
クラスは「派生クラス」と呼ばれます。さらに別の言い方をすると、Employee
は「サブクラス(下位クラス)」、Person
は「スーパークラス(上位クラス)」です。
派生クラスに新しいプロパティとメソッドを追加して、ベースクラスの機能を拡張することができます。C#のbase
というキーワードを使えば、ベースクラスの要素に派生クラスからアクセスすることもできます。継承は.NETの非常に強力な機能で、その活用方法は無数にあります。本稿では、継承を使って開発の労力を軽減する方法を1つだけ示します。継承にまつわるオブジェクト指向の概念すべてをここで説明することはできませんが、本稿で紹介するサンプルの実装方法を理解し、学習するには十分なはずです。
データアクセスの基盤を作成する
フェッチ機能付きドロップダウンリストコントロールの開発に取りかかる前に、グルーコードがデータを取得してコントロールに割り当てるしくみを具体的に見ておきましょう。後で、このコードを使って、本稿の派生コントロールに挿入するテンプレートを作成します。まずは、Webフォーム上の単純なドロップダウンリストコントロールにデータを割り当ててみましょう。
Visual C#のプロジェクトから、新しいASP.NET Webアプリケーションプロジェクトを作成します(図1)。
次に、ツールボックスからドロップダウンリストコントロールをドラッグし、プロジェクトウィザードで作成された既定のWebフォーム「WebForm1.aspx」にドロップします。このコントロールの名前をlstDemoに変更しましょう(図2)。
ドロップダウンリストコントロールをページに追加したら、次はこのドロップダウンリストにデータを割り当てるためのコードを追加します(図3)。このドロップダウンリストには、ページが自分自身にポストバックされたときではなく、ページが最初に読み込まれたときにデータが割り当てられるようにします。このためには、IsPostBack
の値をチェックし、IsPostBack
の値がfalse
の場合にのみドロップダウンリストを割り当てます。
ドロップダウンリストは、DataSet
とDataReader
のどちらにもバインドできます。本稿のシナリオでは、パフォーマンスを上げるために後者を選びました。DataSet
には豊富な機能が用意されていますが、ここではドロップダウンリストへのデータ割り当てを単純化したかったのと、パフォーマンス的な理由から、サイズが小さくて単純なDataReader
を使用することにしました。また、この例ではOLEDBDataReader
クラスではなく、SqlDataReader
クラスを使っている点にも注意してください。この選択により、SQLデータソースにしかアクセスできなくなりますが、パフォーマンスは最適化されます。Microsoft SQL以外のデータベースに接続する必要がある場合は、OLEDBDataReader
クラスを使っても構いません。ただし、既定のASPXページにはSystem.Data
名前空間しか含まれていないため、適切なデータクライアントの名前空間を使用するために、必要なusing
ディレクティブを必ず追加してください。
コードの残り部分は単純明快です。この例ではusing
ステートメントを使ってコネクションとコマンドを作成しています。using
ステートメントを使用すると、それで囲んだコードブロックの実行後に、using
ステートメントで宣言したオブジェクトのDispose
メソッドが自動的に呼び出されます。コマンドとコネクションを作成して定義した後は、コマンドのExecuteReader
メソッドを使用して、「pubs」データベースの「authors」テーブルから著者ID(au_id
)と著者の姓(au_lname
)を取得し、DataReader
オブジェクトに割り当てます。データを取得した後は、そのDataReader
オブジェクトをドロップダウンリストのデータソースとして設定し、ドロップダウンリストの表示テキストとオプション値の取得元となる列名を設定して、最後にデータソースをドロップダウンリストにバインドします。図3のコードを入力したら、プロジェクトをコンパイルしてください。コードを実行するとHTMLのselect
要素が生成され、au_id
列はドロップダウンリストのオプション値、au_lname
列はドロップダウンリスト表示テキストとなります。
必要なプロパティの定義
テンプレートに使える実用的な例が作成できました。これで、本稿の派生コントロールの開発をどう進めるかについて計画を立てることができます。最初のステップは、何を一般化し、パラメータとして収集するか、そして何をコントロールに埋め込むかを決定することです。一見すると、次の項目がパラメータとして渡す候補になりそうです。
- コネクション文字列
- データベーステーブル名
- HTMLの
select
要素の個々のオプションサブ要素の「オプション値」になる列と「表示テキスト」になる列
コネクション文字列は、どのデータベースに接続すべきかを知るために必要です。たいていの場合、これは「Web.Config」ファイルに格納され、おそらくはApplicationレベルの変数のどこかにキャッシュされるため、この値は簡単にキャプチャできます。
データベーステーブル名は、SELECT
ステートメントで使います。今回使用するSELECT
ステートメントは単純なので、データベーステーブル名と、ドロップダウンリストのオプション値および表示テキストとして使用する2つの列の名前だけで、SELECT
ステートメントを動的に作成することができます。
ちょうどよいことに、オプション値と表示テキストの列を指定するための機能は継承元のコントロールにプロパティとして用意されているので、それらを親コントロールから「拝借」することができます。
これで、まだ考慮されていない部分はエラー処理だけになりました。コネクション名やテーブル列名のようなユーザー入力の受け付けを開始するときは、想定されるすべてのエラー、たとえば不正なコネクション文字列やパラメータの不足などをきちんと処理しなければなりません。既定のドロップダウンリストと同じように、エラーを処理せず、呼び出し元にエラーを送り返すだけにしておくこともできますが、この場合、エラーを効果的に処理するには、エンドユーザーがコントロールをtry-catch
ブロックにラップする必要があります。一方、新しいコントロールを派生させるメリットの1つは、自己完結型のエラー処理といった高度な機能を追加できる点にあります。もっとも、これを実装するかどうかは開発者の判断しだいなので、何も手をつけたくない場合はそのままでも構いません。代わりに.NETに組み込まれた例外フレームワークが処理を引き受けてくれます。
さて、そろそろ派生コントロールの作成に話を戻しましょう。最初のステップは、C#で新しいWebコントロールライブラリプロジェクトを作成することです。プロジェクトには、「mycoServerControls」と名前を付けてください。工程管理のために、コントロールの名前に自分の会社名をプレフィックスとして付けておくと好都合な場合もしばしばあります。この例では、会社名を表すプレフィックスとして「myco」を使います。
グルーコードを追加してデータをフェッチし、コントロールに割り当てるには、前もって処理しておくべき細かな事柄がいくつかあります。
- コントロールの名前空間を、
mycoServerControls
からmycoServerControls.DropDownList
に変更します(図4)。 - csファイルの名前を、「WebCustomControl1」から「mycoServerControls.DropDownList」に変更します(図5)。
- クラスの名前を
WebCustomControl1
からmycoDropDownList
に変更し、コントロールの継承をSystem.Web.UI.WebControls.WebControl
からSystem.Web.UI.WebControls.DropDownList
に変更します(図6)。 - SQLクライアントのクラスと名前空間に、必要な参照を追加します(図7)。
名前空間の内容
まずは既定のWebコントロールライブラリプロジェクトに含まれているコードを見ていきますが、その前に、私がなぜファイル名と名前空間を変更するよう勧めたのかを理解していただくために、名前空間についていくつか説明しておきます。これらの変更はあくまでも私の推奨にすぎず、既定の名前空間とファイル名のまま作業を進めても、特に何の影響もなく、完全に機能するサーバーコントロールを開発できます。しかし、変更を加えておけば、いつか企業環境で実装やメンテナンスに携わるような場合にも、作業が簡単になります。
私はいつも、自作のC#ソースファイルの名前を、そのソースファイルに含まれている名前空間へと変更します。確かに、こうするとファイルには名前空間が1つしか含まれなくなりますが、これは、プロジェクト内の作業を論理的に分割するのには好都合です。1つのプロジェクトに複数の名前空間が含まれている場合、プロジェクトに他のソースファイルを追加するのが非常にたやすくなり、なおかつ、プロジェクト内のソースコードをクリーンかつわかりやすい形に分割することができます。また、多くの場合、短めのソースファイルの扱いが簡単になります。さらに、特定の名前空間があるソースコードの場所を識別するのにも役立ちます。ファイルを1つずつ開いて、その中に含まれている名前空間をいちいち調べる必要がなくなるためです。
先ほど、コントロールの名前空間をmycoServerControls
からmycoServerControls.DropDownList
に変更することも勧めました。これは、名前空間に含まれるソースのスコープを狭くすることだけを目的にしています。いずれ、読者の皆さんが本稿の手法を利用して、自分でテキストボックスコントロール用の派生コントロールを開発することがあるかもしれません(たとえば、数値データ入力を評価するコントロールや、文字列入力を評価するコントロールなど)。また、動的SQLではなくストアドプロシージャを使った、別のドロップダウンリスト派生コントロールを開発するケースも考えられます。その場合、ドロップダウンリスト(DropDownList)コントロールの名前空間を既定のまま変更しなければ、4つのコントロールすべてがmycoServerControls
という1つの名前空間に含まれることになり、前の段落で説明したファイルの命名規則を守れば、4つのコントロールすべてが1つのファイルに含まれることになります。お察しのとおり、この調子で新しいコントロール型を次々に派生させていくと、1つのソースコードファイルを使用する手法では、管理が楽になるどころか、手に負えなくなります。
しかし、名前空間をより説明的にしておく(つまり、mycoServerControls.DropDownList
やmycoServerControls.TextBox
という名前空間を作成する)と、コントロール型を1つしか含まないソースコードファイルを個別に作成できるようになります。これにより、.NETでクラスと機能をグループ化するための、はるかにすっきりした論理的なアプローチが生まれます。.NET Frameworkは、必要に応じて開発者の手で拡張できるように設計されています。.NET Frameworkの基本的な名前空間がどうセットアップされているかを調べ、その構造を自作のカスタム名前空間によってモデル化するよう努めていけば、企業全体で利用しても名前空間が衝突することのない、プロフェッショナルなツールキットが出来上がっていきます。重要なのは、名前空間のストラテジを前もって定めておき、断固としてそのストラテジを進めていくことです。.NETを採用する初期段階でこのステップが完了していないと、事態が複雑になり、やがては名前空間を再編成する一大プロジェクトを立ち上げざるを得なくなります。
DefaultPropertyとToolboxDataの情報の設定
Webコントロールライブラリプロジェクトを選択すると、Visual Studio .NET IDEは自動的に既定のコードをプロジェクトに追加します。まず見てほしいのは、図8のコードブロックです。
このコード行では、「コントロールの既定のプロパティ」と、「ツールボックス/タグ情報」の2つを設定しています。既定のプロパティは、コントロールのプロパティと一致している必要があります。ToolboxData
では、このコントロールがツールボックスに表示されるときの名前と、このコントロールがWebフォームに追加された際にASPXページに挿入されるタグの既定のクラス名(この例ではWebCustomControl1
)を指定します。フォーマット文字列内の{0}
は、タグプレフィックスによって置き換えられます。WebCustomControl1
のインスタンスは、新しいクラス名であるmycoDropDownList
によって両方とも置き換えられます。さらに、このコントロールでは「Text
」という名前のプロパティは公開しないため、既定のプロパティを「ConnectionString
」に変更する必要があります。それぞれの変更については、図9を参照してください。
コントロールのプロパティ
次は、図10のコードを見てください。この部分は、コントロールのパブリックプロパティ宣言です。ここには、目を通すべき属性がいくつかあります。1つ目はBindable(true)
です。この属性は設計時に、「このプロパティは有効なソースにデータバインドできるかどうか」をIDEに示すために使われています。次はCategory("Appearance")
です。この属性は、このプロパティをプロパティデザイナ内のどのカテゴリに配置するかを制御します。ここでは「Appearance」や「Data」などの既定のカテゴリの1つを使うか、自作のカスタムカテゴリを入力することができます。最後の属性であるDefaultValue
には、そのプロパティの既定値を指定します。Text
プロパティを削除するか、名称を変更して、次のプロパティをコントロールに追加する必要があります。
ConnectionString
SourceTable
ErrorMsg
これら3つのプロパティ用のコードを、図11に示します。ErrorMsg
プロパティが読み取り専用として実装されている点に注目してください。
コントロールのメソッド
IDEによって追加されたコードの最後の部分は、Render
メソッドの実装サンプルです。メソッドの宣言に、override
キーワードがあることに注目してください。本稿の親クラスにも、Render
という名前のメソッドがあります。このメソッドには、スーパークラスの中でoverridable
(オーバーライド可能)というマークが付けられており、これは「派生クラスは同じ名前でメソッドを公開してもよい」ということを意味しています。override
キーワードの付いたメソッドを派生クラスが実装すると、その機能を派生クラスが担当することになり、親のメソッド実装は、派生クラスから明示的に呼び出されない限り実行されなくなります。要するに、子クラスの動作が親クラスの動作をオーバーライド(上書き)するわけです。今回の例では、コントロールの描画方法を変更しなければならない理由はないので(派生コントロールにデータを割り当てることさえできれば、それで目的は達成されます)、Render
メソッドは完全に削除できます。ここが継承のすばらしいところです。メソッドをオーバーライドして実装する必要のない機能はすべて親が担当してくれるため、一部の特別な機能を追加するだけで済みます。
今回作成する派生コントロールでは、Populate()
というメソッドだけを公開します。このメソッドは、データをデータベースからフェッチし、その結果として生成されるDataReader
をドロップダウンリストコントロール(つまり親コントロール)にバインドします。単に親のDataBind
メソッドをオーバーライドするという方法もありますが、そうすると、ユーザーが本稿のコントロールを既定のドロップダウンリストとして使うことはもうできなくなります。DataBind
メソッドの既定の動作を変更すると、ユーザーは自分のデータフェッチコードを書いたり、本稿のコントロールを既定のドロップダウンリストとして使ったりすることができなくなります。つまり、本稿のコントロールの使用は制限され、事実上、機能性や柔軟性が低下してしまいます。そこで、DataReader
をフェッチした後に親のDataBind
を呼び出し、ドロップダウンリストにデータを割り当てる、という処理を行う新しいPopulate()
メソッドを追加することにします。図12に、Populate()
メソッドの完全なコードを示します。
Populate
メソッドのコードリストに示したとおり、親コントロールのプロパティとメソッドにアクセスするにはC#のキーワードbase
を使います。SQLのコードはすべて、try-catch
ブロックでラップすることができます。SQLアクセスコードがエラーをスローした場合は、Populate
メソッドがfalse
を返し、ErrorMsg
プロパティがセットされます。
コードの残り部分は、先ほど作成してテストしたデータアクセスコードの「テンプレート」とまったく同じです。どれをビルドしても、エラーや警告は表示されません。リリースビルドが完成したら、コントロールを使用する任意のプロジェクトの「bin」フォルダにDLLを配置します(ただし、派生コントロールのDLLがマシンのGCAに配置される場合は除きます)。
コントロールの使用
では、最初に実装してみた、データフェッチコードを含むWebプロジェクトに戻りましょう。新しいコントロールのDLLを、アプリケーションの「bin」フォルダに配置するのを忘れないでください。「bin」フォルダに配置した後は、コントロールをVisual Studio .NETにリンクする必要があります。まず、コントロールからツールボックスへの参照を設定しましょう。このためには、次の手順を実行します。
- ツールボックスを右クリックした後、[Custmize Toolbox ...]をクリックします(図13)。コントロールをツールボックス内の特定の領域(たとえばWebForms、Data、Generalなど)に表示したい場合は、ツールボックスの該当するエリアを確実に右クリックしてください。
- [Customize Toolbox]ダイアログボックスで[.NET Framework Components]タブをクリックし、[Browse]ボタンをクリックしてコントロールのDLLの場所を指定します(図14、図15)。
コントロールからの参照が確立できると、ツールボックス内の選択した領域にコントロールが表示されます(図16)。
コントロールがツールボックス内に表示されるようになったので、他のコントロールを追加する場合と同様、このコントロールを任意のWebフォームに追加できるようになります。図17は、Visual Studio .NET IDEのプロパティウィンドウの[Data]セクションに、標準のドロップダウンリストコントロールのプロパティと、派生コントロールに追加した新しいプロパティが表示されている様子です。これらのプロパティは、先ほどのWebアプリケーションで手動でフェッチしたものと同じデータをドロップダウンリストに割り当てるように設定されています。
プロパティを設定した後は、ASPXページのPage_Load
イベントに、派生コントロールにデータを割り当てるためのコードを追加する必要があります。Populate
メソッドは結果を返すため、その結果をチェックし、エラーが発生したかどうかを示すErrorMsg
プロパティの値を表示する必要もあります。今回の例では、ページに追加したラベルコントロールを使ってメッセージを表示することにしました。図18に、派生コントロールにデータを割り当てるために必要な新しいコードを示します(たったの2行です)。
ページをコンパイルして実行すると、両方のコントロールが同じデータを横並びに表示します(図19)。
最後に残ったのは、エラー処理が期待どおりに動作するかどうかを確かめることです。このことをテストするには、DataTextField
プロパティの値を無効な値に変更し、アプリケーションを再実行します(図20)。コントロールから発されたエラーは、ページのラベルに表示されます(図21)。
まとめ
本稿では.NETの「継承」と「サーバーコントロール」という2つの強力な機能を組み合わせて、フェッチ機能付きドロップダウンリストコントロールを作成する方法を見てきました。データ中心のWeb開発が主流になっている今日では、データドリブンのドロップダウンリストは必須要素になっています。本稿では、コードをプロジェクトごとに何度も記述してテスト、メンテナンスするという労力を大幅に削減するにはどうすればよいかを見てきました。コントロールの作成時には、エラーを呼び出し元に送り返すのではなく、それらのエラーをトラップし、処理する方法を吟味しました。さらに、名前空間の重要性や、名前空間をフル活用して企業全体での再利用が可能なコードを開発する方法も調べました。
本稿のサンプルはごく簡単なものであり、機能を強化したり、より堅牢なエラートラップを実現したり、オプション値と表示テキストの列をユーザーが入力できるようにしたり、組み込みSQLの代わり(あるいは補助)としてストアドプロシージャをサポートするようにプロパティやメソッドを追加したりするなど、改良の余地はまだまだあります。本稿は「サーバーコントロールを拡張したり、新たに作成することによって何ができるか」という問題の表面をなぞっただけでしかありませんが、ここで作成したコンポーネントは、現実の開発ニーズを満たす、再利用可能なコンポーネントです。継承のおかげで、レンダリングや表示の状態などについては特に気にかける必要もなく、これらの細かな処理は親に任せるだけで済みました。しかし、こういった領域でこそ、サーバーコントロールは大きな力を発揮することができます。サーバーコントロールは、従来よりもはるかに機能豊富なWebアプリケーション用ユーザーインターフェイスを実装するための強力なパラダイムとなります。本稿を通じて、サーバーコントロールという領域にさらに興味を抱いてもらえれば幸いです。