Microsoft.Build.CentralPackageVersions で NuGet パッケージのバージョンを一元管理する

# 前置き

仕事で勉強中のとあるソリューションのサンプルプロジェクトで、
[Microsoft.Build.CentralPackageVersions](https://github.com/microsoft/MSBuildSdks/tree/main/src/CentralPackageVersions) というツールが使われていました。

Microsoft.Build.CentralPackageVersions を使用することによって、
ソリューション内のプロジェクトで使用する NuGet パッケージのバージョンを一元管理することが出来ます。

Microsoft.Build.CentralPackageVersions は [MSBuild Project SDKs](https://github.com/microsoft/MSBuildSdks) と呼ばれるビルドの構成と拡張に使用されるSDKの1つです。

Qiita には Microsoft.Build.CentralPackageVersions に関する記事は存在しなかったようなので、
今回簡単に使い方を紹介してみたいと思います。

# 使い方例

- 環境
    - Visual Studio Professional 2019 verasion 16.11.7
    - .NET Framework 4.8

サンプルプロジェクトを作成してみます。
１つのソリューション配下に２つのプロジェクトが存在する例とします。

![ソリューション1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/23789/e1afff90-48e8-8ffc-27f5-a61c5a9559ed.png)

まずソリューションアイテムに `Directory.Build.Targets` という名前のファイルを作成し、
以下の内容を記述します。

```xml
<Project>
  <Sdk Name="Microsoft.Build.CentralPackageVersions" Version="2.1.3" />
</Project>
```

Directory.Build.Targets により、各プロジェクト内の プロジェクトプロパティ値を共通化できます。
Directory.Build.Targets に前述の内容を記述することにより、
「Microsoft.Build.CentralPackageVersions」を全プロジェクトで使用する、という設定にすることができます。
（※ [正確には Directory.Build.Targets の対象範囲はソリューションとは無関係なのでご注意を](https://docs.microsoft.com/ja-jp/visualstudio/msbuild/customize-your-build?view=vs-2022#search-scope)）

この時点でソリューションをビルドしてみます。
ユーザーフォルダ内の .nuget フォルダに Microsoft.Build.CentralPackageVersions のパッケージがダウンロードされていることがわかります。
`C:\Users\<yourusername>\.nuget\packages\microsoft.build.centralpackageversions`
（この辺りの仕組みは[こちらのブログ](https://dfederm.com/authoring-msbuild-project-sdks/)内の「Project SDK resolution」での解説がわかりやすかったです）

続いてプロジェクトで使用する NuGet パッケージをインストールします。
パッケージをダウンロードする前に、NuGet のパッケージ管理形式を「`PackageReference`」にする必要があります。

Visual Studio の「ツール > オプション > Nugetパッケージマネージャー > 既定のパッケージ管理形式」を PackageReference にするか、「パッケージの初回インストール時に形式の選択を許可する」をONにします。

![Nuget設定.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/23789/237c6ee6-0441-3c6d-adb8-f73267bfdc42.png)

ソリューションファイルを右クリックし「ソリューションの NuGet パッケージの管理」からパッケージをインストールします。
もしこの時点でエラーウィンドウにエラーが表示されてインストールが開始されなかったら、一度ソリューションを閉じて Visual Studio を再起動してみてから再度インストールを試してみてください。

![Nuget.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/23789/08b6fea0-6468-6275-de5f-386899b3f9b4.png)

PackageReference 形式でインストールしたパッケージは、アイコンが NuGet マークになっています。

![ソリューション.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/23789/20723e97-c229-ee45-a226-9d995399fa2e.png)

続いてソリューションアイテムに `Packages.props` という名前のファイルを作成し、以下の内容を記述します。

```xml
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ItemGroup>
    <PackageReference Update="Newtonsoft.Json" Version="12.0.3" />
  </ItemGroup>
</Project>
```

ここでソリューションをビルドしてみると、以下のようなエラーが発生するはずです。

```
The package reference 'Newtonsoft.Json' should not specify a version.  
Please specify the version in 'C:\Users\<yourusername>\source\repos\CentrallyPackageManage\Packages.props' or set VersionOverride to override the centrally defined version.	
Project2	C:\Users\<yourusername>\source\repos\CentrallyPackageManage\Project2\Project2\Project2.csproj		
```

csproj ファイルにパッケージのバージョン番号が書かれていてるが、
Microsoft.Build.CentralPackageVersions により
Packages.props に記載しているバージョン番号が優先される旨がエラーとして表示されます。

Packages.props ファイルに記載していないパッケージをソリューション配下のいずれかのプロジェクトでインストールした後にビルドすると、同様のエラーが発生します。
新しいパッケージを採用する際は、Packages.props に記述する必要があります。
Packages.props に記述しているパッケージをプロジェクトで使用しない場合は、ビルドエラーは発生しません。

エラーを解消するには、.csproj ファイル内でパッケージのバージョン番号を指定している箇所を削除します。

```xml
<ItemGroup>
    <PackageReference Include="Newtonsoft.Json">
      **** <Version>12.0.3</Version> *** ←削除する
    </PackageReference>
</ItemGroup>
```

ソリューションエクスプローラーでプロジェクトファイルを右クリックし「プロジェクトのアンロード」を行って .csproj ファイルを直接編集します。
編集が完了したらプロジェクトファイルを右クリックし「プロジェクトの再読み込み」を行い、戻します。

## 特定のプロジェクトだけ異なるバージョンのパッケージを使用したい場合

何らかの理由で特定のプロジェクトだけ異なるバージョンのパッケージを使用したい場合は、以下のように対象のプロジェクトの .csproj ファイル内で `VersionOverride` を記述し、バージョン番号を指定することが可能です。

```xml
<ItemGroup>
    <PackageReference Include="Newtonsoft.Json" VersionOverride="13.0.1">
    </PackageReference>
</ItemGroup>
```