読者です 読者をやめる 読者になる 読者になる

ASP.NET Core MVC で、コントローラー名とアクション名とかをインテリセンスに無理矢理対応させる Part.2

前回までのあらすじ

無理矢理でも
勢いで
頑張ろう

mrgchr.hatenablog.com

ActionNameが設定されていたらどうするのか

さて、前回はサンプルアプリの例をそのまま使ったので、アクション名=メソッド名でした。
ASP.NET MVC にはActionName属性により、アクション名を変更できます。例えばアクション名に"-"を入れたい時や、メソッドオーバーロードを解決するときに使ったりするようです。
で、以下のようにアクションにActionName属性を与えると、

[ActionName("about-us")]
public IActionResult About()
{
  ViewData["Message"] = "Your application description page.";

  return View("About"); // アクション名を変えたので"About.cshtml"を明示しないとアクセス時エラーになる
}

前回の@nameofを用いたアクション名指定では、404が返ってきます。
まあ、わざわざアクション名を変更しているわけですし、そういうものです。しょうがない。

今回は前回よりもさらに強引に解決(?)します。

public const string AboutAction = "about-us";
[ActionName(AboutAction)]
public IActionResult About()
{
  ViewData["Message"] = "Your application description page.";

  return View("About"); // アクション名を変えたので"About.cshtml"を明示しないとアクセス時エラーになる
}
@using Home = PracticeWorld1.Controllers.HomeController
<div class="navbar-collapse collapse">
  <ul class="nav navbar-nav">
    <li><a asp-area="" asp-controller="@Home.Name" asp-action="@nameof(Home.Index)">Home</a></li>
    <li><a asp-area="" asp-controller="@Home.Name" asp-action="@Home.AboutAction">About</a></li>
    <li><a asp-area="" asp-controller="@Home.Name" asp-action="@nameof(Home.Contact)">Contact</a></li>
  </ul>
</div>

「constはコンパイル時に決定するから属性でも使えるんやね!」などとうっかり感心してしまいましたが、なんだこれは!通好みすぎる!
(ちなみに今回もpublicでないとエラーになります)
一応、AboutAction文字列経由でインテリセンスが利くようにはなりました。目的が達成できたとは言え、苦笑い必至です。

他の解決法

そもそもなぜActionNameなんて変更するのさ、という問題を考える必要があるようです。
その理由はRoutingが主な理由だと思うのですが、サンプルコードのRoutingは、Startup.csに下記のように定義されています。

//Startup.cs

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

これから分かるように、"{controller}/{action}/..."とアクション名がURLとして利用されています。
サンプルコードではアクション名が * URLの一部 * コントローラーのメソッド名 * デフォルトのView名 と一致している状態です。
このこと自体に異論はありませんが、URLにハイフンを入れようとしたりするとアクション名を変えたくなるのもまあ、理解できます。
が、上記のように私にとっては望ましい結果にはなっていません。

Routingで解決してもいいじゃないのさ

「URLにハイフンを含めたい」とか「URLをいい感じにしたい」くらいであれば、ルーティングを変えてしまえばいいじゃないのさ。と強く思います。
幸い、ASP.NET Core MVC では、属性ベースのRoutingが強化されていて、

[Route("[controller]/contact-us")]
public IActionResult Contact()
{
  ViewData["Message"] = "Your contact page.";

  return View(); //Action名は変えていないので、"Contact.cshtml"を暗黙的に利用
}

上記のようにRoute属性を与えることで、上記のRazorは、asp-action="@nameof(Home.Contact)"のままで"Home/contact-us"へのリンクとしてレンダリングされます。
また、"Home/Contact"は404を返すようになります。
さらに、HttpGetなどとも統合できるようになったとのことです。便利。

[HttpGet("[controller]/contact-us")]
public IActionResult Contact()
{
  ViewData["Message"] = "Your contact page.";

  return View(); //Action名は変えていないので、"Contact.cshtml"を暗黙的に利用
}

個人的には、ActionNameはできるだけメソッド名と一致させたいと思っているので、こちらの方が好ましいですね。

終わりに

確かに、コントローラーにpublic staticなプロパティを生やしたりして、見た目カッコいいとはいいがたいですが、VisualStudioのインテリセンスを利用出来のは補って余りある利益になると思っています。
ASP.NET 5が発表された直後のサンプルコードには、@nameofを用いたアクション名指定があったのですが、いつのまにやら文字列ハードコーディングに戻っていました。
MSの人も「これはダサい」と思ったのでしょうね。