C#
ASP.NET_MVC

ASP.NET MVCのMapRouteとMapMvcAttributeRoutesを併用するとどうなるか

結論

  • RouteConfigの設定の順序によっては併用にならない。
  • Route属性の指定があればそちらでルーティングされ、MapRouteの方は使用されない。
  • Route属性の指定がなければMapRouteのルールが指定される。

前提知識

ASP.NET MVCでWebアプリのプロジェクトを作った時、デフォルトのルーティングルールはApp_Start/RouteConfig.csに書かれています。内容は以下の通り。

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

            routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
            );
        }
    }

しかしこれは柔軟性が低いです。まずidはすべてのハンドラに必要ではありません。しかしこのままではすべてのハンドラでid付きでアクセスできてしまいます。そのような違いをMapRouteをモリモリ追加していくことで表現できるんですが複雑で読みづらくなります。デフォルトのまま運用しているところも多いのでは。

もちろんこれだけでなく、属性ベースのルーティングが採用できます。以下の通り。

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

            routes.MapMvcAttributeRoutes();
        }
    }

MapMvcAttributeRoutesを指定すると、Route属性やRoutePrefix属性が指定でき、パスを任意のパラメータとして指定できるなどとても便利です。

というわけで属性だけでいいじゃんと思うんですが、これを併記したらどうなるかを調べました。なんとなく両方のパスでアクセスできるんじゃないかと考えたんですがどうなるでしょうか。

パターン1

MapRouteの後にMapMvcAttributeRoutesをコール。

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

            routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
            );
            routes.MapMvcAttributeRoutes();
        }
    }
  • MapRouteは有効
  • Route属性が付いている場合はどちらで設定したパスでもアクセス不可
  • RoutePrefix属性は無視

というわけで併用できません。

ちなみにRazorの@Html.ActionLink()はアクセス不可でも動いてくれます。MapRouteの方で。だからなんだという感じですが。

パターン2

MapRouteより先にMapMvcAttributeRoutesをコール。

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

            routes.MapMvcAttributeRoutes();
            routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
            );
        }
    }
  • MapMvcAttributeRoutesは有効
  • Route属性がない場合のみMapRouteが有効
    • Route属性がある場合はMapRouteのルールではアクセス不可
    • RoutePrefix属性のみが付いている場合はRoutePrefix属性は無視される

併用できました。Route属性の有無でどちらが適用されるか変わります。片方しか適用されないのは理想的ですね。ただしRoute属性なし,RoutePrefix属性のみだとRoutePrefix属性が無視されるので、うっかりRoute属性を省略するとPrefix通りにアクセスできません。
@Html.ActionLink()もちゃんと動いてくれます。

上記の通りできるとはいえ、併用すると書く場所が発散しちゃうのでできるだけMapRoute側をシンプルに保つなどの工夫が必要です。

おまけ

調べるきっかけになった記事。
https://exceptionnotfound.net/attribute-routing-vs-convention-routing/