Minimal API 事始め

## はじめに

C#の簡易アプリが必要になったためRubyのSinatraやPHPのSlim、Flightの用に使える物はないか探してみたら、Minimal APIというものがあるようだったので動かした備忘録になります。
あくまでその時実装した内容についてのまとめになりますので、実運用で必要な機能（Asyncなど）は省いていますのでご了承ください。
まとめようと思ったきっかけは、Postのパラメータの取得方法についてのまとまった記事が見つからなかったためです。（動かすのに1日かかりました）

## 前提
- .NET7
- Ubuntu20.04
- MySQL8.0

## 準備

.NETのインストールとMinimal APIの初期化はだいたいドキュメントの通りです

```
wget https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
sudo dpkg -i packages-microsoft-prod.deb
rm packages-microsoft-prod.deb
```

Minimal API 初期化
```
dotnet new web -o SimpleApi
```

デフォルトで諸々生成されますが、コードの中心になる物は `Program.cs` になります。

```
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();
```
Rootアクセスで `Hello World!` を返すごくシンプルなAPIです。
上記に変更を加えつつ解説します。

## RESTful API

`GET` 以外に `POST` `PUT` `DELETE` にも対応しています。その場合エントリポイントになるのは以下の通り。

```
app.MapPost("/users", (Users user) => 何かしらの処理);
app.MapPut("/users/{id}", (int id) => 何かしらの処理);
app.MapDelete("/users/{id}", (int id) => 何かしらの処理);
```
~~URLからパラメータを取るPutやDeleteに対し、迷ったのがPostの処理で、この場合URLと対応する名前でClassまたはRecord型のオブジェクトでなければ受け取れません。
https://learn.microsoft.com/ja-jp/aspnet/core/tutorials/min-web-api?view=aspnetcore-7.0&tabs=visual-studio
のチュートリアルを見る限り、URLの名前の一部が含まれていれば動くようですが、対応させておいた方が無難かもしれません。
Record型ですと以下のように設定します。~~

訂正。パラメータ名と型があっていれば受け取ることが出来るようです。

```
record User(int user_id); // 必要なパラメータを列挙
record Characters(int[] chara_ids); // 配列を取得する場合
```

## DB

MySQLアクセスでやったことは以下の通りです。OR/Mなどもありますが、今回は特にOR/Mなど使わずDapperを使用しました。

```
dotnet add package MySql.Data
dotnet add package Dapper
```

接続のための設定は大体以下の通り。

`appsettings.Development.json`
```
{
  （略）
  "ConnectionStrings": {
    "WebApiDatabase": "server=mysql; database=your_db_host; user=your_user; password=your_password; SslMode=none;"
  }
}
```

`Development` で分ける場合、環境変数として
`ASPNETCORE_ENVIRONMENT` に `Development` を設定してください。
何も設定しない場合 `appsettings.Production.json` がロードされます。

上記に対して `Program.cs` で
```
var connectionString = builder.Configuration.GetConnectionString("WebApiDatabase");
var connection = new MySqlConnection(connectionString);
connection.Open();
var selectUserSql = "SELECT COUNT(*) FROM `users` WHERE `user_id` = @UserId;";
var userCount = connection.Query<int>(selectUserSql, new {UserId = userId}).FirstOrDefault<int>();

var transaction = connection.BeginTransaction();
try {
    var insertLogSql = "INSERT INTO `log` (`user_id`) VALUES (@UserId);";
    connection.Execute(insertLogSql, new {UserId = userId});
    transaction.Commit();
} catch (Exception){
    transaction.Rollback();
    return Results.StatusCode(500);
} finally {
    connection.Close();
}
```

Dapperの詳細な説明は省きます。

## CORS

CORSの設定例は以下の通り。

```
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCors(options =>
{
    options.AddDefaultPolicy(policy => {
            policy.WithOrigins("http://localhost", "https://*.your_domain") // サブドメインはワイルドカード可
                .SetIsOriginAllowedToAllowWildcardSubdomains()
                .AllowAnyHeader()
                .AllowAnyMethod();
    });
});
app.UseCors();
```

## WebサーバからTCP接続する場合

```
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
    ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});
```

## 戻り値

戻り値の設定は以下の簡略設定があります。
```
var response = new Dictionary<string, dynamic> {
    { "results", results },
};
return Results.Ok(response); //-> 200

return Results.StatusCode(500); //-> 500
return Results.BadRequest("error msg"); // -> 400
```

ただし上記は全てjsonの戻り値になるため、json以外で返却したい場合は独自でレスポンス設定を記述する必要があります。

## Dockerで設定する

Dockerで一括立ち上げしたい場合は以下の通り。

```
## create out dir
FROM mcr.microsoft.com/dotnet/sdk:7.0-focal AS build
WORKDIR /home/your_home/app
RUN dotnet restore
RUN dotnet publish -c Release -o out

## create csh image
FROM mcr.microsoft.com/dotnet/aspnet:7.0-focal AS runtime
WORKDIR /home/your_home/app

# copy out dir
COPY --from=build /home/your_home/app/out ./out

# set entrypoint
WORKDIR /home/your_home/app/out
# port3000で起動する設定
ENV ASPNETCORE_URLS http://*:3000
ENTRYPOINT ["dotnet", "your_app_name.dll"]
```

ビルドコンテナとランタイムコンテナが分かれている点が注意。