[メモ] ルーティング

# はじめに
ASP.NET Core のルーティングについて調べたときのメモです。

# 環境
```bash
$ dotnet --version
3.1.401
```
# ルーティングを使えるようにする
ミドルウェアパイプラインに UseRouting と UseEndpoints を追加する。
(デフォルトで追加されるので、あまり気にする必要ない)

```c#:Startup.cs
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseRouting();
    app.UseEndpoints(endpoints =>
    {
        // コントローラーで定義したアクションをエンドポイントに追加する
        endpoints.MapControllers();
    });
}
```
* 備考  
 * UseRoutingではSetEndpointを使用してエンドポイントをHTTPコンテキストに設定する  
 * UseEndpointsではGetEndpointを使用してエンドポイントの処理を実行する  
https://docs.microsoft.com/ja-jp/aspnet/core/fundamentals/routing?view=aspnetcore-3.1#aspnet-core-endpoint-definition

# ルーティングの定義方法
ASP.NET Coreでルートを定義する方法は二つある。
[Attribute routing vs conventional routing](https://docs.microsoft.com/en-us/aspnet/core/mvc/controllers/routing?view=aspnetcore-3.1#mixed-routing-attribute-routing-vs-conventional-routing)

* 属性ルーティング - REST APIを構築する場合に使う
* 従来のルーティング - ブラウザ用のhtmlページを提供する場合に使う (こっちは自分には必要無いので今回は調べない)

# ルートの結合
コントローラに設定したルートとアクションに設定したルートは結合される。
https://docs.microsoft.com/en-us/aspnet/core/mvc/controllers/routing?view=aspnetcore-3.1#combining-attribute-routes

```c#
using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("products")]
public class ProductsApiController : ControllerBase
{
    [Route] // e.g. https://localhost:5001/products/
    [Route("{id}")] // e.g. https://localhost:5001/products/100
    public ActionResult<string> GetProduct(int? id)
    {
        return ControllerContext.HttpContext.Request.Path.ToString();
    }
}
```

ただし、アクションのルートがスラッシュ(/)またはチルダとスラッシュ(~/)から始まる場合は結合されない。

```c#
using Microsoft.AspNetCore.Mvc;

[Route("Home")]
public class HomeController : Controller
{
    [Route("Index")] // e.g https://localhost:5001/Home/Index
    [Route("/Index")] // e.g https://localhost:5001/Index
    [Route("/")] // e.g https://localhost:5001/
    public ActionResult<string> Index()
    {
        return ControllerContext.HttpContext.Request.Path.ToString();
    }

    [Route("About")] // e.g https://localhost:5001/Home/About
    [Route("~/About")] // e.g https://localhost:5001/About
    public ActionResult<string> About()
    {
        return ControllerContext.HttpContext.Request.Path.ToString();
    }
}
```
* [RouteAttribute](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.routeattribute?view=aspnetcore-3.1)について
 * HTTPメソッドは区別されない。マッチしたアクションが呼び出される。
 * アクションが処理するHTTPメソッドを指定するには[GetAttribute](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.httpgetattribute?view=aspnetcore-3.1)などを使う。

# ルートパラメータ
一つのルートセグメントの中に複数のルートパラメータを設定することができる。
ただし、各ルートパラメータはリテラルで区切る必要がある。

```
{controller=Home}{action=Index} <- 区切られていないのでダメ
{controller=Home}abc{action=Index} <- リテラル'abc'で区切られているのでおk
```
* セグメント・・・ パス区切り(/)で区切られた範囲。 
 `https://localhost:5001/weatherforecast/{a}b{c}`ならセグメントは`weatherforecast`と`{a}b{c}`の2つ。

セグメントの中にオプションパラメータ(?付きのパスパラメータ)を設定する場合は、区切り文字としてドット(.)を使う。(他の文字は使用できない)  

```
https://localhost:5001/weatherforecast/{filename}.{ext?} <- おk
https://localhost:5001/weatherforecast/{filename}-{ext?} <- '.'じゃない('-'区切りになっている)のでダメ
```

# ターミナルミドルウェア
ルートの照合を行うミドルウェアのこと。
ランタイムが提供するルーティング機能を使用せずに実装することは推奨されない。
ルートの照合処理の実装は相当の手間が掛かる。
Map, MapWhen と組み合わせることで、ルートの照合処理とミドルウェアの機能を分離した方が良い。

* ターミナルミドルウェアについての説明とMapメソッドを使用した実装例
 * [ターミナルミドルウェアとルーティングの比較](https://docs.microsoft.com/ja-jp/aspnet/core/fundamentals/routing?view=aspnetcore-3.1#comparing-a-terminal-middleware-and-routing)
 * [MapHealthChecks](https://github.com/dotnet/aspnetcore/blob/c21841f52e066400aae85a776f7b901fdca904e3/src/Middleware/HealthChecks/src/Builder/HealthCheckEndpointRouteBuilderExtensions.cs#L16)

# 参考
[NOTE: You may only have one optional parameter per route, and that optional parameter must be the last parameter.](https://exceptionnotfound.net/asp-net-core-demystified-routing-in-mvc/)