C# (.NET Core 5.0) Web API CRUD実装

# 概要
.NET Core 5.0 Web APIサーバを実装して、MySQL接続&CRUD実装をおこなう。

# バージョン
|Version|
|---|
|.NET Core 5.0|
|MySQL 8.0.22|

# 簡易設計・実装
## 簡易設計
### MySQL テスト用DB
testデータベースを作成し、以下の2つのテーブルを作成する。

helloテーブル

|Field|Type|Null|Key|
|---|---|---|---|
|id|int|NO|PRI|
|name|varchar(128)|NO||

hello_itemテーブル

|Field|Type|Null|Key|
|---|---|---|---|
|id|int|NO|PRI|
|name|varchar(128)|NO||

### API I/F

|Resource path|HTTP Method|Request Body|Response Body|
|---|---|---|---|
|/hello|GET|n/a|`[{"id": 1, "name": ""}]`|
|/hello|POST|`{"id": 1, "name": ""}`|`{"id": 1, "name": ""}`|
|/hello/{id}|PUT|`{"name": ""}`|`{"id": 1, "name": ""}`|
|/hello/{id}|DELETE|n/a|`{"message": "ok"}`|

## 設定値の定義
### パッケージ
MySQLの8系を利用するとEntityFrameworkCoreの5系では未対応のため、3系を利用する。

```bash
<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net5.0</TargetFramework>
    <RootNamespace>http_api</RootNamespace>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="5.0.0" />
    <PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.10" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.10" />
    <PackageReference Include="MySql.Data.EntityFrameworkCore" Version="8.0.22" />
    <PackageReference Include="Swashbuckle.AspNetCore" Version="5.6.3" />
  </ItemGroup>

</Project>
```

### 接続情報
※ 本ドキュメントはMySQL接続してCRUDをおこうことが目的のため、Secret情報の考慮はしていません。
appsettings.jsonに接続情報を追加する。

```json
"ConnectionStrings": {
    "DefaultConnection": "Server=127.0.0.1;Database=test;User=root;Password=root;"
}
```

## Entityの実装
定義したテーブルのEntityクラスを実装する。

Hello.cs

```c#
public class Hello
{
     public long Id { get; set; }   
     [Required]
     [StringLength(100, ErrorMessage = "Name is within 100 length")]
     public string Name { get; set; }
}
```

HelloItem.cs

```c#
public class HelloItem
{
     public long Id { get; set; }   
     [Required]
     [StringLength(100, ErrorMessage = "Name is within 100 length")]
     public string Name { get; set; }
}
```

## DbContextの実装
Microsoft.EntityFrameworkCoreのDbContextを継承して実装する。 
OnModelCreating関数のBuilderでどのEntityがどのテーブルにマッピングするか定義できるので利用する。

[Microsoft.EntityFrameworkCore ModelBuilder Class](https://docs.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.modelbuilder?view=efcore-5.0)

```c#
public class MysqlContext : DbContext
    {
        public MysqlContext(DbContextOptions<MysqlContext> options) : base(options)
        {
            Console.WriteLine("Generated MysqlContext");
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<HelloItem>().ToTable("hello_item");
            modelBuilder.Entity<Hello>().ToTable("hello");
        }

        public DbSet<Hello> Hellos { get; set; }

        public DbSet<HelloItem> HelloItems { get; set; }
    }
```

## サーバのServiceCollectionの実装
実装したMysqlContextの接続数は1つとして、1つのConnectionを使い回すようにする。 
Startup.csのDbContextPoolでMysqlContextをAddする。

```c#
public void ConfigureServices(IServiceCollection services)
{
   ...
   Console.WriteLine(Configuration.GetConnectionString("DefaultConnection"));
   services.AddDbContextPool<MysqlContext>(opt =>
      opt.UseMySQL(Configuration.GetConnectionString("DefaultConnection")));
   ...
}
```

## Controllerの実装
HelloテーブルのCRUDを実装する。
先ほど実装したMysqlContextをSingletonインスタンスで保持するようにDIする。
MysqlContextのコンストラクタに `Generated MysqlContext"` とログを入れているので、期待値は一度だけ出力されていれば良い。

```c#
public class HelloController : ControllerBase
{
    private readonly MysqlContext _mysqlContext;
    
    public HelloController(MysqlContext mysqlContext)
    {
        _mysqlContext = mysqlContext;
        Console.WriteLine("Generated HelloController");
    }

    [HttpGet]
    public List<Hello> Get()
    {
        var list = _mysqlContext.Hellos.ToList();
        return list;
    }

    [HttpPost]
    public Hello Post(Hello hello)
    {
        _mysqlContext.Hellos.Add(hello);
        _mysqlContext.SaveChanges();
        return hello;
    }
    
    [HttpPut("{id}")]
    public Hello Put(long id, Hello hello)
    {
        hello.Id = id;
        _mysqlContext.Hellos.Update(hello);
        _mysqlContext.SaveChanges();
        return hello;
    }
    
    [HttpDelete("{id}")]
    public IDictionary<string, string> Delete(long id)
    {
        var hello = new Hello();
        hello.Id = id;
        _mysqlContext.Hellos.Remove(hello);
        _mysqlContext.SaveChanges();
        
        IDictionary<string, string> map = new Dictionary<string, string>();
        map.Add("message", "ok");
        return map;
    }
}
```

## サーバの起動
実装したAPIサーバを起動する。
バイナリにするまでもないので、runで実行する。

```bash
$ dotnet run
Building...
Server=127.0.0.1;Database=test;User=root;Password=root;
info: Microsoft.Hosting.Lifetime[0]
      Now listening on: http://[::]:8080
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Development
```

# 動作確認
## POST
クライアントのログ。

```bash
$ curl -i -X POST \
  -H "Content-Type: application/json" \
  -d "{\"name\": \"test_data01\"}" \
  http://localhost:8080/hello

HTTP/1.1 200 OK
Date: Thu, 06 May 2021 04:00:39 GMT
Content-Type: application/json; charset=utf-8
Server: Kestrel
Transfer-Encoding: chunked

{"id":24,"name":"test_data01"}
```

サーバのログ。
サーバ起動後の初回リクエストにおいて、MySQLのSingletonインスタンスが生成されることでMySQL接続までの時間がレイテンシに乗っていることがわかった。サーバ起動時にMySQLのConnectionをはることができないかは調査が必要。

```bash
Generated MysqlContext
Generated HelloController instance
```

## GET
クライアントのログ。

```bash
$ curl -i -X GET http://localhost:8080/hello

HTTP/1.1 200 OK
Date: Thu, 06 May 2021 04:03:54 GMT
Content-Type: application/json; charset=utf-8
Server: Kestrel
Transfer-Encoding: chunked

[{"id":1,"name":"string"}, {"id":24,"name":"test_data01"}]
```

サーバのログ。
MysqlContextのログは出力されていないので、使いまわすことができている。

```bash
Generated HelloController instance
```

## PUT
クライアントのログ。

```bash
$ curl -i -X PUT \
   -H "Content-Type: application/json" \
   -d "{\"name\": \"test_data01_updated\"}" http://localhost:8080/hello/24

HTTP/1.1 200 OK
Date: Thu, 06 May 2021 04:06:09 GMT
Content-Type: application/json; charset=utf-8
Server: Kestrel
Transfer-Encoding: chunked

{"id":24,"name":"test_data01_updated"}
```

サーバのログ。

```bash
Generated HelloController instance
```

## DELETE
クライアントのログ。

```bash
$ curl -i -X DELETE http://localhost:8080/hello/24

HTTP/1.1 200 OK
Date: Thu, 06 May 2021 04:07:18 GMT
Content-Type: application/json; charset=utf-8
Server: Kestrel
Transfer-Encoding: chunked

{"message":"ok"}
```

サーバのログ。

```bash
Generated HelloController instance
```

# 所感
サーバ起動時にMySQLに接続しておきたい。
サーバ起動条件の設定を少し調べて、条件満たさない場合のサーバシャットダウン処理等はまた別ドキュメントでまとめていきたい。