SPA を IIS から流す際の ASP 側のルーティング


Single Page Application (SPA) にて、トップ以外のページで F5 (page reload/reflesh) を押すと、サーバーから Page Not Found が返ってくる。通常、SPA をレンダーする Web server は、基本的に、GETリクエストに対して、ごく短い 唯一のページ返すだけがお役目。\cart とか \list 等が GET で飛んできても、左様なページ(MVCだとcontroller) は存在しないので、404 エラーとなる。

そこでさっそく調べると、以下を web.config に追加せよ、とある(Web serverは IIS を想定)。

    <rewrite>
      <rules>
        <rule name="Rewrite Text Requests" stopProcessing="true">
          <match url=".*"/>
          <conditions>
            <add input="{HTTP_METHOD}" pattern="^GET$"/>
            <add input="{HTTP_ACCEPT}" pattern="^text/html"/>
            <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true"/>
          </conditions>
          <action type="Rewrite" url="/" appendQueryString="true" />
        </rule>
      </rules>
    </rewrite>

これにて、IIS で走っている Server App には、/ が渡され、デフォルトの Controller がトップページをレンダーする。404 は解決し、唯一のページがレンダーされる。しかし、何も表示しない。Fiddlerすると、/list にて返ってくる html の js や css のパスが、全て /list/dist/bundle.js のように、/list が頭に付与されてしまう。これらは全部 404 となるので、ページは真っ白。

唯一のページの一部:
    <link rel="stylesheet" href="~/Content/site.css" />
    <script type="text/javascript" src="~/dist/bundle.js"></script>

ようは "~/" が、"/" となるべきところ、"/list" に置き換わり、レンダーされてしまう。私の目が悪いのか、このあたり、ピシッと解説しているのが見つからない。皆さんどうやって解決しているのだろう。/ で決め打ちだったら動作するだろうなという解説は多いが、~/ はこの場合譲れない。virtual site に置かれる可能性がかなり高いから。

あれこれ試したところ、IISのRewriteはあきらめ、ASP アプリのルーティングでとりあえず解決した。こんな感じ。

    public class RouteConfig
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

            var home = new
            {
                controller = "Home",
                action = "Index",
                id = UrlParameter.Optional
            };

            routes.MapRoute(
                name: "list",
                url: "list",
                defaults: home
            );

            routes.MapRoute(
                name: "doc",
                url: "doc/{id}",
                defaults: home
            );

            routes.MapRoute(
                name: "signout",
                url: "signout",
                defaults: home
            );

            routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: home
            );
        }
    }

IIS での Rewrite をやめたので、ASP の tide の挙動は通常ものに戻り、~/ は常に virtual directory を含めて正確にアプリのルートを示してくれる。上の難点は、F5 が押されたり、Bookmark 等から飛んでくるであろう 正常な Url 全てを定義する必要がある事。ページ数が少ないうちは良いが、、、ベターなやり方があったら、教えてください。

---
次に、Virtual Directory 環境できちんと動作するか、テスト。

VS、Project Property, Web にてIIS Express に Virtual directory を以下のとおり作成。
Project Url: https://localhost:44330/spa
Create Virtual Directory をクリック。

VSにてF5すると、 https://localhost:44330/spa (唯一ページ)がレンダーされるが、React側で、左様なページは無いと叱られた。そうだ、Reactにベース・パスを設定しなければ。これは簡単。

        <BrowserRouter basename="/spa"}>

これで叱られなくなったが、先ほど "~/" は譲れないと偉そうなことを申した手前、deploy 毎にこれを書き直すのは武士の一分が立たぬ。そこで、MVCプロジェクトで良くやってきた手を使う。サーバー側の唯一ページのViewに以下を組み込む。

    <script type="text/javascript">
        var virDir = "@Url.Content("~/")";
    </script>
    <script type="text/javascript" src="~/dist/bundle.js"></script>

タイミングが良く分からぬが、bundle.js の前に置いた方が安全っぽ。これにて確実に virDirに virtual directory の文字列が格納される。先ほどの記述を、

        <BrowserRouter basename={virDir || "/"}>

なのだけど、TypeScript で書いているので左様な変数は無いと叱られる。
declare var virDir: string; // web server to render the virtual dir path
を冒頭に置いてOK。

以上で、綺麗に動いている。production で virtual directory に置かれても問題ないだろう(近々テスト予定)。

コメント

このブログの人気の投稿

HiddenFor 要注意

Jest の テスト・スクリプトをデバッグする術