ATL ServerライクなASP.NETプログラミング

概要

ASP.NETは入力フォームの作成にはきわめて有用であるが、カスタム出力の作成には必ずしも適していないことがある。一方ATL Serverが持つプログラミングスタイルは、入力フォームの作成の容易さではASP.NETに劣るものの、シンプルでしかもロジックとユーザーインターフェイスをほぼ完全に分離しながら柔軟な出力成果物を作成できるというメリットがある。しかしながら、このATL ServerのプログラミングスタイルはATL Serverに独特なものではなく、ASP.NETでも採用することが可能である。このスタイルはとりわけ複雑な出力物の作成において威力を発揮する。以下ではATL ServerのプログラミングスタイルをASP.NETで採用する方法について述べる。(2008-7-19)

ASP.NETによる出力帳票作成の問題点

ASP.NETのイベント駆動型プログラミングモデルは、データ入力画面の作成において非常に優れていることは疑いのないところである。しかしながら、ちょっと標準とは異なる出力画面を作る段になると必ずしも最適とは言えないところがある。たとえば、データベースのデータを使って表を作成するような場合、データベースのテーブルの1レコード(正確にはSQLのSELECT文が返す結果の1行)が表の1行に対応するようなものなら、ASP.NETが標準で備えるGridViewやRepeaterコントロールなどを使えば比較的簡単に作成することができる。しかしながら、表の列数が一定していなかったり、表の行ではなく1セルが1レコードに対応していたり、または、対応するレコードが存在しなくても表には1行または1セルを作成しなければならないなど、少し変則的な表になると途端に敷居が高くなる。ASP.NETのやり方でこれらを実現しようとすると、クラスのオーバーライド、イベントハンドラの記述、カスタムコントロールの作成といった手法を使うことになるだろう。だが、HTMLの文法を知っている開発者なら次のように思ったことはないだろうか。「やりたいことはデータを<td></td>タグで囲んで書き出すだけなのに、なぜこんなにも沢山のクラスを生成したり面倒くさいコードを書く必要があるのだろうか」と。

まわりくどいコーディングが嫌になった開発者は、クラシックASPでやったように.aspxファイルに直接プログラムを書き込んだり、プログラムの中で表に関するHTMLを全部構築してそれを一気に書き出すといった方法を採るかもしれない。この方法は有効だが、プログラムとユーザーインターフェイスが分離していないため、ページデザインの変更の容易さが犠牲になってしまう。

ページデザインの変更の容易さを維持したままで、かつ回りくどくないコーディングはできないのであろうか。これに対する答えが以下で述べるように「ATL Serverでやっているプログラミングスタイルを真似すること」である。

ATL Serverのプログラミングスタイル

ATL Serverは、C++言語を使ってWebアプリケーションやWebサービスを構築するためのフレームワークであり、Microsoft社によって開発された。ユーザーインターフェイスとプログラムロジックをほぼ完全に分離でき、実行速度も非常に高速で、しかも開発生産性も高いというすぐれたフレームワークであったが、残念ながら今はMicrosoftの手を離れてしまった。MicrosoftがASP.NET一本に集中した背景には、Windows Server 2008の導入によりASP.NETのパフォーマンスが飛躍的に向上したことがあるように思われる。

ASP.NETもユーザーインターフェイスとプログラムロジックが分離していると言われることがあるが、実際にはデザインとコーディングを並行して行うことができると謳っているだけで、ユーザーインターフェイスとプログラムロジックが分離しているわけではない。たとえばフォーム上にリストボックスを配置した場合、そのリストボックスの項目を埋めるプログラムを書くことになる。これはすなわちプログラムがユーザーインターフェイスと密接な関係があるということである。もしフォーム上からリストボックスを削除したり、リストボックスの代わりにドロップダウンメニューを置いたりすれば、プログラムはエラーを起こして動かなくなってしまう。当たり前と思われるかもしれないが、ATL Serverではこのようなことは普通起こらない。

ATL Serverで使われるタグ

ASP.NETにおける.aspxファイルに相当するものは、ATL Serverでは拡張子.srf が付いたファイル(Server Response File) になる。中身は.aspxファイルと同じく、タグを埋め込んだHTMLないしXHTMLファイルである。ASP.NETでは <% ... %>でコードを、<asp:xxx  /> でサーバーコントロールを埋め込むが、ATL Serverで使うタグは{{...}}で囲まれ、基本的に次の3種類だけである。

1. {{Method1(argument)}}
メソッドが返す文字列で置き換わる
2. {{if Method2(argument)}}Html A{{else}}Html B{{endif}}
メソッドが真を返せばHtml Aの部分が、偽を返せばHtml Bの部分が使われる。「{{else}}Html B」の部分は省略可。
3. {{while Method3(argument)}}Html C{{endwhile}}
メソッドが真を返す間 {{while ...}}と{{endwhile}}の間が繰り返し出力される。

メソッドによっては「(argument)」の部分はないこともある。また、argumentのところはいわゆる変数ではなくて固定の文字列である。

たとえば、入力フォームにおけるチェックボックスなら以下のようになるだろう。
<input type="checkbox" name="MyCheckbox" id="MyCheckbox" {{if IsChecked}} checked="checked"{{endif}} value="1"  />
ここで IsChecked の部分は開発者が作るべきメソッドの名前である。つまり、メソッドIsChecked が真を返せば「checked="checked"」の部分が出力されるのでチェックボックスがオンで表示されることになる。(ここではメソッドに引数は使っていない。)
また、リストボックスを作成する場合は、たとえば以下のようになるであろう。

<select name="MyListbox" id="MyListbox" size="5">{{while MoreData}}
  <option {{if IsSelected}} selected="selected"{{endif}}>{{MyData}}</option>{{endwhile}}
</select>

ここでは、MoreData, IsSelected, MyData という名の3種類のメソッドが使われている。MoreData と IsSelected は真か偽を返し、MyData は表示すべき文字列を返すメソッドである。MoreDataメソッドが真を返す間<option>タグの部分が繰り返されることになる。

C++を知っている人がより理解を深められるように、対応するC++のメソッドの例も示すことにする。たとえば文字列配列の内容を出力する場合、MoreData メソッドは

HTTP_CODE MySamplePage::MoreData()
{
  return ++m_Counter < m_ArraySize? HTTP_SUCCESS: HTTP_S_FALSE;//HTTP_S_FALSEを返すとループを抜ける
}

MyDataメソッドは、

HTTP_CODE MySamplePage::MyData()
{
  m_HttpResponse << m_Data[m_Counter-1];//m_Dataはchar* の配列とする。HTMLエンコードも本当は必要
  return HTTP_SUCCESS;
}

のような形になる。MyDataメソッドは文字列を返すと書いたが、実際にはメソッドの戻り値が文字列というわけではなく、m_HttpResponseというCHttpResponse型のメンバー変数で示されるストリームに文字列を書き出すというのが本当である。また、.srf ファイル中の{{...}}に書くメソッド名と、C++のメソッド名は必ずしも同じである必要はないが、分かりやすくするために同じにしている。

3種類のタグというのは少なすぎるように思えるかもしれない。しかし実際にやってみるとわかるが、これらを駆使すればほとんどの実用的なWebページを記述することができるのである。

ユーザーインターフェイスとコードの分離

リストボックスの例を見ても分かるとおり、ATL Serverではコード側で出力がリストボックスであるという仮定は何もされていないのに気づくだろう。たとえば、リストボックスではなくテーブルとして出力したい場合は、

<table>{{while MoreData}}
  <tr>
    <td>{{if IsSelected}}√ {{endif}}{{MyData}}</td>
  </tr>{{endwhile}}
</table>

のように記述すればよく、C++のソース側は何も変更する必要がないのである。同様に、チェックボックスのオン・オフの代わりに

<img src="{{if IsSelected}}selected.gif{{else}}notselected.gif{{endif}}" />

のようにイメージの切り替えで表示するといったことも簡単にできる。これがATL Serverではユーザーインターフェイスとロジックがほぼ完全に分離していると言われる所以である。ATL Serverでは、プログラマはHTMLに関する詳しい知識をほとんどまったく必要とされないのである。一方、ページを作成するデザイナの方は、{{...}}の形の3種類のタグの意味を理解する必要があるが、これはプログラミングというほど複雑なものではなく、HTMLデザイナならすぐに理解できる程度のものである。しかも、デザイナは最終的に出力されるHTMLを完全に把握かつ制御することができる。ASP.NETでサーバーコントロールが埋め込まれている場合は、最終出力イメージは実際にコードと組み合わせて実行してみないと分かりにくいところがあるし、デザイナはサーバーコントロールのプロパティの意味を理解していないと変更もできない。ATL Serverではこのようなことはまずない。さらに、ATL Serverでは出力するのがHTMLである必要もなく、たとえばCSVファイル形式で出力するようにすることも簡単にできる。異なる言語、たとえば日本語と英語のページを作成しなければならないような場合も、一方のページをコピーしてテキスト部分を置き換えるだけで簡単に作成できる。ただし、文字列データが直接吐き出されるようなところはプログラムでも考慮する必要はある。(前出の例では、たとえば{{MyData(JP)}}と{{MyData(EN)}}のように引数を付け、コード内で引数に応じて出力すべき文字列の言語を判別するといった処理が必要になるかもしれない。)

ASP.NETでATL Serverのスタイルを真似る

ATL Serverでは使えるタグの制限からユーザーインターフェイスとロジックの分離が自然と実現されるのである。しかしながら、この方法は何もATL Serverに限ったことではなく、ASP.NETでも使えることが分かるであろう。つまり、ATL Serverの3種類のタグに対応してASP.NETでも使うタグを次の3つの形に制限するのである。(C#の場合)

  1. 値を出力するところは <%= Method1() %> とする
  2. 条件制御には、<% if(Method2()){ %><% } else { %> および <% } %> を使う
  3. 繰り返しがあるところは、<% while(Method3()){ %> および <% } %> を使う

ATL Serverの{{endif}}に相当するのが <% } %> というのがいま一つ恰好がつかない気もする。C#ではなくVisual Basic.NETを使えばもう少しサマになるのだろうが、今までC++でコーディングしてきたプログラマにとってVisual Basicのような初心者向けの言語を使うのは苦痛であるに違いない。筆者もそうである。なので、ここは恰好が悪くても我慢することにする。また、ASP.NETでは<% if(Method2()+Method3() > 10){ %> といった凝った形式の条件文も書くことができるが、複雑化する元凶なのでなるべく使わないように取り決めた方がよいだろう。

.aspxファイルに埋め込むコードを上の3種類の形にあえて制限することで、自然とユーザーインターフェイスとロジックの分離を実現できるようになる。(もちろんすべてのページをこの方式で作る必要はない。)たとえば、前出のテーブルを作る例は、ASP.NETでは次のようになる(IsSelectedのところは省略した)。

<table><% while(MoreData()){ %>
  <tr>
    <td><%= MyData() %></td>
  </tr><% } %>
</table>

ちなみに列数も可変な場合は、次のようなwhileの2段形式になる。

<table><% while(MoreData()){ %>
  <tr><% while(MoreColumns()){ %>
    <td><%= MyData() %></td><% } %>
  </tr><% } %>
</table>

コーディングスタイルの変更

コーディングスタイルもこれに合わせて若干の変更が必要になる。ASP.NETの普通のプログラミングでは、Page_Load メソッドや、ボタンがクリックされたときに呼び出されるメソッドの中で主だった処理は完了することが多かった。 ATL Server方式では処理がいくつかのメソッドに分散することになる。

たとえば、前出のテーブル(またはリストボックス)を生成する例に使われたMoreDataなどのメソッドは、ASP.NETではたとえば次のようになるだろう。(以下ではエラー処理を省略している。)

private DbConnection conn;
private DbDataReader reader;
...
protected void Page_Load(object sender, EventArgs e)
{
  ConnectionStringSettings settings = ConfigurationManager.ConnectionStrings["MyConnection"];
  DbProviderFactory dbProviderFactory = DbProviderFactories.GetFactory(settings.ProviderName);
  conn = dbProviderFactory.CreateConnection();
  conn.ConnectionString = settings.ConnectionString;
  conn.Open();

  DbCommand cmd = conn.CreateCommand();
  cmd.CommandText = "SELECT MyData FROM MyTable";
  cmd.CommandType = CommandType.Text;
  reader = cmd.ExecuteReader();			
}

protected bool MoreData()
{
  return reader.Read();
}

protected string MyData() 
{
  return Server.HtmlEncode(reader.GetString(0));
}

protected void Page_Unload(object sender, EventArgs e)
{
  if (conn != null)   conn.Close();
  if (reader != null) reader.Close();
}

データベースからデータを取得する場合、一旦配列やDataSetなどにすべて読み込んでから出力するという方法でももちろんかまわない。上の例はReadonlyでデータを最速で読み込む場合の一例である。MoreDataメソッドの中で(reader.Read()で)1行ずつ順に読み込んでいくため、Page_Loadメソッドの中では using(...) { } シンタックスを使ってオブジェクトを自動的にクローズすることはできない。コネクションの解放はPage_Unloadなどで明示的に行う必要がある。

このような単純な例ではむしろGridViewなどのサーバーコントロールを使うほうが簡単かもしれない。最初にも書いた通り、この方式がとりわけ有用になるのは標準とは少し違う形式で出力をする場合である。上の例でいう MoreDataメソッドの1回の呼び出しにつき1レコードを読み込むといった単純なものなら苦労はしないが、そうは問屋がおろさないというのが現実の世界である。その場合MoreDataメソッドのところで最も複雑なコーディングが要求される。しかし、プログラム内でHTML文を生成するのに比べれば純粋にロジックだけに専念できるので、慣れればそう難しいことではない。やり方がわかってくると、(HTMLの<table>の中で使う)colspanやrowspanを使ったセルのマージも意外と簡単にできるようになるだろう。

最後に

従来のASPと変わらないのでは?

繰り返したいHTMLの部分を<% While ...%> と<% Wend %>といったタグで囲むといったことは、従来の(.NET以前の)ASPでも普通に行われていたことで何も珍しいことではないと言う人もいるかもしれない。確かに.aspファイルに直接コードを埋め込んでいた時代では同様な記述を行っていた。しかしながら従来のASPでは、たとえばWhile文の直前でデータベースにアクセスするコードが書かれていたりと見通しが悪いものであったはずだ。if 文の使い方も一定しておらず、前出のATL Serverの例のようにデザイナが表現方法を変えようと思った時に簡単にそれができるようになっていなかったのではないだろうか。使えるタグを整理し、ユーザーインターフェイス部分を分離できるというところがここでの方式の新しいところである。

.aspxファイルにプログラムを埋め込むの良くないのでは?

.aspxファイルにプログラムを書くのは良くないことだと盲目的に信じている人もいるかもしれない。プログラムロジックを.aspxファイルに書き込むことは確かに分かりにくくなるので勧められない。だが、ここで使う3種類のタグはプログラムロジックと言えるほどのものだろうか。<% if (IsSelected()){ %> ... <% } %> といった記述はページ出力を制御するタグとしての最低限のものなのでこれによってロジックが分かりにくくなるということはまずないだろう。また、<asp: xxx /> のような形でサーバーコントロールを埋め込んでプログラムでHTMLを書き出すことの方が優れていると本当に言えるだろうか。結局のところ、最終的に出力されるHTMLに関して個人的に責任を負わされるのは誰かというとそれを作ったプログラマなので、プログラマがあずかり知らぬところで勝手にHTMLを吐き出すサーバーコントロールの方が危うい存在ではなかろうか。

ATL ServerでもHTMLをプログラムから書き出せるのでは?

するどい読者なら気づいただろうが、たとえATL Serverで使うタグが{{Method1}}のような1つであっても、このMethod1から何百行というHTML文を出力することも可能である。これは、ASP.NETのサーバーコントロールをATL Serverで逆に真似たやり方と言えるかもしれない。しかしながら、サーバーコントロールのようにプロパティを付けることができないので、表示の変更が必要な場合は再コンパイルが必要になってしまう。しかもページデザイナから見ればまったく予期できない振る舞いをするので、やはりこの方法は推奨できない。(致命的なエラーが起きた時のエラーメッセージや、全ページに共通して出力するフッター部分といった特殊な場合には有用かもしれない。)

資料室へ戻る