しばやん雑記

Azure とメイドさんが大好きなフリーランスのプログラマーのブログ

ASP.NET Web Forms のコンパイル周りの挙動を調べる

最近は流石に Web Forms を新規開発で使うことは少なくなってきていると信じたいところですが、基本的に Web Forms から MVC への移行はフルスクラッチになってしまうという現状もあり、どうにか Web Forms のままアーキテクチャを改善したいというケースは多いのではないでしょうか。

ASP.NET アプリケーションのモダナイゼーションを行っている立場としても、ここで Web Forms についてさらに深いところまで知っておくべきだと思ったので、まずは基本的な部分を確認しておきました。

コードビハインドと実体のクラスは別

今更言うまでもない感じもしますが Inherits で指定されている通り、コードビハインドで書いたクラスと実際にコンパイルされた ASPX のページクラスは別物です。

f:id:shiba-yan:20180128225548p:plain

あくまでも継承元のクラスを書いているにすぎません。ASPX の実体はランタイムによって実行時にコンパイルが行われるか、予め aspnet_compiler を使ってプリコンパイルすることで生成されます。

コードビハインドは必須ではない

Visual Studio で Web Forms を追加するとコードビハインドがセットになりますが、必要ない場合は削除することが出来ます。この場合は自動的に Page クラスから派生されることになります。

Web Pages や Razor Pages のようにインラインでコードを書くことも出来ますが、基本的に書くべきではありません。あくまでも ASPX を純粋なテンプレートエンジンとして使う場合に限った方が良いでしょう。

<%@ Page Language="C#" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title></title>
</head>
<body>
    <div>
        <%: DateTime.Now %>
    </div>
</body>
</html>

懐かしの ASP.NET MVC 3 より前で良く使われていた書き方です。

ちなみに Inherits を追加すれば、カスタムクラスから派生させることも出来るので、応用範囲としては広いと思います。必要ないクラスを作ることはありません。

ASPX 単位では C# / VB の共存が可能

普通はやらないと思いますが、ASP.NET はビューがアプリケーションとは別にコンパイルが行われるので、C# プロジェクトであっても VB で書かれた ASPX を追加できます。ただしコードビハインドは無理です。

Language に VB を指定して、Visual Studio でファイルを開き直せば VB のコードが書けるようになります。

<%@ Page Language="vb" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
        <div>
            <%: Me.Context.Request.RawUrl %>
        </div>
    </form>
</body>
</html>

ちゃんと ASPX は VB ファイルとしてコンパイルされていることが確認できます。

f:id:shiba-yan:20180128220250p:plain

中身を確認しておきましたが、ちゃんと VB のコードになっています。

f:id:shiba-yan:20180128221019p:plain

C# プロジェクトで VB を使って ASPX を書くメリットは無いと思いますが、逆に VB プロジェクトでは C# を使って ASPX を書けた方が良い場合はありそうです。

余談 : IHttpHandler について

ASP.NET の仕様では IHttpHandler がプリミティブな処理単位となっています。ASHX で書くジェネリック HTTP ハンドラーと同じで、ASPX もコンパイルされた結果は IHttpHandler を実装したクラスになります。

ちなみにコンパイルされた結果のクラスは BuildManager.GetCompiledType を呼び出すと取得できます。

// コンパイルされた About.aspx の型を取得
var type = BuildManager.GetCompiledType("~/About.aspx");

// About.aspx の実体をインスタンス化
var page = (Page)Activator.CreateInstance(type);

そして HttpContext には RemapHandler という IHttpHandler を入れ替えるメソッドがあります。

HttpContext.RemapHandler(IHttpHandler) Method (System.Web) | Microsoft Docs

ASP.NET Routing は IHttpModule と RemapHandler を組み合わせて、ASP.NET MVC と同じようなルーティングを実装しています。ASP.NET MVC も BuildManager 周りを上手く抽象化してテンプレートエンジンとして実装しています。

最終的に IHttpHandler にしてしまえば、後は ASP.NET ランタイムに食わせて処理が可能ということです。