﻿# 使用 Directory.Build.props 自訂方案中所有專案共用的 MSBuild 屬性值

我們在建置 .NET 專案時，有許多可自訂的建置操作是由 MSBuild 的屬性(Properties)所控制的。重要的是要知道如何以及在哪裡設定屬性值，以達到期望的效果。一般來說，你可以在執行 `msbuild.exe` 命令列工具的時候指定屬性值，也可以在專案檔(`*.csproj`)中加入屬性設定，也可以在專案目錄中加入 `Directory.Build.props` 屬性定義檔。今天這篇文章，我就打算來介紹這個 `Directory.Build.props` 檔案的用法，以簡化我們方案中各專案的設定。

![image](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/344769aed64add9784c6-dd21bf80-8c1b-4204-96ad-8e4881d8043c.webp)

> 如果想知道有哪些 MSBuild 屬性可用，可參考 [MSBuild 常見屬性](https://learn.microsoft.com/en-us/visualstudio/msbuild/common-msbuild-project-properties?view=vs-2022&WT.mc_id=DT-MVP-4015686) 文件。

MSBuild 其是一個相當複雜的工具，相對的也非常靈活與強大，這篇文章我不打算講得太複雜，只打算帶入一個情境，幫助大家理解有哪些地方可以透過 `Directory.Build.props` 來客製化專案的建置流程。對於更深入的 MSBuild 設定，可以參考 [Customize your build](https://learn.microsoft.com/en-us/visualstudio/msbuild/customize-your-build?view=vs-2022&WT.mc_id=DT-MVP-4015686) 文件。

首先，你要知道的一個重點是，當你在建置特定專案時，你的 `*.csproj` 裡面就包含了許多 MSBuild 屬性(Properties)與目標(Targets)定義，這些定義有些是從其他地方匯入的。我以 ASP.NET Core 專案為例，你的 `*.csproj` 檔案中，第一行就會匯入特定專案類型的所有屬性與目標：

```xml
<Project Sdk="Microsoft.NET.Sdk.Web">
  ...
</Project>
```

這段定義其實就是幫你匯入 `C:\Program Files\dotnet\sdk\8.0.100\Sdks\Microsoft.NET.Sdk.Web` 目錄下的 `*.props` 與 `*.targets` 檔案，這些檔案就是定義了 ASP.NET Core 專案的所有屬性與目標，其中當然也包含了預設匯入的 namespace 與需要參考的 NuGet 套件。

當我們在建置專案時，除了先載入上述 `<Project>` 匯入的哪些屬性外，還會從當前目錄找尋一個名為 `Directory.Build.props` 的檔案，如果有找到的話，就會載入這個檔案中的屬性定義。這個檔案的位置可以是當前專案目錄，也可以是當前專案目錄的上層目錄，也可以是當前專案目錄的上上層目錄，以此類推。如果你的方案中有多個專案，而你希望這些專案都共用同一組屬性定義，那麼你就可以在方案目錄中加入一個 `Directory.Build.props` 檔案，這樣所有專案都會共用這個檔案中的屬性定義。

瞭解這點之後，我們就可以來介紹一些常見的使用案例了。

### 設定所有專案都要用 SonarAnalyzer.CSharp 套件來進行程式碼分析

假設你的方案中有多個專案，而你希望這些專案都共用同一組 NuGet 套件，那麼你就可以在方案目錄中加入一個 `Directory.Build.props` 檔案，並在這個檔案中加入下列內容：

```xml
<Project>
  <ItemGroup>
    <PackageReference Include="SonarAnalyzer.CSharp" Version="9.17.0.82934">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
  </ItemGroup>
</Project>
```

你沒看錯，只要簡單設定一下，預設你的「每個專案」都會自動加入 [SonarAnalyzer.CSharp](https://www.nuget.org/packages/SonarAnalyzer.CSharp) 套件參考，如此一來你的整個方案下所有專案都不需要再設定 NuGet 參考了，而且都可以透過 SonarAnalyzer.CSharp 套件來進行程式碼分析，是不是方常方便呢！👍

### 設定所有專案都要將 Warnings 視為 Errors

在某些要求高品質的專案，我們需要強制團隊成員認真面對所有的編譯警告(Compiler Warning)，這時候我們可以在 `Directory.Build.props` 檔案中加入 `<PropertyGroup>` 設定，並加入兩個重要的屬性：

```xml
<Project>
  <PropertyGroup>
    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="SonarAnalyzer.CSharp" Version="9.17.0.82934">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
  </ItemGroup>
</Project>
```

### 設定所有專案都要套用一致的程式碼風格

我們可以透過 `.editorconfig` 定義 C# 的程式碼風格，但是如果你的方案中有多個專案，而你希望這些專案都共用同一組程式碼風格，那麼你就可以在方案目錄中加入一個 `Directory.Build.props` 檔案，並在這個檔案中加入下列內容：

```xml
<Project>
  <PropertyGroup>
    <EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
  </PropertyGroup>
</Project>
```

> 詳細介紹可參考 [.NET code style rule options - .NET | Microsoft Learn](https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/code-style-rule-options?WT.mc_id=DT-MVP-4015686) 文件。

### 設定所有專案都要使用相同的 Nullable 設定

我們可以透過 `*.csproj` 定義 C# 的 Nullable 設定，但是如果你的方案中有多個專案，而你希望這些專案都共用同一組 Nullable 設定，那麼你就可以在方案目錄中加入一個 `Directory.Build.props` 檔案，並在這個檔案中加入下列內容：

```xml
<Project>
  <PropertyGroup>
    <Nullable>enable</Nullable>
    <NoWarn>CS8618</NoWarn>
  </PropertyGroup>
</Project>
```

### 設定所有專案都要使用相同的 TargetFramework 版本

我們可以透過 `*.csproj` 定義 C# 的 TargetFramework 設定，但是如果你的方案中有多個專案，而你希望這些專案都共用同一組 Nullable 設定，那麼你就可以在方案目錄中加入一個 `Directory.Build.props` 檔案，並在這個檔案中加入下列內容：

```xml
<Project>
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
  </PropertyGroup>
</Project>
```

### 設定所有專案都要使用相同的程式碼分析器版本

.NET SDK 其實內建許多 Code Analysis (CA) 程式碼分析器規則集，但預設只有啟用一小部分規則而已，如果你希望所有專案都要使用相同的程式碼分析器模式，那麼你就可以在方案目錄中加入一個 `Directory.Build.props` 檔案，並在這個檔案中加入下列內容：

```xml
<Project>
  <PropertyGroup>
    <AnalysisMode>All</AnalysisMode>
    <AnalysisLevel>latest</AnalysisLevel>
    <CodeAnalysisTreatWarningsAsErrors>true</CodeAnalysisTreatWarningsAsErrors>
  </PropertyGroup>
</Project>
```

上述三個屬性的說明如下：

-   `AnalysisMode`
    
    指定要使用的程式碼分析器模式，預設為 `Default`，可選用的值有 `None`、`Minimum`、`Recommended`、`All`。
    
-   `AnalysisLevel`
    
    由於這些規則集都是跟著 .NET SDK 走的，所以你可以指定分析器的版本，或是直接指定 `latest` 直接用最新版即可。
    
-   `CodeAnalysisTreatWarningsAsErrors`
    
    指定是否將程式碼分析器的**警告**視為**錯誤**，預設為 `false`。
    

從 .NET 6 開始，你其實可以忽略 `<AnalysisMode>` 設定，直接使用 `<AnalysisLevel>` 來設定即可。

例如以下設定：

```xml
<AnalysisMode>Recommended</AnalysisMode>
<AnalysisLevel>latest</AnalysisLevel>
```

就可以替換成以下設定：

```xml
<AnalysisLevel>latest-Recommended</AnalysisLevel>
```

> .NET SDK 內建的規則集可以從這份 [AnalyzerReleases.Shipped.md](https://github.com/dotnet/roslyn-analyzers/blob/9.0.0/src/NetAnalyzers/Core/AnalyzerReleases.Shipped.md) 文件查到。

### 總結

有了上述幾個使用案例，各位應該不難發現 `Directory.Build.props` 真的實用的地方，就是可以大幅簡化方案中每個專案的 MSBuild 定義，確保團隊使用一致的設定。若搭配一些程式碼分析器 NuGet 套件，以及程式碼分析器規則集設定，更能夠幫助團隊提升程式碼品質！👍

### 相關連結

-   [Customize your build with extensibility hooks - MSBuild | Microsoft Learn](https://learn.microsoft.com/en-us/visualstudio/msbuild/customize-your-build?view=vs-2022&WT.mc_id=DT-MVP-4015686)
-   [Overview of .NET source code analysis](https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/overview?WT.mc_id=DT-MVP-4015686&tabs=net-8)
-   [AnalyzerReleases.Shipped.md](https://github.com/dotnet/roslyn-analyzers/blob/9.0.0/src/NetAnalyzers/Core/AnalyzerReleases.Shipped.md)
