Web側は主にLAMPで作成していたのですが、しっかりASP.NETもやってみようということでASP.NET MVCについて調べてみました。
読めば自明ですが、ASP.NETの初心者の手探り記事です(内容に間違いあれば指摘お願いします)。
(追記:よりスマートな手段を教えていただいた「続・ASP.NET MVCで管理ページをどう作るか?」も参照ください)
ASP.NET MVCのプロジェクトを眺めてみると、早い段階でCakePHPやFuelPHPのようなファイル構成であることが見て取れます(本当は「RoRのような」と書くべきなんでしょうがLAMPな人間なので・・・)。
Controllersの下にHomeController.csなどがあり、基本的にHomeControllerのIndexメソッドとURLがマッピングされて/Home/IndexでアクセスするとIndexメソッドの処理が走り、左に対応したViewファイルはViews\Home\Index.cshtmlであると・・・。
規約に沿って作るとすごく簡単だけどちょっと線路を外れると荒野を歩く知識が必要になるやつですね。
この辺のフレームワークで個人的に毎回調べるのが「管理ページをどうするのか?」。
管理ページを作成するうえで希望する要件をあげてみる。
要件1:通常のページがドキュメントルート(http://hogehoge/)から始まるなら管理ページはドキュメントルート/admin(http://hogehoge/admin/)にしたい
要件2:コントローラーファイルの位置はControllers/Admin/LoginController.csという位置に配置したい。またController/LoginController.csという管理者ではなく一般の人がログインするような同名で置かれた場所が違うLoginコントローラも存在できること。
要件3:管理ページ用のViewファイルはViews\admin以下に配置する
以上の要件を実装すべく調べてみました。
デフォルトではURLの仕組みは/{controller}/{action}です。
actionはコントローラークラスに記述されたメソッドになります。
それを「管理ページの場合は/admin/{controller}/{action}というルールにしたい」という要件はURLのルーティングをカスタマイズする必要があります。
ルーティングはApp_StartのRouteConfig.csを編集します。
// 管理ページ routes.MapRoute( name: "Admin", url: "admin/{controller}/{action}/{id}", defaults: new { controller = "login", action = "index", id = UrlParameter.Optional } ); routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "top", action = "index", id = UrlParameter.Optional } );
これで/admin/login/indexにアクセスするとLoginControllerのindexメソッドが呼び出されるようになりました。
しかし、表示したページのURLがリンク切れになってしまいました。
/adminから始まらないページのURLが以下のようになっているのが原因でした。
/admin/Hone/Index
・・・頭にadminがついています・・・。
Viewファイルを確認するとサイト内リンクは以下のように生成しているようです。
<li>@Html.ActionLink("ホーム", "Index", "Home")</li>
色々試した結果、以下のように記述することで対応できました。
<li>@Html.RouteLink("ホーム", "Default", new { action = "Index", controller = "Home" })</li>
RouteLinkメソッドの第二引数にRouteConfig.csで指定したMapRouteのDefaultを利用すると明示的に記述しています。
(もっといい方法がありそうな気がします)
Controllers\Admin\LoginController.csのような配置にしても動作はしますが、管理ページのログインではなく、表サイトのログインをControllers\LoginController.csとして作成した場合に問題が起きます。
この場合、ルーティング時に名前空間を指定してあげます。
// 管理ページ routes.MapRoute( name: "Admin", url: "admin/{controller}/{action}/{id}", defaults: new { controller = "login", action = "index", id = UrlParameter.Optional }, namespaces: new string[] {"ProjectName.Controllers.Admin"} ); routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "top", action = "index", id = UrlParameter.Optional }, namespaces: new string[] {"ProjectName.Controllers"} );
ログインページが/admin/login/indexというURLだとするとViewsファイルは/admin/login/index.cshtmlというフォルダに配置したいという要件です。
簡単に対応したい場合はViewクラスを返す箇所を以下のように修正することで動作します。
return View("../admin/login/index.cshtml");
これアクション毎に記述しなきゃいけないじゃないか!! 面倒!!
ということでせめて管理ページ用のコントローラーに親クラスを用意してそこで定義しておいて、管理ページ用のコントローラーはそれを継承するようにしたい。
というわけで調べてみると、ViewのパスはViewEngineで制御していることがわかる。
また、ViewEngineはRazorとWebの2つのViewEngineがあることが判明。
今回はRazorを使っているのでそいつのパスを判定する仕組みをいじってみることにします。
管理ページ用のコントローラーで以下のように記述します。
var engines = ViewEngines.Engines; foreach (var engine in engines) { if (engine is RazorViewEngine) { (engine as RazorViewEngine).ViewLocationFormats = new string[] { "~/Views/admin/{1}/{0}.cshtml" }; } }
ViewLocationFormatsの書き換える前の中身を見るとViewファイルをASP.NET MVCが探す際の仕組みがなんとなく見て取れます。
以上でやりたいことがだいたいできました。
Please give us your valuable comment