地味ですが ASP.NET Core MVC 1.0 では URL ルーティングの仕組みが大きく変わりました。
今までは ASP.NET のルーティングモジュールを使っていましたが、ASP.NET Core 1.0 では新規にミドルウェアとして実装されました。少し挙動が変わっていて、新しい機能も追加されているので調べました。
規約ベースのルーティング
これまで通りに規約ベースのルーティングは使えるようになっていますが、デフォルトパラメータをルーティング URL のテンプレート内に定義できるようになりました。
属性ベースのルーティングでは使えるようになっていた記法ですが、規約ベースでも使えるようになって分かりやすく定義を書けるようになりました。
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); }
ルーティングは属性ベースの方が洗練されていましたが、規約ベースも追いついてきましたね。
エリアを使う場合には MapAreaRoute を使ってルーティングを定義します。パラメータの areaName はコントローラに付ける Area 属性に指定したものと同じものを指定します。
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { app.UseMvc(routes => { routes.MapAreaRoute( name: "area", areaName: "DemoArea", template: "DemoArea/{controller=Home}/{action=Index}/{id?}"); routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); }
ルーティング定義は順番が重要になるので、ジェネリックなテンプレートは最後に書いておかないと詰みます。この辺りは MVC 5 から何も変わっていないです。
属性ベースのルーティング
MVC 5 までは RoutePrefix 属性と Route 属性を使ってルーティングを定義していましたが、Core MVC 1.0 では Route 属性だけを使って定義するようになりました。
[Route("Details/{id:int}")] public IActionResult GetById(int id) { return View(); }
RoutePrefix 属性と同じことをしたい場合には、コントローラ側に Route 属性を付けます。
[Route("Api/Product")] public class ProductController : Controller { [Route("Details/{id:int}")] public IActionResult GetById(int id) { return View(); } }
単純に RoutePrefix を Route に変えるだけで、大体の場合は問題なさそうです。
Core MVC 1.0 の属性ベースのルーティングは IRouteTemplateProvider を実装した属性であれば、Route 以外にも定義できるようになっています。中でも HttpGet/Post などの属性は利用頻度が高いと思います。
[HttpPost("Product/New")] public IActionResult CreateItem() { return Created(); }
Route と HttpPost で 2 つ書く必要なく、単純に 1 つにまとめられるので見た目分かりやすくなりました。
[controller] と [action] トークン
新しく追加された機能として [controller] と [action] トークンがあります。これまで URL ルーティングでは中括弧を使ってプレースホルダを書いてきましたが、このトークンは挙動が特殊です。
説明がしにくいので、具体的な例を挙げてみます。以下のようなコントローラを用意します。
[Route("Api/[controller]") public class ProductController : Controller { [Route("[action]/{id}")] public IActionResult Get(int id) { return View(); } }
この場合に作成されるルーティング定義は /Api/Product/Get/{id} となります。[controller] と [action] はルーティングで処理されるのではなく、MVC 側でコントローラ名とアクション名に置換されて処理されます。
ルーティング側で処理されるものではないので、括弧の種類が変わっているということでした。上の例ではアクションにも Route 属性を付けましたが、コントローラに付けるだけでも問題ないです。
[Route("Api/[controller]/[action]") public class ProductController : Controller { [Route("{id:int}")] public IActionResult Get(int id) { return View(); } }
実際にはパラメータだけはアクションごとに定義したいことが多いと思うので、コントローラでは共通部分だけ括りだして定義することも出来ます。
存在しないルートの扱いが変更
最後になりましたが、Core MVC 1.0 では存在しないルートにアクセスされた場合、例外を投げるのではなく 404 を返すようになりました。
クローラなどで変な URL にアクセスされた時などに MVC 5 では 500 になってしまいました。定義されていないルートで 500 となっていた今までがおかしいので、この変更は大歓迎です。