「C#？Visual Studio要るんでしょ？」からの脱却

# はじめに
2016年に.NET Coreが誕生し、Windowsのプラットフォーム依存がなくなりました。以前書いた記事に対するコメントで「C#？Visual Studio要るんでしょ？」、「開発を支えるツールがあるの？」など、C#がWindows以外で動くことは知っているけど、実用に耐えるのか？を疑問に思っている人が多いと感じました。私が[個人サービス](https://mygkrnk.com/)を開発して得た経験や知見を通じてVisualStudioがなくても充分に開発出来るということを知ってもらえたら嬉しいです。ハンズオン形式でご紹介しますので、よろしければ作成したリポジトリをご覧ください。

https://github.com/ikuosaito1989/dotnetcore_sample

:::note info
本記事は.NET5を対象としています。ご了承下さい。
:::

# 事前準備
.NET Coreを利用するためには.NET SDKと呼ばれる.NETアプリケーションに必要な一連のライブラリとツールが必要です。以下のURLからダウンロードしてください。

https://dotnet.microsoft.com/download


## 環境変数の設定
エラーの原因を探るためにstackoverflow、github issueなど英語の記事を探すことがあります。sdkをインストールするとデフォルトのlocaleが設定され、エラーが日本語で表示されます。日本語では記事が見つかりにくく、トラブルシュートに時間がかかってしまうのでデフォルトのlocaleは`en-us`に設定することをオススメします。`DOTNET_CLI_UI_LANGUAGE `環境変数に`en-us`を設定すれば英語でエラーが表示されるようになります。

## VSCodeの拡張
VSCodeの拡張が充実しているので入れた方が開発体験が上がると思います。最低限必要な拡張機能をインストールすることをオススメします。

https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csharp

C＃のVSCode拡張です。デバッグなどC#にまつわる機能をサポートしています。

https://marketplace.visualstudio.com/items?itemName=k--kato.docomment

`///`と入力するとSummaryのテンプレートを自動生成してくれます。

# プロジェクト作成
https://docs.microsoft.com/ja-jp/dotnet/core/tools/dotnet-new

`dotnet new`コマンドを用いることで用意されたテンプレートに基づいてプロジェクトを作成することが出来ます。`dotnet new`と叩くと、以下のようにconsoleアプリからWEB API、SPAと一通りのアプリケーションテンプレートが利用出来ることがわかります。

```sh
$ dotnet new
Template Name                                Short Name           Language    Tags
-------------------------------------------  -------------------  ----------  ----------------------
Console Application                          console              [C#],F#,VB  Common/Console
Class library                                classlib             [C#],F#,VB  Common/Library
WPF Application                              wpf                  [C#]        Common/WPF
WPF Class library                            wpflib               [C#]        Common/WPF
WPF Custom Control Library                   wpfcustomcontrollib  [C#]        Common/WPF
WPF User Control Library                     wpfusercontrollib    [C#]        Common/WPF
Windows Forms (WinForms) Application         winforms             [C#]        Common/WinForms
Windows Forms (WinForms) Class library       winformslib          [C#]        Common/WinForms
Worker Service                               worker               [C#],F#     Common/Worker/Web
MSTest Test Project                          mstest               [C#],F#,VB  Test/MSTest
NUnit 3 Test Project                         nunit                [C#],F#,VB  Test/NUnit
NUnit 3 Test Item                            nunit-test           [C#],F#,VB  Test/NUnit
xUnit Test Project                           xunit                [C#],F#,VB  Test/xUnit
MVC ViewImports                              viewimports          [C#]        Web/ASP.NET
Razor Component                              razorcomponent       [C#]        Web/ASP.NET
MVC ViewStart                                viewstart            [C#]        Web/ASP.NET
Razor Page                                   page                 [C#]        Web/ASP.NET
Blazor Server App                            blazorserver         [C#]        Web/Blazor
Blazor WebAssembly App                       blazorwasm           [C#]        Web/Blazor/WebAssembly
ASP.NET Core Empty                           web                  [C#],F#     Web/Empty
ASP.NET Core Web App (Model-View-Control...  mvc                  [C#],F#     Web/MVC
ASP.NET Core Web App                         webapp               [C#]        Web/MVC/Razor Pages
ASP.NET Core with React.js and Redux         reactredux           [C#]        Web/MVC/SPA
ASP.NET Core with Angular                    angular              [C#]        Web/MVC/SPA
ASP.NET Core with React.js                   react                [C#]        Web/MVC/SPA
Razor Class Library                          razorclasslib        [C#]        Web/Razor/Library
ASP.NET Core Web API                         webapi               [C#],F#     Web/WebAPI
ASP.NET Core gRPC Service                    grpc                 [C#]        Web/gRPC
```

今回はASP.NET Core Web APIを作成します。作成するには以下のコマンドを入力します。

```sh
$ dotnet new webapi
```

カレントディレクトリを見るとASP.NET Core Web APIのテンプレートが作成されています。

```sh
$ ls
Controllers                  Startup.cs                   dotnetcore_sample.csproj
Program.cs                   WeatherForecast.cs           obj
Properties                   appsettings.Development.json
README.md                    appsettings.json
```

## .NET SDK のバージョンを管理する
.NET CLIコマンドを実行するときに使う .NET SDK のバージョンを定義します。異なるバージョンのプロジェクトを開発する際などに便利な機能です。バージョンを管理するファイルは`global.json`というファイルで以下のコマンドを入力すると作成できます。

```sh
$ dotnet new globaljson
```

すると`global.json`という名前で以下のようなファイルが作成されます。ここで使用するsdkのバージョンを固定することができます。

```json
{
  "sdk": {
    "version": "5.0.403"
  }
}
```

## ソリューションファイルの作成
複数のプロジェクトファイルを整理するためにソリューションファイル（以下、sln）というものがあります。slnは必要なときにコマンドで生成するようになっています。プロジェクト作成で使用した`new`コマンドを利用してslnを作成します。

```sh
$ dotnet new sln
```

これでslnファイルのテンプレートが作成されました。プロジェクトに紐付ける場合はaddコマンドを入力します。以下は先ほど作成したプロジェクトファイルをslnファイルに追加しています。

```sh
$ dotnet sln add dotnetcore_sample.csproj
```

これで先ほど作成したプロジェクトに紐づきました。

## 実行
パッケージを取得します。

```sh
$ dotnet restore
```

プロジェクトを実行します。

```sh
$ dotnet run
```

実行すると<https://localhost:5001>で立ち上がるので`curl`コマンドを叩いて結果を確認してみます。

```sh
$ curl -s -X GET https://localhost:5001/WeatherForecast  | python -mjson.tool
[
    {
        "date": "2021-12-02T23:16:50.986985+09:00",
        "summary": "Warm",
        "temperatureC": -6,
        "temperatureF": 22
    },![スクリーンショット 2021-12-01 午後11.40.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496802/7c1eacb6-274a-4395-1130-d09fea08bb69.png)

    ...
]
```
ちゃんとWEB APIが動くことを確認できました。

ちなみにWEB APIテンプレートはSwaggerも導入されていますので、<https://localhost:5001/swagger/index.html>にアクセスするとswaggerの画面が表示されます。
![スクリーンショット 2021-12-01 午後11.23.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496802/1c802d0f-9543-9649-825a-459d9c96b287.png)

## 監視する
`dotnet watch`コマンドを用いてファイルを監視することで、ファイルが変更されたらビルドをしてくれます。毎回ビルドし直さなくても良くなるので開発効率が上がります。

```sh
$ dotnet watch run
watch : Started
Building...
```

ファイルを変更すると再度ビルドが始まり、変更内容をビルドしてくれます。

```sh
watch : Exited
watch : File changed: dotnetcore_sample/Program.cs
watch : Started
Building...
```

## ステップ実行
プロジェクトファイルが存在するディレクトリでVSCodeを開くと以下のダイアログが表示されます。

![スクリーンショット 2021-12-01 午後11.37.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496802/be892274-ace8-5f0d-88c9-b0af73b1e752.png)
Yesを入力すると.vscodeディレクトリが作成されアタッチするためのファイルが作成されます。F5を押下すると実行され、ブレークポイントを貼ってステップ実行が可能となります。

![スクリーンショット 2021-12-01 午後11.40.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496802/b6b6f3ba-248e-5709-7ae7-8e516414f0b5.png)

これで一通りの開発が出来るようになりました。

#Docker
.NET CoreではDockerが利用出来るようになりました。Dockerを用いることで環境依存を無くなり、AWS、GCP、Azureなどホスティングを選定する選択肢が広がります。AWS FargateやGCP　Cloud Runなどサーバーレスを用いることで従来のホスティングより費用を抑えることが出来るのも魅力です。個人的には、これが一番強力だと感じています。

## ビルド
先ほど作ったWEB APIをビルドします。まずはDockerfileを用意します。DockerfileはMSから公式のサンプルが出ていますので参考にすると良いかもしれません。

https://github.com/dotnet/dotnet-docker


```Dockerfile:Dockerfile
FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build
WORKDIR /app

COPY *.sln .
COPY *.csproj .
RUN dotnet restore
RUN dotnet publish -c release -o /out

FROM mcr.microsoft.com/dotnet/aspnet:5.0
WORKDIR /app
COPY --from=build /app/out ./
ENTRYPOINT ["dotnet", "dotnetcore_sample.dll"]
```

ビルドします。パッケージがキャッシュされるため`--no-cache`をつけています。

```sh
$ docker build --no-cache -t dotnetcore_sample .
```

実行します。

```sh
$ docker run --rm -p 5000:80 dotnetcore_sample
```

これで<localhost:5000>に先ほど作成したWEB APIが立ち上がりました。

# 開発体験（DX）が向上するためのツール
Visual Studioがなくても開発出来ることを説明しましたが、ここからは快適に開発するためのツールを紹介します。

##.NETツール
.NETツールとは、NuGetにアップロードされたコンソールアプリケーションをCLI上で実行出来る仕組みです。nodeでいうnpmをイメージしてもらえるとわかりやすいかもしれません。.NETツールのおかげで、今まで煩雑だった.NET系のCLI処理が簡単にかけるようになりました。.NETツールには、どこでもコマンドが叩けるグローバルツール、既定のディレクトリのみ利用できるローカルツールの２種類があります。ここでは、C#で代表的なコードフォーマッタである`dotnet-format`を例にあげて紹介します。個人的にはDockerの次に魅力を感じています:sunglasses:

.NETツールは以下のリンクから探すことが可能です。

https://www.nuget.org/packages?packagetype=dotnettool

### グローバルツール
グローバルインストールするには`-g`オプションを付けます。`-g`オプションを付けると、どのディレクトリからもコマンドを入力することが可能です。

- インストール

```sh
$ dotnet tool install -g dotnet-format
```

- アンインストール

```sh
$ dotnet tool uninstall -g dotnet-format
```

- アップデート

```sh
$ dotnet tool update -g dotnet-format
```

- 実行

```sh
$ dotnet format
```

### ローカルツール
特定のディレクトリのみ実行する場合は、ローカルツールを使用します。インストールしたツールを管理するマニフェストファイル[^1]が必要です。GitHub ActionsなどのCIで実行する場合は、ローカルツールを利用します。

- マニフェストファイルを作成する

```sh
$ dotnet new tool-manifest
```

コマンドを実行すると`.config/dotnet-tools.json`が追加され、以下のような内容になっているかと思います。

```json:dotnet-tools.json
{
  "version": 1,
  "isRoot": true,
  "tools": {}
}
```

これでマニフェストファイルの雛形が完成しました。次はマニフェストファイルにツールをインストールします。

- インストール

```sh
$ dotnet tool install dotnet-format
```

これでローカルにインストールされ、先ほど作成した`.config/dotnet-tools.json`に`dotnet-format`が追加されています。

```diff
{
   "version": 1,
   "isRoot": true,
+  "tools": {
+    "dotnet-format": {
+      "version": "5.1.250801",
+      "commands": [
+        "dotnet-format"
+      ]
+    }
+  }
 }
```

これでローカルツールを利用する準備が整いました。あとはパッケージを取得し、実行するだけです。

- パッケージを取得する

```sh
$ dotnet tool restore
```

- フォーマットする

```sh
$ dotnet format
```

特定のディレクトリのプロジェクトに対して`dotnet format`が適応されました。

# コードフォーマッター
開発体験を向上するにはコードフォーマッタがあると便利です。.NETツールで紹介した`dotnet-format`を用いて、VSCodeの連携を含めてご紹介します。`dotnet-format`　の使い方は.NETツールの章で説明させて頂きましたので、ここでは説明を省きます。

:::note info
.NET6 SDKからdotnet formatが標準で組込みされるようになりました。.NET6を利用する方は.NETツールでのインストールは不要です
:::

## .editorconfigを作成する
.editorconfigがなくともフォーマットされますが、細かい設定が可能となるので作っておいた方が良いと思います。roslyn[^2]の.editorconfigが公開されているので、この設定を持ってくるのが楽ですし、天下のroslynなので間違いはないかと思います。

https://github.com/dotnet/roslyn/blob/main/.editorconfig

## VSCodeの保存と同時にフォーマットする
Ctrl + Sなどの保存と同時にフォーマットすると非常に開発が楽になります。

-  左下の歯車のアイコン→設定をクリックします

<img width="400" alt="スクリーンショット 2021-12-08 午前0.22.08.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496802/18a0f30f-8b4a-9711-3959-0650db040341.png">

- フォームに「format」と入力します
<img width="400" alt="スクリーンショット 2021-12-08 午前0.23.21.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496802/dd12eda5-43e9-af1c-6490-e034af58ed7b.png">

- Format On Saveにチェックします
<img alt="スクリーンショット 2021-12-08 午前0.24.19.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496802/7a8753cc-edf1-b9b2-31d6-8132b3ad71b9.png">

- フォームに「csharp」と入力します
<img width="400" alt="スクリーンショット 2021-12-08 午前0.26.23.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496802/27f51e6d-de7e-045d-b9ae-bc420c390c64.png">

- settings.jsonで編集をクリックします
![スクリーンショット 2021-12-08 午前0.26.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496802/f28e594d-728a-f817-c65e-fe6135a13e92.png)

- [csharp]に以下の設定を追記します

```diff
"[csharp]": {
+  "editor.defaultFormatter": "ms-dotnettools.csharp",
},
```
これで、保存時にC#のソースコードがフォーマットされるようになりました。


# 静的コード解析
品質の高いコードを書くために、静的解析は必須です。C#にはさまざまな静的コード解析がありますが、ここでは私のオススメする`Roslynator`を紹介します。なぜRoslynatorがオススメかというと、モダンな解析、CLIが充実しており、後述するCIに組み込むことが可能となります。

https://github.com/JosefPihrt/Roslynator

以前は、解析失敗してもexitコードが0で終わるというバグがあったため、CIに組み込む場合は[自作のシェルスクリプト](https://github.com/ikuosaito1989/tools/blob/main/sh/analyze.sh)など書く必要がありましたが、「`dotnet format --check`と同じにして〜〜」と[わがまま](https://github.com/JosefPihrt/Roslynator/issues/759)を言ったところ、改善していただけました:pray:感謝

:::note warn
CLIでの.NET6はまだサポートされていません。issueに上がっており、対応前提で動いてくれてますので気長に待ちましょう（貢献するのもよいかも！）
:::

## インストール
.NETツールをインストールします。

```sh
$ dotnet tool install roslynator.dotnet.cli
```

## パッケージ追加
解析対象のプロジェクトにパッケージを追加します。

```sh
$ dotnet add package Roslynator.Analyzers
```

## 解析
以下のコマンドで解析、修正が出来ます。解析はレベルがあり`--severity-level`を使うと厳密に解析するか指定出来ます。

```sh
# 診断
$ dotnet roslynator analyze dotnetcore_sample.sln

# より厳密な解析
$ dotnet roslynator analyze dotnetcore_sample.sln --severity-level=hidden

# 修正
$ dotnet roslynator fix dotnetcore_sample.sln
```

先ほど作成したプロジェクトを実行すると以下のように解析結果が表示されます。

```sh
$ dotnet roslynator analyze dotnetcore_sample.sln
Load solution 'dotnetcore_sample/dotnetcore_sample.sln'
Done loading solution 'dotnetcore_sample/dotnetcore_sample.sln'
Analyze solution 'dotnetcore_sample/dotnetcore_sample.sln'
Analyze 'dotnetcore_sample' 1/1
  Program.cs(22,17): info RCS1021: Use expression-bodied lambda.
  Startup.cs(32,13): info RCS1021: Use expression-bodied lambda.
  Startup.cs(54,13): info RCS1021: Use expression-bodied lambda.
  Startup.cs(29,1): info RCS1036: Remove redundant empty line.
  Program.cs(12,18): warning RCS1102: Make class static.
Done analyzing solution 'dotnetcore_sample/dotnetcore_sample.sln' in 00:02.30

3 RCS1021 Convert lambda expression body to expression-body.
1 RCS1036 Remove redundant empty line.
1 RCS1102 Make class static.

5 diagnostics found
```

## VSCode拡張

https://marketplace.visualstudio.com/items?itemName=josefpihrt-vscode.roslynator

VSCode拡張を入れると、開発中も解析してくれます。便利ですね〜〜:kissing_closed_eyes:

![スクリーンショット 2021-12-10 午後11.51.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496802/62c94883-204e-65be-a405-8d8706b2f281.png)

# CIを導入する
コードの品質やデプロイを自動化するにはCIの導入が必要です。ここまでご紹介したツールを組み合わせてGithub Actionsを動かすサンプルをご紹介します。Github ActionsでなくともCodeBuildなどどのCIでも同じように動かすことが可能です。

## ワークフローを追加する
mainブランチへのプルリクエストがトリガーとなるワークフローを追加します。github Actionsはlinuxが一番コスパが良いのでubuntuで実行しています。以下の例は、単純にビルド、フォーマット、解析のコマンドを羅列しているだけなので、ここまで記事を見ていただけたらわかると思います:relaxed:

```yaml:.github/workflows/pull_request.yml
name: .NET Core

on:
  pull_request:
    branches: [ main ]

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - name: setup checkout
      uses: actions/checkout@v2
      with:
        submodules: true
    - name: setup .NET Core
      uses: actions/setup-dotnet@v1
      with:
        dotnet-version: 5.0.403
    - name: restore project
      run: dotnet restore
    - name: build project
      run: dotnet build --no-incremental
    - name: restore .NET Tool
      run: dotnet tool restore
    - name: restore .NET Tool
      run: dotnet tool restore
    - name: analyze code
      run: dotnet roslynator analyze --severity-level=hidden
    - name: format code 
      run: dotnet format --check
```

## プルリクエストを出す
プルリクエストを出すと、CIが動きチェックします。下記の例はチェックに失敗しています。
![スクリーンショット 2021-12-10 午後11.07.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496802/91ed9c15-44ac-6b6d-3b12-287313373004.png)

「Details」をクリックし、詳細を確認します。

![スクリーンショット 2021-12-10 午後11.09.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496802/39b6f0b5-58fb-e54f-462e-83befa2514ad.png)

静的コード解析が失敗したことにより、CIが落ちたことがわかります。

このようにCIが失敗したらマージさせないなどコードの品質向上するための仕組みを構築することが出来ました。

# まとめ

以前書いた[記事](https://qiita.com/ikuosaito1989/items/292655de2f038336e33c)ではVisualStudioを使わないとどれだけ不便なのか、それとも問題ないのかをお伝え出来なかったので、一通り開発で必要なものを選別して書かさせていただきました。Windowsに依存するものでなければある程度、同じように開発出来ることがわかっていただけたかと思います。私個人としては、C#がマルチプラットフォームで開発出来ることを夢見てたので、とても感慨深いものがありますし、おそらく同じような方は多いのではないかと思います。とにかく.NET系は破壊的変更が少なく、継続して開発しやすい素晴らしいフレームワークだと思いますのでC#perも他の言語を触れていた人も含め、本記事で興味を持っていただければ幸いです:bow_tone1:

[^1]: [^1]: ローカル アクセス専用の.NETツールを管理するファイル
[^2]: [^2]: C#及びVisual Basic .NETのフリーかつオープンソースのコンパイラ・コード解析API