💨

.NET プロジェクトの設定の共有

2022/06/24に公開3

.NET プロジェクトの設定の共有

ビルドのカスタマイズ に詳しく書かれています。

一つのソリューションに複数のプロジェクトを含める理由

レイヤー分割

三層アーキテクチャでは、アプリケーションを UI を提供するプレゼンテーション層、機能を提供するアプリケーション層、永続化を提供するインフラストラクチャ層の三つに分かれます。

プレゼンテーション層は機能を利用者に提供するのが目的なので、永続化について知る必要はありません。ですので、設計上プレゼンテーション層からインフラストラクチャ層を呼び出してはいけません。ほかにも図にないアプリケーション層からプレゼンテーション層を呼び出したりもいけません。

この分割をどのようにコードに反映するかですが、最も簡単なのがプロジェクトの中の名前空間 (フォルダー) で分割する方法です。

しかし、.NET プロジェクトの場合、同一アセンブリ (プロジェクト) 内のクラスはすべて利用できます。つまり、このできてはいけない呼び出しができてしまうわけです。結果、プレゼンテーション層にアプリケーション層からしか使わないクラスが作られたりしてしまい、レイヤーに分割した意味がなくなります。この状態が進行すると、ただ単に読みにくくメンテナンスしづらいだけの状態になります。

各レイヤーをアセンブリ (プロジェクト) に分ける方法もあります。

この方法では、このできてはいけない呼び出しができません。プロジェクトでの分割は山荘アーキテクチャの設計をそのままコードの世界に持ち込んでいると言えます。

業務分割

レイヤーによる分割のほかに、業務による分割もよく行われます。図書館のシステムでは、書籍・蔵書・職員・貸出などのサブシステムに分割して開発します。(分割の仕方が変だよ! とかは突っ込まないでいただけると...)

これもレイヤー分割の時と同じようにアセンブリ (プロジェクト) を分割しないと、書籍サブシステムが貸出サブシステムを呼び出したりといったカオスな状態になります。

プロジェクト設定の共有

小さいアプリでは気にすることはありませんが、ある程度委譲の規模のアプリは分割して設計することが大事です。.NET ではアセンブリ (プロジェクト) を分割することで、設計のまま素直にコードに落とし込むことができます。

なんとなくわかると思いますが、一つのアプリのためにプロジェクトが 10 を超えることも珍しくありません。

そうなると問題なのが、たくさんあるプロジェクトのプロジェクト設定を合わせることです。個人開発では問題になることは少ないでしょうけど、チーム開発では新しいプロジェクトを作るときに設定をするのを忘れる人が出てきます。

プロジェクトのプロパティ

*.csproj でのプロジェクトのプロパティ設定はソリューションの中では同じになるものが多いです。<LangVersion> のような言語バージョンをプロジェクト単位で変更することはまれでしょう。

ソリューションフォルダーに Directory.Build.props を配置します。

Directory.Build.props
<Project>
  <PropertyGroup>
    <LangVersion>10.0</LangVersion>
  </PropertyGroup>
</Project>

これでこのフォルダー以下のすべてのプロジェクトの言語バージョンは 10.0 になります。

NuGet パッケージバージョン

プロジェクトのプロパティ設定のほかにもう一つ共有したいのが、参照している NuGet パッケージのバージョンです。パッケージはどんどんアップデートされます。脆弱性の修正が含まれていることもあり、アップデートをし続けなければなりません。

テストプロジェクトは NUnit を参照するでしょうし、インフラストラクチャ層のプロジェクトは Dapper を参照するでしょう。プロジェクトごとに参照する NuGet パッケージは異なりますので、単純にすべてのプロジェクトで同じパッケージ参照にはできません。

Directory.Build.tragets を使うとこれを解決できます。

まずはソリューションフォルダに Directory.Build.targets を配置します。

Directory.Build.targets
<Project>
  <ItemGroup>
    <PackageReference Update="Dapper" Version="2.0.123" />
  </ItemGroup>
</Project>

Dapper を利用するインフラストラクチャ層のプロジェクトの csproj にバージョンなしで参照をします。

Infrastructure/Infrastructure.csproj
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Dapper" />
  </ItemGroup>

</Project>

Dapper をアップデートする際は Directory.Build.targets にあるバージョンを上げるだけです。それだけですべての Dapper を利用しているプロジェクトの Dapper のバージョンが上がります。

最後に

Directory.Build.propsDirectory.Build.targets もそれなりに昔からあるようです。知らんかったわ...

Support for NuGet package manager to update packages in Directory.Packages.props file にあるように JetBrains Rider での対応は限定的なんですよね。

とはいえ、Rider 自体、ソリューションのプロジェクトのパッケージ参照をまとめてアップデートをする機能があるので、Directory.Build.targets はないならないで何とかなるんですよねぇ。

Discussion

Mayuki SawatariMayuki Sawatari

Directory.Build.props は MSBuild デフォルトでその置かれたディレクトリー以下に影響しますが、Directory.Packages.props は多分 NuGet の Central Package Management で有効になる (Import対象となる)ものだと思います。

とはいえ結局はどのタイミングで props が取り込まれるかの違いかもということを考えると、後者は目的を絞った専用のやつってことでよさそうです。