2023年2月14日 星期二

在 .NET MAUI 專案內使用 AutoMapper 套件,將 DTO 轉換成為 Model

在 .NET MAUI 專案內使用 AutoMapper 套件,將 DTO 轉換成為 Model

在進行 .NET MAUI 專案開發的時候,通常會使用 Web API 的方式與外部系統進行通訊,進行相關資料的處理工作,為了要讓這兩個系統(.NET MAUI App 與 Web API 的系統)能夠具有鬆散耦合的協同運作方式,對於要進行 請求 Request 與 回應 Response 的資料模型,會抽取出來另外進行設計,兩個系統將會透過這個 DTO , Data Transfer Object 資料傳輸物件模型定義內容,進行彼此間的溝通。可是,對於各自系統內,將會有屬於自己的資料模型、商業模型、檢視模型等等,這個時候若自行進行這些模型物件的轉換工作,將會是相當耗費時間與人力成本的,因此,可以透過類似 AutoMapper 這樣的套件來完成這樣的需求,不用重新再次發明輪子。

AutoMapper 是一個 .NET C# 中的物件映射套件,它可以自動將一個類別的屬性映射到另一個類別的屬性。這個套件可以幫助您省去手動映射類別屬性的麻煩,並且可以大大簡化您的程式碼。

使用 AutoMapper,您只需要建立一個映射配置檔案,然後就可以讓 AutoMapper 自動地將一個類別的屬性映射到另一個類別的屬性。例如,如果您有一個 User 類別和一個 UserDTO 類別,您可以使用 AutoMapper 將 User 物件的屬性映射到 UserDTO 物件的屬性,而不需要手動逐個設定屬性。

在這裡將會要設計一個 .NET MAUI 專案,在此專案內將呼叫一個遠端 Web API,此 Web API 將會回傳一個 APIResult 型別的物件,其中在 Payload 屬性將會存放著型別為 ProductDto 型別的物件,接下來要來看看如何在 .NET MAUI 專案內,如何使用 AutoMapper 這個套件。

建立採用 Prism 開發框架的 MAUI 專案

  • 打開 Visual Studio 2022 IDE 應用程式
  • 從 [Visual Studio 2022] 對話窗中,點選右下方的 [建立新的專案] 按鈕
  • 在 [建立新專案] 對話窗右半部
    • 切換 [所有語言 (L)] 下拉選單控制項為 [C#]
    • 切換 [所有專案類型 (T)] 下拉選單控制項為 [MAUI]
  • 在中間的專案範本清單中,找到並且點選 [Vulcan Custom Prism .NET MAUI App] 專案範本選項
  • 點選右下角的 [下一步] 按鈕
  • 現在顯示出 [設定新的專案] 對話窗
  • 在 [專案名稱] 欄位內輸入 MA54 作為此專案的名稱
  • 請點選右下角的 [建立] 按鈕
  • 此時,將會建立一個可以用於 MAUI 開發的專案

安裝 AutoMapper 套件

使用 AutoMapper 可以說相當的容易,僅需要安裝一個 [AutoMapper.Extensions.Microsoft.DependencyInjection] 套件,便可以開始使用

  • 滑鼠右擊專案根目錄下的 [相依性] 節點

  • 選擇 [管理 NuGet 套件] 選項

  • 在 NuGet 視窗內,點選 [瀏覽] 標籤頁次

  • 在 [搜尋] 文字輸入盒內,輸入 AutoMapper.Extensions.Microsoft.DependencyInjection

  • 當搜尋到這個套件,點選這個套件,接著點選右上方的 [安裝] 按鈕,進行這個套件的安裝

建立 DTO 模型 APIResult 類別

在這個專案將會呼叫一個遠端 Web API,而該 API 服務將會回傳一個 [APIResult] 物件,這個物件就是一個 Data Transfer Object , DTO 型別的物件,主要的目的是提供呼叫各個 Web API ,都有統一回傳格式。

  • 滑鼠右擊專案節點

  • 在彈出功能清單視窗內,選擇 [加入] > [資料夾]

  • 將剛剛產生的新資料夾命名為 Dtos

  • 滑鼠右擊專案根目錄節點下的 [Dtos]

  • 在彈出功能清單視窗內,選擇 [加入] > [類別]

  • 在 [新增項目] 視窗的下方 [名稱] 欄位內輸入 APIResult.cs

  • 點選視窗右下方的 [新增] 按鈕

  • 使用底下程式碼替換掉剛剛建立檔案內容

namespace MA54.Dtos;

/// <summary>
/// 呼叫 API 回傳的制式格式
/// </summary>
public class APIResult : ICloneable
{
    /// <summary>
    /// 此次呼叫 API 是否成功
    /// </summary>
    public bool Status { get; set; } = true;
    public int HTTPStatus { get; set; } = 200;
    public int ErrorCode { get; set; }
    /// <summary>
    /// 呼叫 API 失敗的錯誤訊息
    /// </summary>
    public string Message { get; set; } = "";
    /// <summary>
    /// 呼叫此API所得到的其他內容
    /// </summary>
    public object Payload { get; set; }

    #region 介面實作
    public APIResult Clone()
    {
        return ((ICloneable)this).Clone() as APIResult;
    }
    object ICloneable.Clone()
    {
        return this.MemberwiseClone();
    }
    #endregion
}
  • 在這個 APIResult 類別內的程式碼,都已經加上了相關註解說明
  • 通常在使用的時候,會如底下過程
    • 一旦呼叫 Web API 之後,所得到的 JSON 物件,將會是 [APIResult] 型別
    • 透過 [Status] 這個屬性,可以知道此次呼叫 Web API 結果是否有成功
    • 若不成功,可以透過 [Message] 屬性得到此次呼叫失敗的原因為何
    • 若呼叫 Web API 是成功的,將會有一個 JSON 物件存在放 [Payload] 屬性內,而其 .NET C# 的型別將會取決於各個 Web API 實際回傳物件而定。

建立 DTO 模型 ProductDto 類別

在這個練習中,若成功呼叫 Web API 之後,將會有個 [List] 集合型別物件可以在 [Payload] 屬性中得到,因此,需要在此 App 中建立 [ProductDto] 型別類別。

  • 滑鼠右擊專案根目錄節點下的 [Dtos]
  • 在彈出功能清單視窗內,選擇 [加入] > [類別]
  • 在 [新增項目] 視窗的下方 [名稱] 欄位內輸入 ProductDto.cs
  • 點選視窗右下方的 [新增] 按鈕
  • 使用底下程式碼替換掉剛剛建立檔案內容
namespace MA54.Dtos;

public class ProductDto : ICloneable
{
    public int Id { get; set; }
    public string Name { get; set; }
    public short ModelYear { get; set; }
    public decimal ListPrice { get; set; }

    #region 介面實作
    public ProductDto Clone()
    {
        return ((ICloneable)this).Clone() as ProductDto;
    }
    object ICloneable.Clone()
    {
        return this.MemberwiseClone();
    }
    #endregion
}
  • 在後端 Web API 內,將會有個 Product 型別,用來宣告每個 產品 應該要有哪些屬性要儲存

  • 透過 Web API 的呼叫,在後端 Web API 將會把 [Product] 型別轉換成為 [ProductDto] 型別

    在這個練習中,為了簡化操作,因此對於 [Product] 與 [ProductDto] 這兩個類別內的屬性成員,都是相同的

建立 產品模型 Product 類別

由於透過 Web API 取得的 [ProductDto] 型別物件,將是用於呼叫 Web API 傳送或回應之用,而在 .NET MAUI 應用專案內,當需要用到這些 [ProductDto] 物件,將會有些不方便,例如,這些 [xxxDto] 型別的物件內,都沒有實作 [INotifyPropertyChanged] 介面,因此,沒有辦法直接把這些 [xxxDto] 型別的物件用於 MVVM 設計模式下;所以,在 .NET MAUI 專案內將會需要設計一個 [Product] 型別,用於行動應用程式專案內使用,當然,這裡的 [Product] 型別物件會有可能與後端 Web API 內的 [Product] 型別物件有些不同

  • 滑鼠右擊專案節點
  • 在彈出功能清單視窗內,選擇 [加入] > [資料夾]
  • 將剛剛產生的新資料夾命名為 Models
  • 滑鼠右擊專案根目錄節點下的 [Models]
  • 在彈出功能清單視窗內,選擇 [加入] > [類別]
  • 在 [新增項目] 視窗的下方 [名稱] 欄位內輸入 Product.cs
  • 點選視窗右下方的 [新增] 按鈕
  • 使用底下程式碼替換掉剛剛建立檔案內容
using CommunityToolkit.Mvvm.ComponentModel;

namespace MA54.Models;

public partial class Product : ObservableObject, ICloneable
{
    [ObservableProperty]
    int id = 0;
    [ObservableProperty]
    string name = string.Empty;
    [ObservableProperty]
    short modelYear = 0;
    [ObservableProperty]
    decimal listPrice = 0;

    #region 介面實作
    public Product Clone()
    {
        return ((ICloneable)this).Clone() as Product;
    }
    object ICloneable.Clone()
    {
        return this.MemberwiseClone();
    }
    #endregion
}
  • 對於這裡新建立的 [Product] 類別,將會套用 MVVM 工具組 , CommunityToolkit.Mvvm , 或稱之為 MVVM Toolkit 套件所提供的功能
  • 因此,將會使用 自動實作的屬性 方式來宣告類別的屬性成員
  • 而是使用 欄位 Field 方式來宣告這些屬性成員,另外,都是使用 [Private] 方式來宣告
  • 對於公開 Public 的屬性,將會透過 .NET Compiler Platform (Roslyn) 內的 來源產生器 來產生出來
  • 對於要用於 來源產生器 產生的屬性,在這些欄位上方都要使用 [ObservableProperty] 屬性來宣告
  • 對於類別的宣告部分,這些類別需要繼承 [ObservableObject] 類別,而且,在類別前面需要使用 [partial] 這個修飾詞,這樣 MVVM Toolkit 才能夠正常運作

建立 AutoMapper 設定 類別

現在要來設計 AutoMapper 要用到的對應方式宣告,這裡需要告知 AutoMapper 物件,要將哪個型別對應到另外一個型別上(或者可以指定那些屬性使用那些組合會者自訂對應關係)

  • 滑鼠右擊專案節點
  • 在彈出功能清單視窗內,選擇 [加入] > [資料夾]
  • 將剛剛產生的新資料夾命名為 Helpers
  • 滑鼠右擊專案根目錄節點下的 [Helpers]
  • 在彈出功能清單視窗內,選擇 [加入] > [類別]
  • 在 [新增項目] 視窗的下方 [名稱] 欄位內輸入 AutoMapping.cs
  • 點選視窗右下方的 [新增] 按鈕
  • 使用底下程式碼替換掉剛剛建立檔案內容
using AutoMapper;
using MA54.Dtos;
using MA54.Models;

namespace MA54.Helpers;

public class AutoMapping : Profile
{
    public AutoMapping()
    {
        #region DTO - Model 對應關係宣告
        CreateMap<Product, ProductDto>();
        CreateMap<ProductDto, Product>();
        #endregion
    }
}
  • 這裡設計的類別將會需要繼承 [Profile] 類別
  • 在該類別的建構式內,使用 CreateMap<T1,T2>() 方法,宣告不同型別的對應方式
  • 在這裡宣告了當有個 [Product] 物件,可以透過 AutoMapper 將這個物件內的值,轉換到型別為 [ProductDto] 物件內
  • 另外也宣告了當有個 [ProductDto] 物件,可以透過 AutoMapper 將這個物件內的值,轉換到型別為 [Product] 物件內

在程式進入點宣告 AutoMapper 服務

現在,為了要讓 AutoMapper 可以正常運作,需要將 AutoMapper 用到的服務註冊到相依性注入容器內

  • 在專案根目錄下,找到並且打開 [MauiProgram.cs] 檔案
  • 在該檔案最上方,加入底下的命名空間宣告
using MA53.Helpers;
  • 找到 var builder = MauiApp.CreateBuilder(); 敘述
  • 在其下方加入底下程式碼
#region AutoMapper 服務註冊
builder.Services.AddAutoMapper(c => c.AddProfile<AutoMapping>());
#endregion
  • 底下將會是完成後的程式碼
using MA54.ViewModels;
using MA54.Views;
using MA54.Helpers;

namespace MA54;

public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
    {
        var builder = MauiApp.CreateBuilder();

        #region AutoMapper 服務註冊
        builder.Services.AddAutoMapper(c => c.AddProfile<AutoMapping>());
        #endregion

        builder
            .UseMauiApp<App>()
            .UsePrism(prism =>
            {

                prism.RegisterTypes(container =>
                      {
                          container.RegisterForNavigation<MainPage, MainPageViewModel>();
                      })
                     .OnInitialized(() =>
                      {
                          // Do some initializations here
                      })
                     .OnAppStart(async navigationService =>
                     {
                         // Navigate to First page of this App
                         var result = await navigationService
                         .NavigateAsync("NavigationPage/MainPage");
                         if (!result.Success)
                         {
                             System.Diagnostics.Debugger.Break();
                         }
                     });
            })
            .ConfigureFonts(fonts =>
            {
                fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
                fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
            });

        return builder.Build();
    }
}

開始使用 AutoMapper 功能

完成相關準備與設定工作之後,便可以開始來使用 AutoMapper,當要使用 AutoMapper 的時候,可謂相當的簡單,只需要在建構式內注入 [IMapper] 這個型別物件,之後便可以透過此物件來進行不同型別物件的轉換工作。

  • 在專案根目錄下,打開 [ViewModels] > [MainPageViewModel.cs] 檔案
  • 在程式碼最上方加入底下命名空間的宣告
using MA54.Dtos;
using MA54.Models;
  • 使用底下程式碼,建立一個 [IMapper] 型別的私有欄位
private readonly IMapper mapper;
  • 找到這個 ViewModel 類別的建構式,也就是 public MainPageViewModel(INavigationService navigationService)
  • 將這個建構式使用底下程式碼來取代
public MainPageViewModel(INavigationService navigationService,
    IMapper mapper)
{
    this.navigationService = navigationService;
    this.mapper = mapper;
}
  • 找到 private void Count() 方法宣告
  • 將這個 Count() 方法程式碼,使用底下程式碼來替換
private async Task Count()
{
    APIResult apiReslut = new();
    Text = "請稍後 ...";
    HttpClient client = new HttpClient();
    var responseMessage = await client
        .GetAsync("https://blazortw.azurewebsites.net/api/SampleAutoMapper");
    apiReslut = await responseMessage.Content.ReadFromJsonAsync<APIResult>();
    if (responseMessage.IsSuccessStatusCode)
    {
        if (apiReslut.Status == true)
        {
            List<ProductDto> productDtos = JsonConvert
                .DeserializeObject<List<ProductDto>>(
                apiReslut.Payload.ToString());
            List<Product> products = mapper.Map<List<Product>>(productDtos);
            Text = $"取得 Product 筆數 : {products.Count}";

            foreach (var item in products)
            {
                Text += $",{item.Name}";
            }
        }
    }
}
  • 在 [Count()] 方法內,首先建立一個 [HttpClient] 物件,需要透過此物件進行 Web API 呼叫
  • 使用 [HttpClient.GetAsync] 方法,發出一個 HTTP Get 請求 Request
  • 當取得型別為 [HttpResponseMessage] 物件,也就是存放在 [responseMessage] 變數內,透過 responseMessage.Content.ReadFromJsonAsync<APIResult>() 取得 API 的回應結果,並且將回應 JSON 物件,反序列化成為 [APIResult] 型別的物件
  • 若這個 [APIResult] 物件內的 [Status] 屬性為 true,那就表示此次呼叫 Web API 是成功的,若有回應 JSON 內容,將會存放在 [Payload] 屬性內
  • 透過 JsonConvert.DeserializeObject<List<ProductDto>>(apiReslut.Payload.ToString()) 方法,將 [Payload] 的 JSON 物件反序列化為 [List] 型別的 .NET 物件
  • 這裡反序列化的結果將會儲存在 [productDtos] 物件內
  • 最後透過 mapper.Map<List<Product>>(productDtos) 方法,使用 IMapper 型別物件(該物件是透過建構式注入的方式來取得)內提供的 [Map] 泛型 API ,將取得的 List<ProductDto> 集合物件,轉換成為 List<Product> 型別的物件
  • 完成這樣的需求僅需要一行程式碼即可以做到,若沒有類似 [AutoMapper] 這樣的物件,那就需要更多的程式碼來完成同樣的需求

執行結果

現在來實際測試看看執行結果

  • 切換到 [Android Emulator] 模式,選擇一個適合的模擬器,開始執行此專案,將會看到底下結果

  • 點選 [Click me] 按鈕,將會出現底下畫面結果

 







2023年2月13日 星期一

如何解決 Android 模擬器無法透過 Internet 打開任何網頁與看到網站內容

 

如何解決 Android 模擬器無法透過 Internet 打開任何網頁與看到網站內容

也許之前在使用 .NET MAUI 開發的時候,大多是用於進行各種教學課程的練習專案設計,還沒有進入到需要連線到網路上的應用範例;然而,上週需要進行使用 .NET MAUI 開發的專案,進行 OAuth2 的身分驗證開發需求,這裡首先需要用到的是要與 Azure AD 來進行身分驗證。

經過一番奮戰,首先決定先採用 Web authenticator 這個由 .NET MAUI 平台在 平臺整合 內所提供的 API 來嘗試能夠完成 OAuth2 與 Azure AD 的身分驗證。

在解決了大部分的問題之後,突然發現到,我經常使用的 Android Pixel 5 - API 33 模擬器,要使用裡面內建的 Chrome 瀏覽器來開啟自己設計的 Web API 服務的時候,卻無法開啟這個服務端點。當遇到這個問題,當然先去網路上來搜尋看看有沒有類似的問題發生,首先看到的就是,很多人都指向這樣的問題是與 DNS 設定有關,因此,根據網路上查詢到的資料,進行本機或者模擬器端的 DNS 修正,結果是沒有任何效果,之後回想,當第一次在模擬器開啟 Chrome 瀏覽器的時候,將會看到如下圖的畫面,在下方的 [Discovery] 的內容,可以看到這個瀏覽器似乎有正常運作,因為可以抓取到最新的網頁內容;另外,我也嘗試打開 [Youtube] App,發現到這個 App 可以正常運作,這表示了該模擬器的網路與 DNS 運作是沒有問題的。

其實,我是可以忽略掉這個問題,因為,在我的桌機上面,有個之前很早之前就安裝的模擬器,我用他來開啟任何網頁都可以正常運作,不過,當我沒有使用家裡的桌機時候,就必須面對到上面所提到的問題,因此,還是需要燃燒自己的生命,再次來進行探索與嘗試解決這個問題。

再次透過網路來搜尋,無意見看到一個這段文字

It's caused by vulkan. To fix it, you must turn vulkan off on emulator or chrome.

其中 Vulkan 代表甚麼與為什麼會造成這樣的問題,其實我並不關心,所以,我根據相關網頁提到的線索,進行底下的操作

  • 找到這個目錄 [C:\Users%USER%.android] 下的 [advancedFeatures.ini]
  • 使用任何文字編輯器工具來開啟這個檔案
  • 底下將會是我這台電腦上所看到的預設內容
WindowsHypervisorPlatform=on
  • 在這個檔案內,加入底下兩行敘述
WindowsHypervisorPlatform=on
Vulkan = off
GLDirectMem = on
  • 儲存並且關閉這個檔案
  • 重新開啟 Android 模擬器
  • 打開模擬器上的 Chrome 瀏覽器
  • 打開任何網頁 ,這裡打開聯合報的網站
  • 此時便可以正常運作了







2023年2月8日 星期三

動手練習 ASP.NET Core7 用強型別來讀取設定練習

動手練習 ASP.NET Core7 用強行別來讀取設定練習

最近在進行 ASP.NET Core 7 專案開發的時候,突然遇到一個需要,那就是在 [Program.cs] 這個檔案內,進行使用 [build.Services] 這個屬性要進行 DI / IoC 容器注入的時候,卻想要讀取 ASP.NET Core 的設定 Configuration 內提供的相關設定值的時候,卻又想要使用強型別的方式,將一個區段內的設定值,直接全部讀取出來,並且儲存到一個物件內,這樣就會免除了需要逐一透過 [Configuration] 物件來取得這些物件值的麻煩程式碼,也會造成神奇字串輸入錯誤所造成的額外副作用影響問題。

會有這樣的需求,是因為要進行 JWT 的需求設計,而在 [Program.cs] 內,需要使用 [builder.Services.AddAuthentication().AddJwtBearer] 來進行 JWT 服務的宣告,然而,在這個 [AddJwtBearer] 方法內,將會需要建立一個 [TokenValidationParameters] 物件,宣告這個 JWT 要進行那些驗證行為與這個 JWT 的設定值。

由於在進行設計使用者登入身分驗證的時候,若通過身分驗證後,需要產生一個適合的 JWT 物件出來,此時,也需要用剛剛提到的 JWT 相關設定值。因此,就會想要把這些設定值設定到 [appsettings.json] 檔案內,方便可以集中管理與變更,也不用把這些設定值使用 Hard Code 的方式,寫死在程式碼內,造成使用 Ctrl + C / Ctrl + V 操作模式下所造成的副作用影想。

在這裡將會要使用 ASP.NET Core 中的選項模式 所提供的能力來做到可以使用強型別的方式,來讀取 Configuration 設定的內容值,另外,在這裡也會使用較不方便的 Configuration 物件,說明如何使用弱型別的方式來讀取設定值的做法。

建立 ASP.NET Core 7 專案

首先先來建立一個 ASP.NET Core 空白專案,請依照底下說明來建立這個專案

  • 打開 Visual Studio 2022 IDE 應用程式
  • 從 [Visual Studio 2022] 對話窗中,點選右下方的 [建立新的專案] 按鈕
  • 在 [建立新專案] 對話窗右半部
    • 切換 [所有語言 (L)] 下拉選單控制項為 [C#]
    • 切換 [所有專案類型 (T)] 下拉選單控制項為 [Web]
  • 在中間的專案範本清單中,找到並且點選 [空的 ASP.NET Core] 專案範本選項
  • 點選右下角的 [下一步] 按鈕
  • 在 [設定新的專案] 對話窗
  • 找到 [專案名稱] 欄位,輸入 AN020 作為專案名稱
  • 點選右下角的 [下一步] 按鈕
  • 現在將會看到 [其他資訊] 對話窗
  • 請點選右下角的 [建立] 按鈕

修正設定檔案的來源內容

  • 在專案根節點內,找到並且打開 [appsettings.json] 檔案
  • 找到 "AllowedHosts": "*" 屬性宣告項目
  • 在該節點之後,加入底下宣告內容
"JWT": {
"ValidIssuer": "Backend",
"ValidAudience": "Backend",
"ExpireMinutes": 20,
"RefreshExpireDays": 7,
"IssuerSigningKey": "https://randomkeygen.com/"
}
  • 在這裡建立一個設定設定節點,該設定節點物件內將會包含五個屬性值,分別是 ValidIssuer 、 ValidAudience 、 ExpireMinutes 、 RefreshExpireDays 、 IssuerSigningKey
  • 這五個屬性值將會用於設定這個專案內的 JWT 元件運作行為與表現
  • 底下將會是完成後的 [appsettings.json] 檔案內所有內容
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "JWT": {
    "ValidIssuer": "Backend",
    "ValidAudience": "Backend",
    "ExpireMinutes": 20,
    "RefreshExpireDays": 7,
    "IssuerSigningKey": "https://randomkeygen.com/"
  }
}

使用弱型別方式,讀取 設定 中的 JWT 區段內容

  • 在專案根目錄內,找到並且打開 [Program.cs] 檔案
  • 找到 var builder = WebApplication.CreateBuilder(args); 敘述
  • 在此敘述下方加入底下程式碼
#region 使用 Configuration 物件來讀取設定內容
string ValidIssuer = builder.Configuration["JWT:ValidIssuer"];
IConfigurationSection configurationSection = builder
    .Configuration.GetSection("JWT");
string ValidAudience = configurationSection["ValidAudience"];
string IssuerSigningKey = builder.Configuration["JWT:IssuerSigningKey"];
string ExpireMinutesContent = builder.Configuration["JWT:ExpireMinutes"];
int ExpireMinutes = int.Parse(ExpireMinutesContent);
string RefreshExpireDaysContent = configurationSection["RefreshExpireDays"];
int RefreshExpireDays = int.Parse(RefreshExpireDaysContent);

Console.WriteLine($"Configuration ValidIssuer : {ValidIssuer}");
Console.WriteLine($"Configuration ValidAudience : {ValidAudience}");
Console.WriteLine($"Configuration ExpireMinutes : {ExpireMinutes}");
Console.WriteLine($"Configuration RefreshExpireDays : {RefreshExpireDays}");
Console.WriteLine($"Configuration IssuerSigningKey : {IssuerSigningKey}");
  • 首先,想要取得型別為 [ConfigurationManager] 的物件,可以透過 [builder.Configuration] 物件來得到
  • ConfigurationManager 提供 key 為字串,而 value 為字串的資料字典運算子,因此,可以使用設定路徑字串作為鍵值,取得當時設定屬性值是甚麼。
  • 不同屬性間,可以使用 冒號 : 來分隔不同屬性階層關係
  • 例如,這裡使用 string ValidIssuer = builder.Configuration["JWT:ValidIssuer"]; 敘述,宣告鍵值 Key 為 JWT:ValidIssuer 這個絕對路徑,取得 [ConfigurationManager] 物件內的相對應 Value 值到 [ValidIssuer] 區域變數內
  • 另外,可以透過 [Configuration.GetSection] 方法,設定內容內的某個子階層設定內容,得到的物件型別將會是 ConfigurationSection
  • [ConfigurationSection] 型別的物件,提供 key 為字串,而 value 為字串的資料字典運算子
  • 透過 [ConfigurationSection] 物件,可以使用相對路徑的描述方式,取得某個子階層內的設定屬性值是甚麼。
  • 例如,這個敘述 string ValidAudience = configurationSection["ValidAudience"]; ,將會取得指定子階層內的 [ValidAudience] 屬性值到 ValidAudience 區域變數內
  • 在這個 JWT 設定區段內,有兩個屬性的屬性值將會是整數,其他的都是字串
  • 因此,需要使用上述的方式,取得相關屬性在設定環境內的實際文字內容,接著使用 [int.Parse] 方法,強制轉型成為整數數值。
  • 最後,就可以將這五個 .NET 物件,顯示在 Console 螢幕上
  • 從這裡程式碼與使用方式可以觀察到,由於 Configuration 屬性本身是透過 資料字典 資料結構來取的指定鍵值所對應的數值,不過,所取得的 Value 都是一樣的型別,都是字串;因此,若在設定環境中,指定某個屬性的型別要為 bool, 整數, 浮點數, 列舉,此時,程式設計師需要自己進行這些型別的轉換。
  • 另外一個看到的問題,那就是這裡要取得五個設定屬性內容,就需要在 .NET 系統內宣告五個區域物件來分別儲存這些設定數值,這顯得相當的不方便與不好維護。

建立要讀取設定內容的類別

  • 使用滑鼠右擊專案根節點
  • 從彈出功能表中,點選 [加入] > [類別] 功能項目
  • 此時出現 [新增項目] 對話窗視窗
  • 在 [新增項目] 對話窗下方的名稱欄位內,輸入 JwtConfiguration
  • 點選右下方 [新增] 按鈕,完成新增這個類別檔案
  • 使用底下程式碼,替換掉剛剛產生出來的檔案內容
namespace AN020
{
    public class JwtConfiguration
    {
        public string ValidIssuer { get; set; }
        public string ValidAudience { get; set; }
        public int ExpireMinutes { get; set; }
        public int RefreshExpireDays { get; set; }
        public string IssuerSigningKey { get; set; }
    }
}
  • 在這裡建立一個新的類別,其名稱為 [JwtConfiguration]
  • 在這個類別內,建立了五個屬性,其屬性名稱將會與剛剛在 [appsettings.json] 檔案內新增內容的屬性名稱需要相同。

使用強型別方式來取得設定環境內的數值

  • 有了 [JwtConfiguration] 類別,就可以開始進行用強型別方式來取得設定內的綁定數值
  • 在專案根目錄內,找到並且打開 [Program.cs] 檔案
  • 找到 var app = builder.Build(); 敘述
  • 在此敘述上方加入底下程式碼
#region 加入 設定 強型別 注入宣告
builder.Services.Configure<JwtConfiguration>(builder.Configuration
    .GetSection("JWT"));
#endregion

#region 使用 ServiceProvider 進行強型別讀取 JWT 設定值
using ServiceProvider serviceProvider = builder.Services.BuildServiceProvider();
JwtConfiguration jwtConfiguration = serviceProvider
    .GetRequiredService<IOptions<JwtConfiguration>>().Value;
#endregion

#region 顯示 JWT 設定值
Console.WriteLine($"ValidIssuer: {jwtConfiguration.ValidIssuer}");
Console.WriteLine($"ValidAudience: {jwtConfiguration.ValidAudience}");
Console.WriteLine($"ExpireMinutes: {jwtConfiguration.ExpireMinutes}");
Console.WriteLine($"RefreshExpireDays: {jwtConfiguration.RefreshExpireDays}");
Console.WriteLine($"IssuerSigningKey: {jwtConfiguration.IssuerSigningKey}");
#endregion
  • 首先,需要宣告準備要使用強型別的方式來取得設定環境內的數值
  • 透過 [builer.Services] 來對 DI / IoC 容器進行需要用到的型別註冊
  • 在此將會透過 builder.Services.Configure<JwtConfiguration>(builder.Configuration.GetSection("JWT")); 敘述來做到,在這裡將會透過 builder.Configuration.GetSection("JWT") 方法取得子階層節點的設定內容,接著,註冊這個設定子節點內容將會對應到 [JwtConfiguration] 類別
  • 想要取得這個設定內容子階層的所有數值到 [JwtConfiguration] 的物件內,這個時候就需要透過 DI 相依性注入機制,使用 [IOptions] 這樣的型別來取得一個物件,接著使用這個物件的 Value 屬性,便可以取得一個型別為 [JwtConfiguration] 物件
  • 不過,因為在這裡的程式碼,還在進行 DI 容器的註冊與設定工作,所以,幾乎沒有辦法透過建構式等方式來注入相依物件
  • 因此,在此將會使用 服務定位器 Service Locator 機制來取的一個 DI 容器物件,使用該 DI Container 物件提供的方法,取得指定型別的相依型別物件
  • 這可以從 ServiceProvider serviceProvider = builder.Services.BuildServiceProvider() 敘述可以看出,透過 Services (型別為 [IServiceCollection] 的物件)提供的 [BuildServiceProvider] 方法,便可以取得這裡個 IoC 容器物件
  • 使用 JwtConfiguration jwtConfiguration = serviceProvider.GetRequiredService<IOptions<JwtConfiguration>>().Value 敘述,便可以取得位於設定環境中的 [JWT] 子階層的所有設定內容到 JwtConfiguration 物件內
  • 最後,將 [JwtConfiguration] 物件內的屬性值輸出到螢幕上

執行專案與查看輸出結果

  • 請執行此專案,並且查看 Console 輸出結果是否如下所示
Configuration ValidIssuer : Backend
Configuration ValidAudience : Backend
Configuration ExpireMinutes : 20
Configuration RefreshExpireDays : 7
Configuration IssuerSigningKey : https://randomkeygen.com/
ValidIssuer: Backend
ValidAudience: Backend
ExpireMinutes: 20
RefreshExpireDays: 7
IssuerSigningKey: https://randomkeygen.com/
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: https://localhost:7033
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: http://localhost:5063
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
      Content root path: C:\Vulcan\Projects\AN020\AN020