﻿# 使用 .NET 8.0 與 Azure Functions Core Tools 開發與部署函式應用程式

我一直以來都是 Serverless 架構的信徒，因為實在是不太想自己管伺服器，我從 2018 年就開始採用 Azure Functions 技術，到目前為止歷經了 4 個大版本，到現在終於算是穩定些了。不過即便到目前為止，你要是照著官方文件下去設定，肯定會在很多不同的環節鬼打牆，地雷一直都沒有少過。這篇文章我將介紹如何使用 [.NET 8.0](https://dotnet.microsoft.com/en-us/?WT.mc_id=DT-MVP-4015686) 與 [Azure Functions Core Tools](https://github.com/Azure/azure-functions-core-tools) 來開發函式應用程式，並說明如何部署應用程式到 Azure App Service 使用 Consumption Plan (`Y1`) 方案來執行！

![image](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/f2f6bd59d513dfa871eb-ad058af3-3294-4943-83a1-849dbde16454.webp)

### 安裝 Azure Functions 開發環境

1.  安裝或升級 [Azure Functions Core Tools](https://github.com/Azure/azure-functions-core-tools?tab=readme-ov-file) 工具
    
    若要在 VS Code 之中進行偵錯，那麼你一定要安裝 `x64` 的版本！
    
    ```bash
    choco install azure-functions-core-tools --params "'/x64:true'" -y
    ```
    
    升級的時候如果沒加上 `--params "'/x64:true'"` 參數，那麼就會安裝 `x86` 的版本。
    
    ```bash
    choco upgrade azure-functions-core-tools --params "'/x64:true'" -y
    ```
    
    查詢版本
    
    ```bash
    func --version
    ```
    
    ```
    4.0.5571
    ```
    
2.  安裝 [VS Code](https://code.visualstudio.com/) 編輯器
    
3.  安裝與 Azure Functions 相關的 [VS Code](https://code.visualstudio.com/) 擴充套件
    
    [C# Dev Kit](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csdevkit)
    
    [Azure Functions](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azurefunctions)
    
    [Azure Tools](https://marketplace.visualstudio.com/items?itemName=ms-vscode.vscode-node-azure-pack)
    
    [Azurite](https://marketplace.visualstudio.com/items?itemName=Azurite.azurite)
    
    [.NET Core Extension Pack](https://marketplace.visualstudio.com/items?itemName=doggy8088.netcore-extension-pack)
    
    [REST Client](https://marketplace.visualstudio.com/items?itemName=humao.rest-client)
    

### 建立 Azure Functions 應用程式

1.  建立 Azure Functions 應用程式
    
    ```bash
    func init linebots --worker-runtime dotnet-isolated
    ```
    
    使用 VS Code 開啟 `linebots` 資料夾
    
    ```bash
    code linebots
    ```
    
    第一次開啟可以讓 VS Code 自動進行最佳化設定
    
    ![Detected an Azure Functions Project in folder "linebots" that may have been created outside of VS Code. Initialize for optimal use with VS Code?](https://stwillblogassets.blob.core.windows.net/files/images/external/github.com/279394bda315315d0ac9-f5a7fdfb-3158-49cf-a504-63e4e3881411.jpg)
    
2.  建立一個新的 HTTP Trigger 函式
    
    ```bash
    func new --template "Http Trigger" --name FirstHttpTrigger
    ```
    
    這個命令會產生一個 `FirstHttpTrigger.cs` 程式，程式內容如下：
    
    ```csharp
    using System.Net;
    using Microsoft.Azure.Functions.Worker;
    using Microsoft.Azure.Functions.Worker.Http;
    using Microsoft.Extensions.Logging;
    
    namespace linebots
    {
        public class FirstHttpTrigger
        {
            private readonly ILogger _logger;
    
            public FirstHttpTrigger(ILoggerFactory loggerFactory)
            {
                _logger = loggerFactory.CreateLogger<FirstHttpTrigger>();
            }
    
            [Function("FirstHttpTrigger")]
            public HttpResponseData Run([HttpTrigger(AuthorizationLevel.Function, "get", "post")] HttpRequestData req)
            {
                _logger.LogInformation("C# HTTP trigger function processed a request.");
    
                var response = req.CreateResponse(HttpStatusCode.OK);
                response.Headers.Add("Content-Type", "text/plain; charset=utf-8");
    
                response.WriteString("Welcome to Azure Functions!");
    
                return response;
            }
        }
    }
    ```
    
    預設接受 `GET` 與 `POST` 要求，且授權等級設定為 `Function` 層級 ([Authorization level](https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-http-webhook-trigger?WT.mc_id=DT-MVP-4015686&tabs=python-v2%2Cisolated-process%2Cnodejs-v4%2Cfunctionsv2&pivots=programming-language-csharp#http-auth))。
    
    若要允許**匿名存取**，可以將 `AuthorizationLevel.Function` 改為 `AuthorizationLevel.Anonymous`。
    
3.  啟動 `Azurite` 模擬器
    
    [Azurite](https://github.com/Azure/Azurite) 是一套開放原始碼的 Azure Storage 模擬器，提供免費的本機儲存體環境，可用來測試您的 Azure Blob、Queue，以及 Table 儲存體等應用。如果測試無誤，就可以切換成使用雲端的 Azure 儲存體帳戶。這套 Azurite 模擬器提供 Windows、Linux 和 macOS 的跨平台支援。
    
    > 詳見: [使用 Azurite 模擬器進行本機 Azure 儲存體開發](https://learn.microsoft.com/zh-tw/azure/storage/common/storage-use-azurite?WT.mc_id=DT-MVP-4015686&tabs=visual-studio%2Cblob-storage)
    
    你可以透過 VS Code 的 `F1` > `Azurite: Start` 命令，快速啟動 Azure Storage 模擬器。
    
    預設 Azurite 會在你的專案根目錄建立幾個目錄與檔案，這些檔案建議加入 `.gitignore` 忽略清單，否則這些檔案會被加入到版控中：
    
    ```
    __azurite*
    __blobstorage__/
    __queuestorage__/
    ```
    
4.  本機偵錯
    
    你可以很方便的直接在 VS Code 中按下 `F5` 開始偵錯，或者執行以下命令啟動執行：
    
    ```bash
    func start
    ```
    
    這支 API 的網址: [http://localhost:7071/api/FirstHttpTrigger](http://localhost:7071/api/FirstHttpTrigger)
    
    > 注意: 進入偵錯時，執行檔的**當前目錄**會在 `bin\Debug\net8.0` 目錄下。如果用 `func start` 啟動的話，執行檔的**當前目錄**會在 `bin\output` 目錄下。
    
    測試 API 時，你可以建立一個 `FirstHttpTrigger.http` 檔案，內容如下：
    
    ```http
    GET http://localhost:7071/api/FirstHttpTrigger
    ```
    
    然後透過 VS Code 的 `REST Client` 擴充套件來執行這個檔案，你就可以看到回應結果了。
    
    ![REST Client .http file](https://stwillblogassets.blob.core.windows.net/files/images/external/github.com/65985b7a8c1bf1bf16ab-9f6acd45-c4e6-423f-b72e-b596685a2614.jpg)
    

### 部署 Azure Functions 應用程式至 Azure App Service

1.  選擇正確的 Azure 租用戶 (Tenant)
    
2.  選擇正確的 Azure 訂用帳戶 (Subscription)
    
3.  使用 Azure CLI 登入並選擇預設的訂用帳戶
    
    這個步驟還不需要先建立資源群組。
    
    ```bash
    # 先清空現有的登入資訊
    az account clear
    
    # 使用 Device Code 登入 (這個步驟會顯是一個網址與一個裝置代碼)
    az login --use-device-code
    
    # 開啟瀏覽器，輸入 https://microsoft.com/devicelogin
    
    # 從瀏覽器輸入裝置代碼以進行身份驗證
    
    # 選擇正確的訂用帳戶
    az account set -s "Microsoft Azure Sponsorship"
    
    # 查詢登入的資訊
    az account show -o jsonc
    
    # 查詢目前訂用帳戶下有多少資源群組
    az group list
    ```
    
4.  建立 Azure 資源
    
    在 VS Code 開啟 `Azure` 側邊欄(Sidebar)，登入 Azure 帳戶，列出所有 Azure 資源。
    
    點擊 `Azure` 側邊欄的右上角 `+` 符號，選擇 `Create Function App in Azure`，輸入 `duotify-linebots` 名稱，選擇 `.NET 8 Isolated`，選擇 `Japan East` 區域。這個過程會自動建立許多資源，其中包含 `duotifylinebots` 資源群組、`duotifylinebots` 儲存體帳戶、`duotifylinebots` Application insights、`duotify-linebots` 函式應用程式、`ASP-duotify-linebots-xxxx` App Service Plan (`Y1` Consumption plan)。
    
5.  部署網站
    
    執行以下命令部署網站：
    
    ```bash
    func azure functionapp publish duotify-linebots
    ```
    
    如果你選擇使用 `Function` 層級的授權，那你必須進入 Azure Portal 找到 `duotify-linebots` 函式應用程式的 `Overview` 頁籤，該頁面下方有個 `FirstHttpTrigger` 函式，點進 `Function Keys` 可以複製名為 `default` 的 Function keys 金鑰。之後若要呼叫這個 API 的話，就必須用以下網址結構來呼叫 Function 執行：
    
    ```
    <api-url>?code=f9qr9TaxgFqWpEZk5pNkHNquKnRf2BhcXOc-GnDuNhF4AzFuqRwQBA==
    ```
    
    例如：
    
    ```
    https://duotify-linebots.azurewebsites.net/api/firsthttptrigger?code=f9qr9TaxgFqWpEZk5pNkHNquKnRf2BhcXOc-GnDuNhF4AzFuqRwQBA==
    ```
    
    下次更新程式只要再執行一次上述命令即可再次部署！
    

### 如何從 `local.settings.json` 檔案中取得組態設定值

在 Azure Functions 的應用程式中，所有組態設定都是放在 `local.settings.json` 檔案中，預設內容如下：

```json
{
  "IsEncrypted": false,
  "Values": {
    "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
    "AzureWebJobsStorage": "UseDevelopmentStorage=true"
  },
  "ConnectionStrings": {}
}
```

> 注意: 這個 `local.settings.json` 檔案預設不會被加入到 Git 版控中，因為這個檔案在建立專案的時候，就已經被加入到 `.gitignore` 檔案中。若是多人開發專案，建議將這些組態設定寫入專案的 `README.md` 文件中，讓其他開發者知道有這些設定。

以前我們在 ASP.NET Core 之中，要取得組態都要靠 `IConfiguration` 服務，但在 Azure Functions 之中，你可以直接使用 `Environment.GetEnvironmentVariable` 來取得組態設定，以下是程式碼範例：

```csharp
var runtime = Environment.GetEnvironmentVariable("FUNCTIONS_WORKER_RUNTIME")!;
```

### 從 Azure 下載 `local.settings.json` 應用程式設定

當你想重新建立開發環境時，因為 `local.settings.json` 並不在版控中，所以你在本機基本上是無法成功執行的。

還好 Azure Functions Core Tools 提供了一個 `func azure functionapp fetch-app-settings` 命令，可以從 Azure 下載應用程式設定，並且將設定直接寫入 `local.settings.json` 應用程式設定檔中，這樣你就可以在本機測試 Azure Functions 了。

```bash
func azure functionapp fetch-app-settings duotify-linebots
```

這裡有個小地雷要注意，當你在下載應用程式設定時，如果專案中還沒有 `local.settings.json` 應用程式設定檔的存在，那麼下載回來的 `local.settings.json` 檔案會是「已加密」的版本，你還需要透過以下命令才能進行解密，否則你會看不到內容，也無法編輯：

```bash
func settings decrypt
```

### 取得 Storage account 的連接字串

在你建立好 Azure Functions 網站與相對應的儲存體帳戶後，你可以使用以下命令取得儲存體帳戶的連接字串：

```bash
func azure storage fetch-connection-string duotifylinebots
```

上述命令會在 `local.settings.json` 檔案中新增一個 `duotifylinebots_STORAGE` 設定，並填入 Azure Storage 的連接字串。這個命令的方便之處在於，當你想在本機測試 Azure Functions 並希望直接連結 Azure Storage 進行測試時比較方便。不過大部分的情況下，都應該在本機測試 Storage 即可。

### 使用 GitHub Copilot 的注意事項

由於 Azure Functions 至今推出過 4 個版本，每個版本之間的差異非常大，因此 GitHub Copilot 在幫你產生程式碼時，可能會產生錯誤的程式碼。這時你就必須自己手動修改程式碼，或者參考官方文件來修改程式碼，這應該算是比較地雷的地方。

### 相關連結

-   [Azure Functions HTTP trigger](https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-http-webhook-trigger?WT.mc_id=DT-MVP-4015686&tabs=python-v2%2Cisolated-process%2Cnodejs-v4%2Cfunctionsv2&pivots=programming-language-csharp#http-auth)
-   [Develop Azure Functions locally using Core Tools](https://learn.microsoft.com/en-us/azure/azure-functions/functions-run-local?tabs=windows%2Cisolated-process%2Cnode-v4%2Cpython-v2%2Chttp-trigger%2Ccontainer-apps&pivots=programming-language-csharp&WT.mc_id=DT-MVP-4015686)
-   [Azure/Azurite](https://github.com/Azure/Azurite): A lightweight server clone of Azure Storage that simulates most of the commands supported by it with minimal dependencies
-   [使用 Azurite 模擬器進行本機 Azure 儲存體開發](https://learn.microsoft.com/zh-tw/azure/storage/common/storage-use-azurite?WT.mc_id=DT-MVP-4015686&tabs=visual-studio%2Cblob-storage)
