しばやん雑記

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

ASP.NET ルーティングで静的ファイルを扱う

id:ladybug さんの以下のコメントで System.Web.Routing を調べた時に面白いプロパティを見つけたので試しました。

いつも、IgnoreRoute に contents/* や scripts/* を追加しなくてよいのだろうか?と悩みますが、追加しないものなのでしょうか?(最近だと、scripts/ は、CDN を使用するのでデバッグ用なのですが)

適当に静的ファイルだから ASP.NET の処理パイプラインに入らないから IgnoreRoute の指定がいらないんだろうとか思ってましたが、MSDN を調べたところしっかりと動作が説明されてました。

URL パターンに一致する物理ファイルが見つかった場合

既定では、ルーティングは、Web サーバー上の既存の物理ファイルにマップされる要求を処理しません。たとえば、http://server/application/Products/Beverages/Coffee.aspx という要求は、Products/Beverages/Coffee.aspx に物理ファイルが存在すると、ルーティングによって処理されません。この場合、定義されているパターン ({controller}/{action}/{id} など) に要求が一致しても、ルーティングによる処理は行われません。
すべての要求 (ファイルを指す要求であっても) をルーティングで処理するには、RouteCollection オブジェクトの RouteExistingFiles プロパティを true に設定して、既定の動作をオーバーライドします。この値を true に設定すると、定義されているパターンに一致するすべての要求がルーティングによって処理されます。

ASP.NET ルーティング

つまり、RouteCollection の RouteExistingFiles プロパティが true の時は静的なファイルへのリクエストでも ASP.NET MVC で処理できるということです。

そこでリクエストされた CSS ファイルをクライアントに返す前に、空白削除と改行削除という簡単な Minify 処理を行うサンプルコードを書いてみました。何はともあれ、まずはルーティング定義を弄るので Global.asax を編集します。

public static void RegisterRoutes(RouteCollection routes)
{
    // 静的ファイルもルーティングの対象とする
    routes.RouteExistingFiles = true;

    // 今回は Scripts 以下のファイルは処理しない
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
    routes.IgnoreRoute("Scripts/{*pathInfo}");

    // Content 以下にある拡張子が CSS のファイルへのリクエストは Content コントローラの Index へルーティング
    routes.MapRoute(
        "Stylesheet",
        "Content/{file}.css",
        new { controller = "Content", action = "Index" }
    );

    routes.MapRoute(
        "Default", // Route name
        "{controller}/{action}/{id}", // URL with parameters
        new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
    );
}

今回は JavaScript に対しては処理を行わないので IgnoreRoute に追加しておきます。そして CSS ファイルへのリクエストは ContentController の Index アクションへルーティングするようにしました。ではコントローラとアクションを見ていきます。

public class ContentController : Controller
{
    //
    // GET: /Content/

    public ActionResult Index(string file)
    {
        var path = Server.MapPath("~/Content/" + file + ".css");

        var content = string.Join("", System.IO.File.ReadAllLines(path).Select(p => p.Trim()));

        return Content(content, "text/css");
    }
}

ちょっと手抜きしましたが、MapPath で実体のファイルパスを取得して File.ReadAllLines でファイルの全行を読み込み、そして LINQ を少しだけ使って空白削除をして結合しています。これを Content メソッドで MIME 指定して呼び出してあげるだけです。

それでは IE9 のネットワークタブでレスポンスを確認してみます。

Content/Site.css へリクエストを投げた応答本文は行表示が 1 しかないので 1 行で出力されていることがわかると思います。

これを応用すれば User-Agent を調べてキャリアごとに特化した CSS を出力することなどが出来ますね。