はじめに
ご存じのとおり、ASP.NET WebページはHTML部分とソースコード部分から成ります。このうち、HTML部分にはHTMLマークアップとWebコントロールが混在していて、誰かがASP.NET Webページを訪れると、そのWebコントロールからHTMLマークアップが生成され、静的HTMLマークアップと合わせて、そのページを要求した訪問者のもとに送られ、ブラウザに表示されます。
WebコントロールからどのようなHTMLが生成されるかは、ASP.NET Webページを要求したブラウザに依存します。その意味で、これをアダプティブ(適応的)レンダリングと呼びます。たとえば、訪問者がInternet Explorer 6.0を通じてページを要求したのであれば、生成されるマークアップはHTML 4.0準拠のマークアップです。仮にLabel WebコントロールのForeColorプロパティが赤(red)なら、生成されるマークアップは次のようになります。
<span id="controlID" style="color: red">Label Text</span>
しかし、同じ訪問者がNetscape 4.72を使ったとすれば、生成されるマークアップはHTML 3.2準拠となります。その場合、Label Webコントロールのカラー指定は、<span>のstyle属性によらず、<font>要素で行われます。
<span id="controlID"><font color="red">Label Text</font></span>
アダプティブなコントロールというのは確かにすぐれたアイデアですが、ASP.NETの既定の実装は必ずしもいいことばかりではありません。というのも、最近普及してきているブラウザ群(Mozilla、Firefox、Netscape、Operaなどの最近バージョン)に対しては、ASP.NETコントロールが既定でHTML 3.2(HTML 4.0でなく)準拠のHTMLを生成するからです。ただ、幸いなことに、個々のWebアプリケーション(あるいはWebサーバー全体)に適切な設定をすれば、これら新しい非Microsoftブラウザに対してもHTML 4.0準拠のHTMLが生成されるように指定できます。以下では、WebコントロールのHTMLを4.0準拠とするか3.2準拠とするか、ASP.NETがどう判断するのかを見ていくことにします。また、Netscape、Opera、Mozillaの最近バージョンに対しても4.0準拠のHTMLマークアップが生成されるよう、Webアプリケーションを設定する方法も見ておくことにします。
Webコントロールのレンダリングの仕組み
すべてのASP.NET Webページは、直接あるいは間接にSystem.Web.UI.Pageクラスから導かれます。このクラスには、ASP.NET Webページを表現するための基本的なメソッドとプロパティが用意されています。Webサーバーは、ASP.NET Webページへの要求を受信すると、それをASP.NETエンジンに引き渡します。ASP.NETエンジンは要求されたページのPageクラスのインスタンスを作成し、このクラスのProcessRequest()メソッドを呼び出します。
ProcessRequest()メソッドが呼び出されるところから、このページのライフサイクルが始まります。具体的には、ページのコントロール階層がロードされ、表示状態が復元され、Loadイベントが起こり、Webコントロールイベント群が起こり、表示状態が保存され、ページのレンダリングが完成します。次の図は、ASP.NETページのライフサイクルを示しています。
本稿では、このライフサイクルの詳細はさして重要ではありませんが、詳しく学びたいという方には、「How ASP.NET Web Pages are Processed on the Web Server」と「The ASP.NET Page Object Model」のページをお勧めします。
ASP.NET Webページのレンダリングは、次の方法で行われます。まず、PageクラスがSystem.Web.UI.HtmlTextWriterクラスのインスタンスを作成し、そのコントロール階層に含まれるすべてのコントロールのRenderControl()メソッドを再帰的に呼び出して、それにHtmlTextWriterインスタンスを引き渡します。各コントロールは、それぞれのHTMLマークアップを1つの(つまり、同じ)HtmlTextWriterインスタンスの末尾に付加していきます。すべてのコントロールのレンダリングが終わると、PageクラスはそのHtmlTextWriterインスタンスの内容(つまり、ページ全体のHTMLマークアップ)を返します。
HtmlTextWriterによるアダプティブレンダリング
System.Web.UI名前空間には、Webコントロールレンダリングのためのクラスが2つ含まれています。1つはHtmlTextWriter、もう1つはHtml32TextWriterです。前者がHTML 4.0準拠のマークアップ、後者がHTML 3.2準拠のマークアップのレンダリング用です(Html32TextWriterは、HtmlTextWriterからの派生クラスです)。
Pageクラスはレンダリング段階に入ると、まず新しいHtmlTextWriterインスタンスを作成します。HtmlTextWriterとHtml32TextWriterのどちらのクラスインスタンスを作成するかを判断しなければなりませんが、それにはBrowserオブジェクトのTagWriterプロパティを調べます(Browserオブジェクトには、ASP.NET Webページを要求してきたブラウザを特定するための読み取り専用プロパティがいくつかあります)。Webブラウザの特定には、User-Agent文字列が使われます。
Webブラウザはページを要求するとき、HTTPヘッダにUser-Agent文字列を含めて送信します。これが、ブラウザの特定に使用されます。たとえば、Internet ExplorerならMozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; .NET CLR 1.0.3705; .NET CLR 1.1.4322)、Mozilla FireFoxならMozilla/5.0 (Windows; U; Windows NT 5.0; en-US; rv:1.6) Gecko/20040206 Firefox/0.8というUser-Agent文字列を送信します。User-Agent文字列を送信しないブラウザや、「偽の」User-Agent文字列を送信するブラウザもあります。たとえば、Operaでは、IE6のUser-Agent文字列を送信させることができますし、Mozillaにも同様の拡張機能があります(ご使用のブラウザがどのようなUser-Agent文字列を送信するかに興味がある方は、User-Agentのデモを参照してください)。
ASP.NETエンジンはUser-Agent文字列を調べ、Browserのプロパティの値を特定します。プロパティの1つはTagWriterです。この値は、既定ではブラウザがInternet Explorer 4.0以上の場合のみHtmlTextWriterとなり、その他のブラウザではHtml32TextWriterとなります。しかし、これらの値は幸いなことに設定可能であり、Netscape、Mozilla、Operaの最近バージョンについても、Html32TextWriterではなくHtmlTextWriterクラスを使用するよう指示できます。こうした設定は、「maching.config」ファイルか「Web.config」ファイルの<browserCaps>セクションで行います(「maching.config」で設定を変更すると、そのWebサーバー上のすべてのWebアプリケーションの既定動作が変わります。アプリケーション単位の設定変更には、「Web.config」ファイルを使用してください)。
<browserCaps>要素群の検査
<browserCaps>要素には、いくつかの<case>要素が含まれています。どの<case>要素にもmatch属性があり、そこに何らかの正規表現が含まれています。ブラウザから送られてきたUser-Agent文字列とこの正規表現が一致すると、一致した<case>要素内部のプロパティ群がBrowserオブジェクトに適用されます。「machine.config」ファイルを見ると、既定では、TagWriterプロパティがHtml32TextWriterクラスに設定されていることがわかります。
<browserCaps>
...
tagwriter=System.Web.UI.Html32TextWriter
...
</browserCaps>
さらに下のほうを見ていくと、Internet ExplorerのUser-Agentに一致する<case>要素も見つかります。そこのTagWriterプロパティはHtmlTextWriterクラスに設定されているはずです。
<browserCaps>
...
<case match="^Mozilla[^(]*(compatible; MSIE <snip>">
browser=IE
...
tagwriter=System.Web.UI.HtmlTextWriter
</case>
</browserCaps>
非Microsoftブラウザでは、TagWriterプロパティが既定値(Html32TextWriter)のままです。
Netscape、Mozilla、Operaに対するHTML 4.0準拠のレンダリング設定
最近の非MicrosoftブラウザでもHTML 4.0準拠のマークアップが生成されるようにするには、「maching.config」ファイルや「Web.config」ファイルをどう設定すればよいでしょうか。その前に、なぜそのような手間をかける必要があるのかを考えてみましょう。赤いForeColorを持つLabelを表示するとして、ASP.NETがそのLabelを<span id="controlID" style="color: red">Label Text</span>としてレンダリングしようが、<span id="controlID"><font color="red">Label Text</font></span>(Netscapeの場合)としてレンダリングしようが、結果的に同じものが表示されるのなら、いっこうにかまわないのではないでしょうか。
確かに、ForeColorの場合はかまわないでしょう。しかし、LabelのBackColorプロパティならどうでしょうか。HtmlTextWriterクラスは、背景色の指定にbackground-colorというCSS設定を使用します。CSSのないHTML要素の場合は、<table>を使うか、何らかの裏技でも使わなければ、背景色を設定する方法がありません。したがって、Html32TextWriterクラスはBackColorプロパティを無視します。4.0準拠のマークアップが望まれる理由は、もう1つあります。それは、HtmlTextWriterなら<div>要素のレンダリングができることです。Html32TextWriterクラスは<div>を<table>としてレンダリングするので、その結果、状況によっては不都合が生じます。
以下に、「Web.config」ファイルに追加できる<browserCaps>セクションの一例を示します。この追加により、非Microsoftブラウザを使っている場合でも、WebコントロールからHTML 4.0準拠のマークアップが生成されるようになります。この<browserCaps>セクションでは、TagWriterプロパティ以外にも、Browserオブジェクトのプロパティがいくつか設定されていることに注意してください(この<browserCaps>セクションはRob Eberhardtが作成したもので、ここから入手できます)。
<!-- 2003-12-03, Rob Eberhardt - http://slingfive.com/demos/browserCaps/ -->
<browserCaps>
<!-- GECKO Based Browsers (Netscape 6+, Mozilla/Firebird, ...) //-->
<case match="^Mozilla/5.0 ([^)]*) (Gecko/[-d]+)? (?’type’[^/d]*)
([d]*)/(?’version’(?’major’d+)(?’minor’.d+)(?’letters’w*)).*">
browser=Gecko
type=${type}
frames=true
tables=true
cookies=true
javascript=true
javaapplets=true
ecmascriptversion=1.5
w3cdomversion=1.0
css1=true
css2=true
xml=true
tagwriter=System.Web.UI.HtmlTextWriter
<case match="rv:(?’version’(?’major’d+)(?’minor’.d+)(?’letters’w*))">
version=${version}
majorversion=${major}
minorversion=${minor}
<case match="^b" with="${letters}">
beta=true
</case>
</case>
</case>
<!-- AppleWebKit Based Browsers (Safari...) //-->
<case match="AppleWebKit/(?’version’(?’major’d)(?’minor’d+)(?’letters’w*))">
browser=AppleWebKit
version=${version}
majorversion=${major}
minorversion=0.${minor}
frames=true
tables=true
cookies=true
javascript=true
javaapplets=true
ecmascriptversion=1.5
w3cdomversion=1.0
css1=true
css2=true
xml=true
tagwriter=System.Web.UI.HtmlTextWriter
<case match="AppleWebKit/(?’version’(?’major’d)(?’minor’d+)
(?’letters’w*))( (KHTML, like Gecko) )?(?’type’[^/d]*)/.*$">
type=${type}
</case>
</case>
<!-- Konqueror //-->
<case match = "Konqueror/(?’version’(?’major’d+)(?’minor’.d+)
(?’letters’));w*(?’platform’[^)]*)">
browser=Konqueror
version=${version}
majorversion=${major}
minorversion=${minor}
platform=${platform}
type=Konqueror
frames=true
tables=true
cookies=true
javascript=true
javaapplets=true
ecmascriptversion=1.5
w3cdomversion=1.0
css1=true
css2=true
xml=true
tagwriter=System.Web.UI.HtmlTextWriter
</case>
</browserCaps>
さらに先へ――クライアントサイドでの妥当性検査
Netscape、Opera、Mozillaの最近のバージョンを使っているユーザーに対してHTML 4.0準拠のマークアップが生成されるようにASP.NETを設定することは、最初の重要な一歩です。しかし残念ながら、この変更を加えても、妥当性検査コントロールはクライアントサイド妥当性検査スクリプトを生成しません。これには2、3の困った理由がありますが、それについてはまたの機会に書くことにします。
そのときまで、ハッピープログラミング!
クライアントサイド妥当性検査についての記事
私が以前に執筆した記事では、クライアントサイド妥当性検査の実装方法を解説するとともに、ダウンレベルのブラウザではなぜクライアントサイド妥当性検査が動作しないかという理由について述べています。興味のある方は、「Client-Side Validation in Downlevel Browsers」を参照してください。