しばやん雑記

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

ASP.NET Core MVC の Areas と URL ルーティングの話

そろそろ ASP.NET Core MVC を本格的に使っていこうかと思っているので、これまで ASP.NET MVC 5 でよく使っていた Area を実際に Core MVC で使ってみることにします。

とは言っても、公式のドキュメントに書いてあることで十分ですが、URL ルーティングとの組み合わせの時に少し疑問があったので、その解消も同時に行います。

今の Visual Studio の Tooling だと Area を追加する方法が用意されていないので、手動でディレクトリを作成していく必要があるみたいです。正直かなりめんどくさいです。

ディレクトリ構造は ASP.NET MVC 5 の時と同じです。ビューの解決ルールも同じようです。

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

ただしビューの解決に関しては、Core MVC ではシンプルな方法でカスタマイズ可能になっています。詳細は省きますが AddRazorOptions で ViewLocationFormats を弄ると可能です。

ASP.NET MVC 5 の時は AreaRegistration が必要でしたが、Core MVC ではシンプルにコントローラに Area 属性を付けるだけになってます。全てに付けるのはちょっとめんどくさそうです。

using Microsoft.AspNetCore.Mvc;

namespace AreasDemo.Areas.Admin.Controllers
{
    [Area("Admin")]
    public class HomeController : Controller
    {
        public IActionResult Index()
        {
            return View();
        }
    }
}

これで Area の準備は出来てますが、アクセスするには URL ルーティングを追加する必要があります。

規約ベースで使う

大体の場合は規約ベースの URL ルーティングを追加するだけで問題ない場合が多いと思います。なので、最初にこっちの方法を紹介しておくことにします。

Core MVC では URL ルーティングのテンプレートに area を指定できるようになっているので、Area が存在する場合にはそのルーティングを優先するような書き方をするだけです。

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    app.UseMvc(routes =>
    {
        routes.MapRoute(
            name: "areaDefault",
            template: "{area:exists}/{controller=Home}/{action=Index}/{id?}");

        // Area 名を決めておきたい場合はこっち
        //routes.MapAreaRoute(
        //    name: "adminArea",
        //    areaName: "Admin",
        //    template: "AdminSite/{controller=Home}/{action=Index}/{id?}");

        routes.MapRoute(
            name: "default",
            template: "{controller=Home}/{action=Index}/{id?}");
    });
}

このルーティングを追加して、実際にアクセスするとちゃんと追加したページが表示されます。

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

これまでと同じ使い勝手なので簡単でした。

しかしアクション名を変えたり、ルーティングのテンプレートにパラメータを含めたい場合には、少し使い勝手が悪い挙動となるので、その場合には属性ベースで統一した方が良いと思いました。

属性ベースで使う

属性ベースのルーティングを使う場合には Area 用のルーティングは不要です。あまり情報が見つからなかったのですが、コントローラに Area 名を使う Route 属性を指定しておくと良さそうです。

2 重に定義してるみたいなので、ちょっと気持ち悪いですが我慢します。

using Microsoft.AspNetCore.Mvc;

namespace AreasDemo.Areas.Admin.Controllers
{
    [Area("Admin")]
    [Route("[area]")]
    public class HomeController : Controller
    {
        [Route("")]
        public IActionResult Index()
        {
            return View();
        }

        [Route("sample-page")]
        public IActionResult Sample()
        {
            return View();
        }
    }
}

一部のアクションには特別なルーティングを定義していますが、この場合は area 名が prefix として必ず付くので意図したとおりの挙動となります。

具体的には以下のような URL でアクセス可能となります。

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

個人的には規約ベースのルーティングより属性ベースの方が好きなので、こっちを使っていく予定です。

URL の生成

Area を使っている場合の Tag Helpers を使った URL 生成は asp-area 属性に Area 名を指定するだけです。規約ベースや属性ベースに関係なく、意図したとおりの URL をちゃんと生成してくれます。

<a asp-action="Index" asp-controller="Home" asp-area="Admin">リンクのテスト 1</a>

<a asp-action="Sample" asp-controller="Home" asp-area="Admin">リンクのテスト 2</a>

実際に生成された URL は以下の通りです。ちゃんと生成されています。

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

基本的には問題ないですが、URL は小文字にしておきたいので Routing のオプションを弄って小文字の URL を作成するようにします。

public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddRouting(options =>
    {
        options.LowercaseUrls = true;
    });

    services.AddMvc();
}

これでアクセスし直すと小文字の URL がちゃんと生成されていることが確認できます。

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

これで個人的に ASP.NET Core MVC と Area を使ってやりたいことは実現出来ました。ついでに属性ベースの URL ルーティングを使った時の疑問も解消出来たので、実際に使っていく予定です。

Core MVC に関係するのオプションは結構多いので、いつか時間があるときに調べたいです。