NSwagでBuild時にclient側のSwaggerを自動生成する(NSwagの紹介)

## はじめに

この記事はまめコーヒ
[まめコーヒーアドベントカレンダー Advent Calendar 2021](https://qiita.com/advent-calendar/2021/mamecoffee), 15日目の記事です

こちらのコミュニティで、以前C#のコードジェネレーターであるNSwagの紹介しましたので
その時の内容を簡単に記事に起こします

## NSwagについて
C# のコードジェネレータで、API Contollerの内容より、
Swaggerのコードを自動生成してくれます
![webAPI.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/681992/b56780c5-c515-324b-a26a-9258d8d289c5.png)


https://docs.microsoft.com/ja-jp/aspnet/core/tutorials/getting-started-with-nswag?view=aspnetcore-6.0&tabs=visual-studio

## 使用するライブラリ

- NSwag Asp.NET core(NSwag本体)

- NSwag MSBuild(Build時にNSwagを走らせるのに必要)

## 生成されるコードについて

NSwagはコントローラーを見てOpenAPI の仕様書を生成してくれます

例えば、以下のようなContollerがあったときは  

``` C#

namespace NSwag_app.Controllers
{
    [ApiController]
    public class TestController : Controller
    {
        [HttpGet]
        [Route("[controller]")]
        public TestArray Test()
        {
            return new TestArray();
        }
    }

    public class Test
    {   
        public string test{ get; set; }
    }

    public class TestArray
    {
        public Test[] tests { get; set; }
    }
}
```
 下記のようなjsonファイルを出力してくれます

``` C#
     "/Test": {
        "get": {
          "tags": [
            "Test"
          ],
          "operationId": "Test_Test",
          "responses": {
            "200": {
              "description": "",
              "content": {
                "application/json": {
                  "schema": {
                    "$ref": "#/components/schemas/TestArray"
                  }
                }
              }
            }
          }
        }
      },
```

## NSwagでSwaggerを自動生成する

どのようにSwaggerを生成するかはnswag.jsonで管理します
設定項目は多岐にわたっており、一個ずつ確認していくのは大変ですが
[NSwag Studio](https://docs.microsoft.com/ja-jp/aspnet/core/tutorials/getting-started-with-nswag?view=aspnetcore-6.0&tabs=visual-studio#generate-code-with-nswagstudio)という、GUIで設定ができるツールがあるので、
こちらから設定して、設定ファイルをエクスポートするのが早いのではないでしょうか

出力をTypeScriptsで設定した場合、
下記のようなtsファイルが出力されます

``` ts:.ts
export class TestApiFactory {
    private instance: AxiosInstance;
    private baseUrl: string;
    protected jsonParseReviver: ((key: string, value: any) => any) | undefined = undefined;

    constructor(baseUrl?: string, instance?: AxiosInstance) {
        this.instance = instance ? instance : axios.create();
        this.baseUrl = baseUrl !== undefined && baseUrl !== null ? baseUrl : "";
    }

    test(  cancelToken?: CancelToken | undefined): Promise<TestArray> {
        let url_ = this.baseUrl + "/Test";
        url_ = url_.replace(/[?&]$/, "");

        let options_ = <AxiosRequestConfig>{
            method: "GET",
            url: url_,
            headers: {
                "Accept": "application/json"
            },
            cancelToken
        };

        return this.instance.request(options_).catch((_error: any) => {
            if (isAxiosError(_error) && _error.response) {
                return _error.response;
            } else {
                throw _error;
            }
        }).then((_response: AxiosResponse) => {
            return this.processTest(_response);
        });
    }

    protected processTest(response: AxiosResponse): Promise<TestArray> {
        const status = response.status;
        let _headers: any = {};
        if (response.headers && typeof response.headers === "object") {
            for (let k in response.headers) {
                if (response.headers.hasOwnProperty(k)) {
                    _headers[k] = response.headers[k];
                }
            }
        }
        if (status === 200) {
            const _responseText = response.data;
            let result200: any = null;
            let resultData200  = _responseText;
            result200 = TestArray.fromJS(resultData200);
            return result200;
        } else if (status !== 200 && status !== 204) {
            const _responseText = response.data;
            return throwException("An unexpected server error occurred.", status, _responseText, _headers);
        }
        return Promise.resolve<TestArray>(<any>null);
    }
}

```

かつ、APIの引数または、返り値がmodel定義しているときは、
modelも一緒に作ってくれます  

```ts
export interface ITest {
    test?: string;
}
```
## Build時にNSwagを走らせるには
Build時にNSwagを走らせるにはNSwag MSBuildをインストールする必要があります

基本的には、プロジェクトファイルに以下のように書き込んであげれば良いです
Versionはプロジェクに合わせてかえてあげてください

``` C#

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="3.1.16" />
    <PackageReference Include="NSwag.AspNetCore" Version="13.13.2" />
    <PackageReference Include="NSwag.MSBuild" Version="13.13.2">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
  </ItemGroup>
```

## 終わりに
フロントとバックエンドが完全に分離しているようなシステムの場合、
APIの仕様変更がフロンまで修正がいかず、
バグになることがあったり、修正してもメンテが大変だったりしますが、
NSwagのようなコードジェネレーターを使えば、
自動でSwaggerの修正してくれて、かつコンパイルエラーで呼び出し部分のエラー箇所を教えてくれるので、
ぜひ、使ってみると良いのではないでしょうか