tech.guitarrapc.cóm

Technical updates

.NET アップグレード アシスタントでCentral Package Managementに移行する

C#(.NET)プロジェクトでNuGetパッケージを管理する方法はいくつかありますが、Visual Studio 2022 17.2以降 (NuGet 6.2以降)で導入されたCentral Package Managementはプロジェクトのパッケージ管理を簡単にする新しい方法です。これを使うと複数のcsprojファイルを持つソリューションで、パッケージとバージョンを一元管理できるため、今後の標準になっていくと予想されます。

.csprojのNuGetパッケージ定義からCentral Package Managementへの移行はこれまで手作業でしたが、Visual Studio 2022と.NET Upgrade Assistant拡張を使うと簡単に移行できるようになったので紹介します。

.NET Upgrade Assistantとは

.NET Upgrade Assistant | Marketplace VisualStudioは、.NETアプリケーションを最新の.NETバージョンにアップグレードするためのツールです。.NET Frameworkから.NET 6+(.NET 9.0含む)へアップグレードや、.NET Coreから.NET 6+(.NET 9.0含む)へのアップグレードもできます。1

先日0.5.793.65096がリリースされCentral Package Managemen移行もサポートされました。うれしいですね。

.NET Upgrade Assistant Now Supports Upgrading to Centralized Package Mangement | .NET Blog

.NET Upgrade Assistantをインストールする

.NET Upgrade AssistantはVisual Studio 2022 17.1以降で利用できます。Visual Studioのメニューバー > Manage Extensions > .NET Upgrade Assistantを検索してインストールします。

image

細かい手順は以下のリンクを参照してください。

.NET アップグレード アシスタントをインストールする

Central Package Managementに移行するとどうなる?

サンプルに次のようなプロジェクトを用意します。

CPMSample
│  CPMSample.sln
│
├─ConsoleApp
│      ConsoleApp.csproj
│      Program.cs
│
└─TestProject1
        TestProject1.csproj
        UnitTest1.cs

Central Package Management移行前

NuGetパッケージはそれぞれのcsprojファイルで参照されています。

  • ConsoleApp.csproj
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
      <OutputType>Exe</OutputType>
    <TargetFramework>net9.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="ConsoleAppFramework" Version="5.3.3">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
    <PackageReference Include="ProcessX" Version="1.5.5" />
  </ItemGroup>

</Project>
  • TestProject1.csproj
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net9.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <IsPackable>false</IsPackable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="coverlet.collector" Version="6.0.2" />
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
    <PackageReference Include="xunit" Version="2.9.2" />
    <PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
  </ItemGroup>

  <ItemGroup>
    <Using Include="Xunit" />
  </ItemGroup>

</Project>

Central Package Management移行後

このプロジェクトをCentral Package Managementに移行すると次のような構成になります。Directory.Packages.propsファイルが追加されています。Central Package Managementはプロジェクトのルートディレクトリに配置されたDirectory.Packages.propsファイルでパッケージとバージョンを一元管理します。2 移行後にcsprojファイルをみると、csprojで指定していたパッケージのバージョン指定がなくなっています。

CPMSample
│  CPMSample.sln
│  Directory.Packages.props
│
├─ConsoleApp
│      ConsoleApp.csproj
│      Program.cs
│
└─TestProject1
        TestProject1.csproj
        UnitTest1.cs
  • Directory.Packages.props
<Project>
  <PropertyGroup>
    <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
    <CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
    <!-- TransitivePinningを無効にするとfalse -->
    <!-- <CentralPackageTransitivePinningEnabled>false</CentralPackageTransitivePinningEnabled> -->
    <NoWarn>$(NoWarn);NU1507</NoWarn>
  </PropertyGroup>
  <ItemGroup>
    <PackageVersion Include="ConsoleAppFramework" Version="5.3.3" />
    <PackageVersion Include="coverlet.collector" Version="6.0.2" />
    <PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
    <PackageVersion Include="ProcessX" Version="1.5.5" />
    <PackageVersion Include="xunit" Version="2.9.2" />
    <PackageVersion Include="xunit.runner.visualstudio" Version="2.8.2" />
  </ItemGroup>
</Project>
  • ConsoleApp.csproj
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net9.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="ConsoleAppFramework">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
    <PackageReference Include="ProcessX" />
  </ItemGroup>
</Project>
  • TestProject1.csproj
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net9.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <IsPackable>false</IsPackable>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="coverlet.collector" />
    <PackageReference Include="Microsoft.NET.Test.Sdk" />
    <PackageReference Include="xunit" />
    <PackageReference Include="xunit.runner.visualstudio" />
  </ItemGroup>
  <ItemGroup>
    <Using Include="Xunit" />
  </ItemGroup>
</Project>

Central Package Managementのメリットと注意点

すべてのcsprojで使っているNuGetパッケージとバージョンがDirectory.Packages.propsに記載されているので、パッケージを簡単に確認できるようになりました。

Directory.Packages.propsファイルのManagePackageVersionsCentrallyPackageVersionに注目してください。ManagePackageVersionsCentrallyはCentral Package Managementを有効にするためのプロパティです。.csprojに直接定義するとそのcsprojだけをCentral Package Managementに移行できますが、アップグレードアシスタントを使うと、Directory.Packages.propsに記載されて複数のcsprojを一括でCentral Package Managementに移行できます。PackageVersionはManagePackageVersionsCentrallyで指定したパッケージのバージョンを一元管理するためのプロパティです。3

Central Package Management移行後、csprojでパッケージのバージョンをVerisonで指定するとエラーになります。csprojでバージョンを指定するには<PackageReference Include="PackageA" VersionOverride="3.0.0" />のようにVersionOverrideを使います。csprojに例外を作るとパッケージバージョン管理が難しくなるので個別指定はおすすめしませんが、例外的にバージョンを固定したい場合に使えます。

.NET Upgrade AssistantでCentral Package Managementに移行する

.NET Upgrade AssistantでCentral Package Managementに移行する手順は次の通りです。

1. Visual Studioでソリューションファイルを開く

Visual Studioでソリューションファイルを開きます。.csprojじゃないので注意してください。

2. 適当なcsprojを右クリック > Upgradeを選択

image

3. メニューからNuGet central package management (CPM)を選択する

Upgrade AssistantのメニューにNuGet upgradesがあります。これを選択します。

image

※ 注意: ソリューションを右クリック > Upgradeを選択しても、Central Package Managementに移行メニューが出ないので注意してください。↓はSolution > Upgradeを選択したときの画面ですが、NuGet upgradesが存在しません。

slnのUpgradeではCentralPackageManagementに移行できない

4. Central Package Managementに移行するプロジェクトを選択する

Central Package Managementに移行するプロジェクトを選択します。可能なら全プロジェクトを選択すると一元管理できていいですね。

image

5. プロジェクトのカスタマイズ

Central Package Managementに移行するプロジェクトを選択すると、プロジェクトのカスタマイズ画面が表示されます。ここでDirectory.Packages.propsの場所を指定します。デフォルトではソリューションファイルと同じディレクトリに配置されます。

また、Enable transitive pinningTransitive Pinningを有効にするか選べます。Transitive Pinningは、推移的なパッケージ(依存しているパッケージが依存しているパッケージ)のバージョンを固定する機能です。Transitive Pinningを有効にすると、推移的な依存関係が暗黙的にトップレベルの依存関係に固定されます。4.NETチームはTransitive Pinning有効を推奨にしています。

Transitive Pinningを有効にすると、NuGetパッケージが参照しているライブラリも更新されるため動作担保できない可能性があります。が、推移的パッケージのバージョンもコントロールされているほうがセキュリティ管理しやすいので、有効にできるといいですね。

image

6. 移行実行

正常に移行できると、次のようにチェックマークがつきます。また、Directory.Packages.propsファイルが作成されます。

image

Dockerビルドとの相性

Dockerビルド時にdotnet buildしている場合、Directory.Packages.propsがないとエラーになります。場合によっては、Dockerビルド開始前にDirectory.Packages.propsをコピーする処理を追加する必要があります。

C#の.csprojごとにDockerビルドすると、Dockerfileにプロジェクトが参照しているcsprojを引っ張る処理が書かれます。ただDirectory.Packages.propsは追加されず、またDockerコンテキストパスよりも上位に配置されていることもあり相性が悪いです。

C#プロジェクト全般に言えるのですが、Dockerビルドするならsln以下のディレクトリ構成ごとDockerコンテキストに持ってきて、まとめてビルドするとシンプルで管理しやすくなります。あるいはホストマシンでdllビルドしてDockerコンテナにコピーする方法もあります。

× これは面倒
1. csprojごとにDockerコンテキストへコピー
2. Directory.Packages.propsをコピー
3. dotnet restore & dotnet build

〇 これが簡単でおすすめ
1. slnの構造ごとDockerコンテキストへコピー
2. dotnet restore & dotnet build

まとめ

これまで手作業でCentral Package Managementに移行していたので、.NET Upgrade Assistantで簡単に移行できるのはとても便利です。これでDirectory.Package.propsを作って、csprojを1つ1つ確認していく手間が省けます。

運用を考えるとOSSライブラリの管理は重要です。Central Package Managementを使ってパッケージのバージョンを一元管理することで、ライセンス情報を一元管理できるので、セキュリティやライセンス管理にも役立ちます。ぜひ快適なNuGetパッケージ管理を試してみてください。


  1. csprojのTargetFrameworkTargetFrameworksを書き換えるだけで済む場合は不要ですが、csprojを触らないという人もいもいますし、SDKスタイルにcsprojを書き直す必要がある場合もあります
  2. .slnと同じパスでなくてもよいですが、.slnファイルと同じディレクトリに配置することが多いです
  3. csprojは引き続きPackageReference Includeです
  4. NPMのpackage-lock.jsonファイルのように依存関係のNuGetバージョンも固定する機能です