﻿# 如何在專案中透過 .NET CLI 管理專案的本地工具 (.NET Local Tool)

我們在使用 .NET CLI 開發專案的時候，經常會需要安裝一些 .NET 全域工具 (.NET Global Tool)，但是安裝全域工具有個小缺點，那就是這些工具會需要被註冊到 `PATH` 環境變數中，而且經常會有與 .NET SDK 版本不一致的問題。我在授課的時候，學員就遇過好幾次 [dotnet-ef](https://www.nuget.org/packages/dotnet-ef) 套件跟 .NET SDK 版本不一致，導致出現靈異事件的狀況。今天這篇文章，我就來跟大家說一下如何將這些 **.NET 全域工具** 改裝成 **.NET 本地工具**！

![.NET Local Tool](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/176bade1f195723c262d-211576502-8a3d5c47-fdc0-44c0-bf1f-2e6c4d412a2c.png)

### 問題描述

在使用 .NET CLI 提供的命令列工具時，若是微軟官方提供的版本，通常都會有相對應的 .NET SDK 版本。例如今天微軟剛推出 .NET Runtime `6.0.4` 版本，而這個版本的 .NET Runtime 也會隨著 .NET SDK `6.0.202` 一起發佈，還有 Vsual Studio 2022 也同步發行 `17.1.4` 版本，這個版本也同時包含了 .NET SDK `6.0.202`，所以也自然附帶 .NET Runtime `6.0.4` 一起發佈。有時候你不小心升級了 .NET Runtime 你都還不知道！

其實在大部分的情況下，升級 .NET Runtime 是不影響現有 .NET 程式執行的。因為 .NET 應用程式在執行的時候，會自動判斷系統中已安裝的 .NET Runtime 版本，並自動 Roll-Forward 到最新的版本執行，而且 .NET 保證在 Minor 版本更新時一定會向前相容，因此程式不會有任何狀況才對。

但是有些 .NET 重要的開發工具，其實會隨著 .NET SDK 或 .NET Runtime 一起更新，但這些工具並不會內含在 .NET SDK 之中，而是需要額外安裝 NuGet 套件才有的功能，例如以下這些：

-   [dotnet-ef](https://www.nuget.org/packages/dotnet-ef) `v6.0.4`
-   [dotnet-sql-cache](https://www.nuget.org/packages/dotnet-sql-cache) `v6.0.4`
-   [Microsoft.dotnet-openapi](https://www.nuget.org/packages/Microsoft.dotnet-openapi) `v6.0.4`

上述三個 .NET 工具都會隨著 .NET Runtime 一起推出更新版本，但卻不會自動更新 NuGet 套件。另外還有一個 .NET 工具也會推出更新，但是永遠會少一個版本（我不知道為什麼）

-   [dotnet-aspnet-codegenerator](https://www.nuget.org/packages/dotnet-aspnet-codegenerator) `v6.0.3`

上述最常用的 .NET 工具應該就是 [dotnet-ef](https://www.nuget.org/packages/dotnet-ef) 了！

你想想看這種情境：

1.  你在一週之前就先裝好了上課所需的 .NET 工具，假設 [dotnet-ef](https://www.nuget.org/packages/dotnet-ef) 安裝 `v6.0.3` 版本
2.  然後今天看到 Visual Studio 2022 推出新版，然後直接更新，此時 .NET SDK 就被升級到 `v6.0.202`，且 .NET Runtime 是 `v6.0.4` 版本
3.  然後你在執行 `dotnet ef` 相關命令時出現錯誤訊息，你很難從訊息中得知是因為「版本」的問題導致錯誤發生！

你再想想看這種情境：

1.  你在一週之前就先裝好了 .NET SDK `v6.0.201` 且 .NET Runtime 是 `v6.0.3` 版本
    
2.  然後今天看到 Visual Studio 2022 推出新版，但是沒有套用更新！
    
3.  接著你因為需要 [dotnet-ef](https://www.nuget.org/packages/dotnet-ef) 工具，因此直接安裝這套工具（不指定版本）
    
    ```bash
    dotnet tool update --global dotnet-ef
    ```
    
    > 注意：使用 `dotnet tool install` 或 `dotnet tool update` 都可以安裝 .NET 工具，若你已經安裝過此工具，透過 `dotnet tool update` 也可以直接更新版本。
    
4.  此時你安裝的 [dotnet-ef](https://www.nuget.org/packages/dotnet-ef) 將會是 `v6.0.4` 最新版本
    
5.  然後你在執行 `dotnet ef` 相關命令時出現錯誤訊息，你很難從訊息中得知是因為「版本」的問題導致錯誤發生！
    

這個問題我遇過很多次了，所以會特別注意工具版本的問題！

### 解決方案 1：永遠使用最新版本

你只要記得更新 .NET SDK 或 Visual Studio 2022 的時候，記得也要將以下三個套件一併更新：

```bash
dotnet tool update --global dotnet-ef
dotnet tool update --global dotnet-sql-cache
dotnet tool update --global Microsoft.dotnet-openapi
```

> 是的，只有上述這三個版本可能有問題！

另外，我也會特別看一下 [dotnet-aspnet-codegenerator](https://www.nuget.org/packages/dotnet-aspnet-codegenerator) 最新版有沒有更新，有的話就會去更新到最新版。

```bash
dotnet tool update --global dotnet-aspnet-codegenerator
```

> 其實 [dotnet-aspnet-codegenerator](https://www.nuget.org/packages/dotnet-aspnet-codegenerator) 不是很好用，我之前有寫過兩篇文章分享過用法。(1) [如何使用 .NET CLI 快速產生 ASP․NET Core 的 Controllers 與 Views 程式碼](/post/2020/09/09/Create-Controller-and-Views-with-dotnet-aspnet-codegenerator) (2) [如何使用 VSCode 建立 ASP.NET Core 3.1 的 MVC Area 區域](/post/2020/09/12/Create-ASPNET-Core-MVC-Area-using-VSCode-and-dotnet-aspnet-codegenerator)

### 解決方案 2：鎖定 .NET SDK 與 .NET 工具版本

由於少數 .NET 工具有版本的相依性，因此在開發環境鎖定 .NET SDK 與 .NET 工具版本應該是個相當合理的決定，而且若能將版本資訊也加入版控，就可以大幅減少團隊成員之間的工具版本差異，減少靈異事件的發生。

這裡我就進入本次文章的主軸，將 .NET 工具裝在本地端！

1.  我們先建立兩個專案，一個類別庫專案、一個 Web API 專案，外加一個方案檔，最後加入版控
    
    ```bash
    # 建立方案資料夾
    mkdir DotNetLocalToolDemo
    cd DotNetLocalToolDemo
    
    # 建立 .NET 專案：一個類別庫專案、一個 Web API 專案
    dotnet new classlib -n DotNetLocalToolDemo.Lib
    dotnet new webapi -n DotNetLocalToolDemo.Api
    
    # 建立方案檔 (方便讓 Visual Studio 2022 開啟整個方案)
    dotnet new sln
    dotnet sln add ./DotNetLocalToolDemo.Lib/DotNetLocalToolDemo.Lib.csproj
    dotnet sln add ./DotNetLocalToolDemo.Api/DotNetLocalToolDemo.Api.csproj
    
    # Git 版控
    dotnet new gitignore
    git init
    git add .
    git commit -m "Initial commit"
    ```
    
2.  我們希望在這個資料夾使用自己的 .NET SDK 與 .NET 工具版本
    
    ```bash
    # 建立 global.json 檔案
    dotnet new globaljson --sdk-version 6.0.202
    
    # 建立 .config/dotnet-tools.json 檔案
    dotnet new tool-manifest
    
    # 查詢 .NET 版本資訊 (你可以從 Host 區塊找到 Version: 6.0.4 等 .NET Runtime 版本)
    dotnet --info
    
    # Git 版控
    git add .
    git commit -m "Lock .NET SDK and create .NET Local Tool manifest file"
    ```
    
3.  安裝 .NET 工具到 Local 並指定版本安裝
    
    ```bash
    # 注意不要加上 --global 參數就會自動安裝到本地
    dotnet tool update --version 6.0.4 dotnet-ef
    dotnet tool update --version 6.0.4 dotnet-sql-cache
    dotnet tool update --version 6.0.4 Microsoft.dotnet-openapi
    dotnet tool update --version 6.0.3 dotnet-aspnet-codegenerator
    
    # Git 版控
    git commit -am "Install .NET Tool with specific version to Local"
    ```
    
    此時工具的 NuGet 套件名稱與版本號，都會被註冊進 `./.config/dotnet-tools.json` 檔案中！
    
    ```json
    {
      "version": 1,
      "isRoot": true,
      "tools": {
        "dotnet-ef": {
          "version": "6.0.4",
          "commands": [
            "dotnet-ef"
          ]
        },
        "dotnet-sql-cache": {
          "version": "6.0.4",
          "commands": [
            "dotnet-sql-cache"
          ]
        },
        "microsoft.dotnet-openapi": {
          "version": "6.0.4",
          "commands": [
            "dotnet-openapi"
          ]
        },
        "dotnet-aspnet-codegenerator": {
          "version": "6.0.3",
          "commands": [
            "dotnet-aspnet-codegenerator"
          ]
        }
      }
    }
    ```
    

### 執行 .NET 工具

現在我們有了 **.NET 全域工具**，也有 **.NET 本地工具**，那工具要如何執行並選擇版本呢？我們其實有好幾種選擇！

我們以 `dotnet-ef` 為例，你可以有以下三種選擇：

1.  .NET 全域工具可以直接用註冊的命令來執行 (命令本身就是一個可執行檔)
    
    你可以先用 `dotnet tool list --global` 查詢 .NET 本地工具有哪些，然後用以下命令執行工具：
    
    ```bash
    dotnet-ef
    ```
    
2.  .NET 本地工具可以搭配 `dotnet tool run` 來執行此命令
    
    你可以先用 `dotnet tool list` 查詢 .NET 本地工具有哪些，然後用以下命令執行工具：
    
    ```bash
    dotnet tool run dotnet-ef
    ```
    
    也可以簡化成以下命令：
    
    ```bash
    dotnet dotnet-ef
    ```
    
3.  但無論是 .NET 全域工具或 .NET 本地工具，所有命名為 `dotnet-*` 開頭的命令都可以簡化成以下這樣
    
    我們透過 `dotnet` 執行 `dotnet-ef` 命令時，可以簡化成 `dotnet ef` 這樣的用法：
    
    ```bash
    dotnet ef
    ```
    
    > 注意：這種執行方式會自動查找**當前**或**上層目錄**是否有個 `.config/dotnet-tools.json` 檔案，如果有的話，就會優先選擇本地工具版本，否則就會選擇全域工具版本！
    
    你可以故意將 `dotnet-ef` 的 .NET 全域工具版本降低到 `6.0.3` 舊版，然後本地版本安裝 `6.0.4` 新版，使用 `dotnet ef` 就可以看出版本差異了！
    
    ```bash
    dotnet tool uninstall --global dotnet-ef
    dotnet tool update --global --version 6.0.3 dotnet-ef
    
    # 在 DotNetLocalToolDemo 目錄下執行會得到 6.0.4 版本
    dotnet ef --version
    
    # 在其他目錄下執行會得到 6.0.3 版本
    dotnet ef --version
    ```
    

### 注意事項

如果要在一台全新的電腦，剛裝好 .NET SDK 的狀態下執行 .NET 本地工具，第一次執行前要透過 `dotnet tool restore` 命令先還原 NuGet 套件，否則將會無法執行。

```bash
git clone https://github.com/doggy8088/DotNetLocalToolDemo.git
cd DotNetLocalToolDemo

dotnet ef
```

此時你應該會看到以下錯誤：

```
Run "dotnet tool restore" to make the "dotnet-ef" command available.
```

解決方案就寫在訊息中，直接執行以下命令就可以還原套件了：

```bash
dotnet tool restore
```

### 相關連結

-   範例程式：[https://github.com/doggy8088/DotNetLocalToolDemo](https://github.com/doggy8088/DotNetLocalToolDemo)
-   [How to manage .NET tools](https://learn.microsoft.com/en-us/dotnet/core/tools/global-tools?WT.mc_id=DT-MVP-4015686)
-   [Troubleshoot .NET tool usage issues](https://learn.microsoft.com/en-us/dotnet/core/tools/troubleshoot-usage-issues?WT.mc_id=DT-MVP-4015686)
-   [Tutorial: Create a .NET tool using the .NET CLI](https://learn.microsoft.com/en-us/dotnet/core/tools/global-tools-how-to-create?WT.mc_id=DT-MVP-4015686)
-   [Tutorial: Install and use a .NET global tool using the .NET CLI](https://learn.microsoft.com/en-us/dotnet/core/tools/global-tools-how-to-use?WT.mc_id=DT-MVP-4015686)
-   [Tutorial: Install and use a .NET local tool using the .NET CLI](https://learn.microsoft.com/en-us/dotnet/core/tools/local-tools-how-to-use?WT.mc_id=DT-MVP-4015686)
