2021年5月25日 星期二

快取 Web API 內容 2 : 在 Xamarin.Forms 專案內,將 .NET 物件永久保存儲存,並可以讀取回應用程式內

快取 Web API 內容 2 : 在 Xamarin.Forms 專案內,將 .NET 物件永久保存儲存,並可以讀取回應用程式內

在上一篇 快取 Web API 內容 1 : 將 .NET 物件永久保存儲存,並可以讀取回應用程式內 文章中有說明如何將 .NET 物件序列化成為 JSON 物件,接著寫入到檔案內,藉以儲存該 .NET 物件當時的狀態;另外,也說明如何從檔案內讀取之前儲存的 JSON 文字內容,並且反序列化成為 .NET 物件的做法。

其中,[StorageUtility] 這個類別提供了兩個方法,可以將會把文字內容寫入到指定目錄下的指定檔案名稱,另外一個則可以從指定的目錄與檔案名稱讀取文字內容回來;另外 [StorageJSONService] 則是一個泛型類別,可以將指定的類別型別的物件,寫入到檔案內,或者從檔案內把這個物件的狀態讀取回來。

這些操作都是使用 Console 類型的專案來實作,那麼,對於 Xamarin.Forms 的專案內,這樣的需求是該要如何實踐出來呢?在這篇文章中將會說明該如何做到。

這篇文章的原始碼位於 xfObjectToFile

範例專案設計說明

在這裡將會建立一個 Xamarin.Forms for Prism 的專案,當然,要直接建立一個 Visual Studio 預設專案樣板的 Xamarin.Forms 專案也是可以的。接著,會安裝兩個 NuGet 套件到 Xamarin.Forms 專案內: [Newtonsoft.Json] & [PropertyChanged.Fody]。

在這個專案的首頁畫面中,將會設計兩個按鈕與一個文字控制項,該文字控制項將會用來顯示執行狀態結果,底下將會是這個專案執行後的螢幕截圖。

MainPage.xaml

首先,來看看這個唯一的一個手機頁面的宣告內容,在這裡透過資料綁定,將兩個按鈕的 Command 屬性,分別綁定到 ViewModel 內的兩個命令物件上,如此,當這些按鈕被觸發的時候,將會執行所指定的命令委派方法;而最後一個 Label 控制項,則是綁定 ViewModel 內的 Message 屬性,用來顯示 ViewModel 程式碼的執行結果內容。

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="xfObjectToFile.Views.MainPage"
             Title="{Binding Title}">

    <StackLayout HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand">
        <Label Text="Welcome to Xamarin Forms and Prism!" />
        <Button Text="寫入"
                Command="{Binding WriteCommand}"/>
        <Button Text="讀取"
                Command="{Binding ReadCommand}"/>
        <Label Text="{Binding Message}"
               FontSize="24" TextColor="Blue"/>
    </StackLayout>

</ContentPage>

MainPageViewModel.cs

對於 View 的 XAML 文件,可以說是相當的簡單,現在來看看這個頁面(View)相對應的 ViewModel 類別的程式碼;這個 MainPageViewModel 已經經過重構,可以用於 Prism 這個套件與使用 PropertyChanged.Fody 這個套件來簡化資料綁定的宣告,若讀者使用的不是這兩個環境,請自行修正相關程式碼。

對於 WriteCommand 這個命令物件,其委派方法將會建立一個 [YourClass] 類別執行個體,這個類別內也有一個型別為 MyClasee 的屬性,形成一個複合式的物件。

接著,將會使用 await StorageJSONService<YourClass>.WriteToDataFileAsync("VulcanDir", nameof(YourClass), yourClass); 這樣的敘述,把這個 .NET 物件 (yourClass) 寫入到檔案內,而要寫入的目錄名稱將會是 [VulcanDir],最後,使用 Message = "YourClass 類別執行個體已經儲存到手機內"; 敘述將此處理結果顯示到螢幕上。

using Prism.Commands;
using Prism.Mvvm;
using Prism.Navigation;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace xfObjectToFile.ViewModels
{
    using System.ComponentModel;
    using Newtonsoft.Json;
    using Prism.Events;
    using Prism.Navigation;
    using Prism.Services;
    using xfObjectToFile.Storages;

    public class MainPageViewModel : INotifyPropertyChanged, INavigationAware
    {
        public event PropertyChangedEventHandler PropertyChanged;

        private readonly INavigationService navigationService;
        public DelegateCommand WriteCommand { get; set; }
        public DelegateCommand ReadCommand { get; set; }
        public string Message { get; set; }
        public MainPageViewModel(INavigationService navigationService)
        {
            this.navigationService = navigationService;
            WriteCommand = new DelegateCommand(async () =>
            {
                var yourClass = new YourClass()
                {
                    Id = 168,
                    MyClasee = new MyClass()
                    {
                        MyPropertyInt = 20,
                        MyPropertyString = "Vulcan Lee",
                        MyPropertyDateTime = DateTime.Now.AddDays(-3),
                        MyPropertyDouble = 99.82,
                        MyPropertyTimeSpan = new TimeSpan(15, 23, 58),
                    }
                };
                await StorageJSONService<YourClass>.WriteToDataFileAsync(
                    "VulcanDir", nameof(YourClass), yourClass);
                Message = "YourClass 類別執行個體已經儲存到手機內";

            });
            ReadCommand = new DelegateCommand(async () =>
            {
                var yourClass = await StorageJSONService<YourClass>.ReadFromFileAsync(
                      "VulcanDir", nameof(YourClass));
                string json = JsonConvert.SerializeObject(yourClass);
                Message = $"讀取成功:{json}";
            });
        }

        public void OnNavigatedFrom(INavigationParameters parameters)
        {
        }

        public void OnNavigatedTo(INavigationParameters parameters)
        {
        }

    }
}

這裡將會產生一個問題,就是這個檔案究竟寫到哪個目錄下,也就是該檔案寫入的絕對目錄是在哪裡?

這裡需要來看看 [StorageUtility] 這個類別,例如,在這個類別內的 [WriteToDataFileAsync] 方法,使用這個敘述 string rootPath = FileSystem.AppDataDirectory; 來取得這個 App 可以存取的根路徑是在哪裡,而不是使用之前 Console 專案內使用的 string rootPath = Environment.CurrentDirectory; 敘述來取得。

首先要先知道,每個應用程式安裝到手機內之後,原則上僅能夠存取本身應用程式安裝下的目錄,不能夠存取其他應用程式安裝的目錄,因為這些目錄受到保護,這樣的規則適用於 iOS & Android 系統下。所以,讀者也可以使用類似檔案總管的App來試用看看,因為,你也無法透過類似這樣的檔案總管看到這些受保護的目錄。

在這個 [StorageUtility] 類別內的 [WriteToDataFileAsync] / [ReadFromDataFileAsync] 方法內,有這個敘述 Console.WriteLine($"寫入檔案的路徑 {fooPath}");,這個敘述將會顯示出真正要讀寫的檔案完整目錄是甚麼?這些內容可以從 Visual Studio 的輸出視窗內顯示出來。

現在,可以執行這個專案,點選寫入按鈕,會出現如下圖畫面

接著,請從 Visual Studio 的輸出視窗,可以看到如下面截圖畫面,在輸出視窗的倒數第二行可以看到這樣的內容:

寫入檔案的路徑 /data/user/0/com.companyname.appname/files/VulcanDir/YourClass

因此,可以看到這個應用程式資料的根目錄是位於 /data/user/0/com.companyname.appname

現在,透過 Android SDK 工具來查看該模擬器的檔案系統,看看是否可以看到這些目錄

點選 Visual Studio 功能表的 [工具] > [Android] > [Android 裝置監視器]

由於這個應用程式資料的根目錄是位於 /data/user/0/com.companyname.appname,因此,點選 [Android 裝置監視器] 右方的 [data] 目錄節點左方的 > 符號,嘗試展開這個節點,不過,卻會看到如同下方的結果,無法展開 [data] 目錄下有哪些檔案或者目錄。

要解決這個問題,需要提升這個模擬器的層級,也就是要越獄 Root,因此,請依據底下的做法,讓這個模擬器可以看到這些受保護的目錄內容。

  • 點選 Visual Studio 功能表的 [工具] > [Android] > [Android adb 命令提示字元]
  • 輸入 adb shell 命令,並且按下 [Enter] 按鍵
  • 將會看到這樣的內容 generic_x86:/ $ 這表示你不具備管理者權限
  • 接著輸入 exit + [Enter] 按鍵,離開除錯環境
  • 接這在命令提示字元下輸入 adb root 命令,並且按下 [Enter] 按鍵
  • 現在將會看到這樣的內容 restarting adbd as root
  • 請關閉與重新開啟模擬器,這樣就會越獄成功

請先再度執行這個範例專案,執行完成之後,點選 [寫入] 按鈕

現在,透過 [Android 裝置監視器] 中,展開 [data] 目錄,看到如下圖的畫面

當展開到 [data] > [user] > [0] 這個目錄之後,就再也無法展開,從 [0] 這個目錄的右方,可以看到這個目錄是 [data] > [data] 這個目錄的捷徑。

因此,從目錄 [data] > [data] 繼續來展開,將會看到 com.companyname.appname/files/VulcanDir 這個目錄下的內容了

在這個目錄下,將會看到一個檔案,就是剛剛寫入的檔案

點選 [YourClass] 這個檔案,接著在螢幕右上方找到下載檔案的這個按鈕,當游標移動到這個按鈕之後,將會顯示出 [Pull a file from the device] 這樣的文字。

現在會看到一個名為 [Get Device File] 的對話窗出現,請選擇你要儲存的目錄,點選 [存檔] 按鈕,此時就會從手機模擬器裝置內,把這個檔案下載到你的電腦上。

使用任何文字編輯器來查看這個檔案內容,就會看到如下的內容。

這就是剛剛寫入到檔案內的 .NET 物件內容(這裡是經過 JSON 序列化處理)

{"Id":168,"MyClasee":{"MyPropertyInt":20,"MyPropertyString":"Vulcan Lee","MyPropertyDateTime":"2021-05-20T11:35:27.271805+00:00","MyPropertyTimeSpan":"15:23:58","MyPropertyDouble":99.82}}

最後,在模擬器上,點選讀取按鈕,確認可以從手機中把資料讀取出來,如下圖所示

 







2021年5月24日 星期一

Blazor SfDropDownList 聯動式下拉選單的設計 Part 1 : 符合預期規劃結果

Blazor SfDropDownList 聯動式下拉選單的設計 Part 1 : 符合預期規劃結果

當我在進行 Xamarin.Forms App 專案開發教學的時候,這個需求是學員一定需要做的練習,畢竟,這樣的應用需求是個相當普遍的;在這篇文章中,將會說明如何在 Blazor 專案內,使用 Syncfusion 所提供的 SfDropDownList 元件,做出這樣的設計。

不過,在這篇文章中也會探討一些意外情況,也就是說,當使用其他方式來進行這樣需求的程式設計時候,會有意想不到的效果,也就是下拉選單的清單項目,無法正常顯示在螢幕上。

首先,先來看看這樣的需求,該如何來設計。

這篇文章的原始碼位於 bzDropDownListCorrelation

需求說明

在這個範例專案中,需要設計三個下拉選單,如上圖所示。

第一個下拉選單(在此稱為我的下拉選單)在程式啟動之後,將會有預設清單項目可以來選擇,而第二個(在此稱為你的下拉選單)下拉選單與第三個(在此稱為他的下拉選單)下拉選單則預設沒有任何項目可以選擇,必須要等到我的下拉選單選擇一個新的選單項目之後,底下的兩個下拉選單將會清空,並且會依據我的下拉選單所選擇的內容,建立其可以選擇的清單項目。

例如,在下圖中,在我的下拉選單中,選擇了 [我的選擇清單項目 8],一旦我的下拉選單項目有變動,此時,將會依據剛剛選擇的 [我的選擇清單項目 8] 項目,建立起你的下拉選單與他的下拉選單可以使用的選擇清單項目,這個時候,將會看到如同下圖的樣貌。

另外,在每個下拉選單控制項的下方,將會有兩個數值,分別代表這個下拉選單控制項目的資料來源 DataSource 這個屬性所綁定的物件內,已經有多少的項目存在,另外一個數值則表示這個下拉選單所選擇項目 Id 值為多少。

每個下拉選單項目的 Id 值將會從 1 開始編碼,而當每個下拉選單建立之後,將會產生一個項目,名稱為 [請選擇],而 Id 值為 -1,並且設定這個清單項目為預設的選擇項目。

Problem.razor 下拉選單元件設計程式碼

在這個範例中,所有的測試功能,都設計在 [Problem.razor] Razor 元件內,並且在 [Index.razor] 首頁元件內,加入這個 [Problem.razor] 元件。

對於 [Index.razor] 元件,其原始碼如下

@page "/"

<h1>Hello, SfDropDownList!</h1>

<Problem/>

<SurveyPrompt Title="How is Blazor working for you?" />

而 [Problem.razor] Razor 元件的程式碼如下

@using Syncfusion.Blazor.DropDowns
@using Syncfusion.Blazor.Buttons
@using bzDropDownListCorrelation.Data
@inject ProblemViewModel RazorModel

<SfButton @onclick="Click">OK</SfButton>
<div>
    <SfDropDownList TValue="int" TItem="MyClass" Placeholder="我的條件"
                    Enabled="@RazorModel.ReadyGo"
                    DataSource="@RazorModel.MyClassList" @bind-Value="@RazorModel.QueryCondition.MyId">
        <DropDownListEvents TValue="int" TItem="MyClass"
                            ValueChange="RazorModel.MasterListChanged" />
        <DropDownListFieldSettings Text="Name" Value="Id" />
    </SfDropDownList>
</div>
<div>
    @RazorModel.MyClassList.Count()
</div>
<div>
    @RazorModel.QueryCondition.MyId
</div>

<div>
    <SfDropDownList TValue="int" TItem="YouClass" Placeholder="你的條件"
                    Enabled="@RazorModel.ReadyGo"
                    DataSource="@RazorModel.YourClassList" @bind-Value="@RazorModel.QueryCondition.YourId">
        <DropDownListEvents TValue="int" TItem="YouClass" />
        <DropDownListFieldSettings Text="Name" Value="Id" />
    </SfDropDownList>
</div>
<div>
    @RazorModel.YourClassList.Count()
</div>
<div>
    @RazorModel.QueryCondition.YourId
</div>

<div>
    <SfDropDownList TValue="int" TItem="YouClass" Placeholder="他的條件"
                    Enabled="@RazorModel.ReadyGo"
                    DataSource="@RazorModel.HisClassList" @bind-Value="@RazorModel.QueryCondition.HisId">
        <DropDownListEvents TValue="int" TItem="YouClass" />
        <DropDownListFieldSettings Text="Name" Value="Id" />
    </SfDropDownList>
</div>
<div>
    @RazorModel.HisClassList.Count()
</div>
<div>
    @RazorModel.QueryCondition.HisId
</div>


@code {

    protected async override Task OnInitializedAsync()
    {
        await RazorModel.ProblemViewModelInit();
    }
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender == true)
        {
            #region 若把下拉選單的初始化呼叫動作放在這裡,需要額外呼叫,否則會無法生效
            //await RazorModel.ProblemViewModelInit();
            //StateHasChanged();
            #endregion
        }
    }
    void Click() { }
}

在這個 [Problem.razor] Razor 元件內,最上方有三行 Using ,這裡是在宣告這裡類別將會使用到的命名空間(注意,每個 Razor 元件,在建置期間,都會由編譯器產生成為一個類別,該類別的名稱,就是該元件的名稱)。

而對於 @inject ProblemViewModel RazorModel 則是宣告在這個元件內,將會由相依性注入容器注入一個 [ProblemViewModel] 類別執行個體,也就是說,這裡採用的類似於 MVVM 的設計方式,將畫面內容與商業邏輯分隔開來,所有在畫面上要看到的內容、大小、顏色、位置等等,都會在 Razor 元件內 (也就是通稱的 View) 使用 HTML + CSS 來設計,而對於該頁面的商業邏輯程式碼,將會移到 ViewModel 內來使用 C# 程式碼來進行設計;與傳統的 MVVM 最大的差異,那就是在這裡對於 View 與 ViewModel 之間的互動,不是透過 NotifyPropertyChanged 這樣的機制來運行,而是透過了 Blazor 內建的 MVU 機制來做到。

對於 [ProblemViewModel] 這個類別,將會於 [Startup] 類別內的 [ConfigureServices] 方法內,使用 services.AddTransient<ProblemViewModel>(); 敘述進行註冊到相依性容器內,這樣當要注入到任何類別或者元件內的時候,就會使用暫時生命週期的方式,注入到指定的類別內。

至於 [ProblemViewModel] 類別內容,等下會繼續說明

[Problem.razor] Razor 元件首先宣告一個 OK 按鈕,而對於其綁定的 [@onclick] 事件方法 void Click() { },似乎沒有做任何事情,那麼,為什麼需要在這裡放上一個這樣的按鈕呢?

對於這樣一個空白按鈕事件的設計,是為了要讓 Blazor 元件,可以觸發自動更新頁面的效果,怎麼說呢?在 Blazor 元件內,若想要手動觸發更新元件的效果時候,可以執行 [StateHasChanged()] 這個方法;而對於任何標籤 Element / Tag 有綁到 [@onclick] 這個事件的方法,只要觸發並且執行完成該事件的委派方法,則會自動地進行該元件更新的行為,就如同手動執行 [StateHasChanged()] 方法所得到相同效果。

因此,這樣的設計將會在等下會用到

接著,這裡宣告了三個 SfDropDownList ,分別代表 我的、你的、他的下拉選單控制項,每個控制項都透過了 [DataSource] 這個參數,指定該下拉選單的清單項目資料來源,而當使用者點選了該下拉選單,並且做出選擇之後,該遠則清單項目的 Id 值,將會儲存在 [RazorModel.QueryCondition] 類別內的某個屬性內。

使用 [我的下拉選單] 這個控制項來做說明,這個下拉選單的清單項目資料來源將會從 ViewModel , 也就是這個 ProblemViewModel 類別產生的執行個體, 物件內 [MyClassList] 這個集合清單屬性來獲取而得;當然,這個下拉選單內的清單項目想要顯示甚麼內容,就僅需要針對 [MyClassList] 這個集合性質物件來進行調整即可,對於綁定到的下拉選單控制項而言,就會顯示出相對應的最新清單項目內容。

在每個下拉選單控制項的下方,分別有這樣的宣告

<div>
    @RazorModel.MyClassList.Count()
</div>
<div>
    @RazorModel.QueryCondition.MyId
</div>

這也是為了除錯方便,可以從畫面上看到現在這個下拉選單內的 [DataSource] 所指定的集合物件究竟有多少物件存在、當使用者點選該下拉選單的時候,就必須一定要能夠看到這些數量的清單項目在螢幕上,否則,就是所設計的元件出了問題;而第二個則是顯示使用者當時點選的項目是哪一個,將會在這裡即時顯示出來。

在這個元件的最後面,將會使用到兩個元件生命週期的事件,分別是: [Task OnInitializedAsync()] 與 [Task OnAfterRenderAsync(bool firstRender)],在 [@code] 區段內的 C# 程式碼可以看的出來,對於 [ViewModel] 物件(也就是 [ProblemViewModel] 這個類別所注入的物件) 要進行相關資料初始化的時候,可以分別選擇這兩個事件來進行呼叫 [await RazorModel.ProblemViewModelInit()] 這個非同步方法,不過,在這裡會先使用 [Task OnInitializedAsync()] 這個生命週期事件來做為觸發與產生預設資料;當你使用的都是同步程式碼的時候(不含使用非同步的程式碼,但是使用封鎖式的等待作法),原則上不會遇到太多詭異的問題,但是若使用到非同步程式呼叫的的時候,許多詭異的現象就會發生了。

ProblemViewModel 的 ViewModel 程式碼

using bzDropDownListCorrelation.Data;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace bzDropDownListCorrelation
{
    public class ProblemViewModel
    {
        public ProblemViewModel(MyClassService myClassService,
            YourClassService youClassService)
        {
            MyClassService = myClassService;
            YourClassService = youClassService;
        }
        public List<MyClass> MyClassList { get; set; } = new List<MyClass>();
        public List<YouClass> YourClassList { get; set; } = new List<YouClass>();
        public List<YouClass> HisClassList { get; set; } = new List<YouClass>();
        public QueryCondition QueryCondition { get; set; } = new QueryCondition();
        public MyClassService MyClassService { get; }
        public YourClassService YourClassService { get; }
        public bool ReadyGo { get; set; } = false;

        public async Task ProblemViewModelInit()
        {
            var myLists = await MyClassService.Get();

            #region 使用 AddRange + await 會造成資料無法讀取進來
            //MyClassList.Clear();
            //MyClassList.Insert(0, new MyClass { Name = "請選擇", Id = -1 });
            //QueryCondition.MyId = -1;
            //MyClassList.AddRange(await MyClassService.Get());
            #endregion

            MyClassList.Clear();
            MyClassList = myLists;
            MyClassList.Insert(0, new MyClass { Name = "請選擇", Id = -1 });
            QueryCondition.MyId = -1;

            YourClassList.Clear();
            YourClassList.Insert(0, new YouClass { Name = "請選擇", Id = -1 });
            QueryCondition.YourId = -1;

            HisClassList.Clear();
            HisClassList.Insert(0, new YouClass { Name = "請選擇", Id = -1 });
            QueryCondition.HisId = -1;

            ReadyGo = true;
        }

        public async Task MasterListChanged(Syncfusion.Blazor.DropDowns.ChangeEventArgs<int, MyClass> args)
        {
            if (QueryCondition.MyId <= 0) return;

            var yourList = await YourClassService.Get("你的選擇清單項目", QueryCondition.MyId);
            var hisList = await YourClassService.Get("他的選擇清單項目", QueryCondition.MyId);

            #region 使用這個方式,會造成無法連動顯示
            //YourClassList.Clear();
            //YourClassList.Insert(0, new YouClass { Name = "請選擇", Id = -1 });
            //QueryCondition.YourId = -1;
            //YourClassList.AddRange(yourList);
            //YourClassList = yourList;
            #endregion

            YourClassList.Clear();
            YourClassList = yourList;
            YourClassList.Insert(0, new YouClass { Name = "請選擇", Id = -1 });
            QueryCondition.YourId = -1;


            HisClassList.Clear();
            HisClassList = hisList;
            HisClassList.Insert(0, new YouClass { Name = "請選擇", Id = -1 });
            QueryCondition.HisId = -1;
        }
    }
    public class QueryCondition
    {
        public int MyId { get; set; }
        public int YourId { get; set; }
        public int HisId { get; set; }
    }
}

在這個 [ProblemViewModel] 類別,將會透過相依性注入容器,注入到所要使用的 Razor 元件內;在建構函式內,將會注入 [MyClassService] 與 [YourClassService] 這兩個類別物件。

另外,在這個類別內宣告了許多屬性,這些屬性將會用於綁定到 Razor 元件上

該類別還有設計兩個方法

  • ProblemViewModelInit

    在這個下拉選單元件建立的時候,會透過這個方法來建立起我的下拉選單可用的清單項目紀錄,在此是透過 [await MyClassService.Get()] 這個非同步方法呼叫的方式來取得;一旦取得了要顯示的所有顯示清單項目,便會將這個最新清單項目指派給要綁定屬性 [MyClassList],接著會在最前面置入一個額外選項,名稱為 "請選擇" 的選項,並且設定該選項為預設選擇項目。

    之後,便會把你的下拉選單與他的下拉選單清空。

  • MasterListChanged

    這是個 [SfDropDownList] 的 [ValueChange] 事件的委派事件,當該下拉選單的選擇項目有異動的時候,便會觸發這個 [MasterListChanged] 事件委派方法

    在這個事件委派方法內,將會依據我的下拉選單的最新輸入的清單項目,動態產生出你的下拉選單與他的下拉選單之可用清單項目,並且把這兩個下拉選單之前的輸入結果,進行重新設定;不論是你的下拉選單或者是他的下拉選單的清單項目,都是透過非同步方法呼叫方式來取得。

MyClassService 程式碼

using bzDropDownListCorrelation.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace bzDropDownListCorrelation
{
    public class MyClassService
    {
        public async Task<List<MyClass>> Get()
        {
            await Task.Delay(1000); // 模擬存取資料庫的非同步執行時間
            List<MyClass> result = new();
            for (int i = 1; i < 100; i++)
            {
                MyClass myClass = new()
                {
                    Id = i,
                    Name=$"我的選擇清單項目 {i}",
                };
                result.Add(myClass);
            }
            return result;
        }
    }
}

底下為這個類別的程式碼

該類別僅設計一個非同步方法 [Get],會產生出 99 筆的清單項目,不過,在這裡使用了 await Task.Delay(1000) 敘述來按停 1 秒鐘的時間,代表模擬存取資料庫的非同步執行時間,因此,當網頁顯示在瀏覽器的時候,將會需要等候 1 秒的時間,我的下拉選單控制項,才可以看到這些清單項目可以使用。

YourClassService 程式碼

using bzDropDownListCorrelation.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace bzDropDownListCorrelation
{
    public class YourClassService
    {
        public async Task<List<YouClass>> Get(string subject, int parentId)
        {
            Random random = new();
            var maxItems = random.Next(10, 60);
            var delay = random.Next(500, 3000);
            await Task.Delay(delay); // 模擬存取資料庫的非同步執行時間
            List<YouClass> result = new();
            for (int i = 1; i < maxItems; i++)
            {
                YouClass myClass = new()
                {
                    Id = i,
                    Name=$"{subject}{parentId} - {i}",
                };
                result.Add(myClass);
            }
            return result;
        }
    }
}

底下為這個類別的程式碼

該類別僅設計一個非同步方法 [Get],不過該方法需要提供兩個參數,第一個是該選擇清單項目要顯示的前導文字內容,第二個參數則是我的下拉選單所選擇清單項目 Id,這樣可以方便看出你的和他的下拉選單清單項目,會隨著我的下拉選單選擇結果而有我變化。接著會隨機產生出 10 - 60 筆的清單項目,不過,在這裡同樣的使用了 var delay = random.Next(500, 3000);await Task.Delay(delay); 敘述來按停 0.53 秒鐘的時間,代表模擬存取資料庫的非同步執行時間,因此,當我的下拉選單輸入項目有變動的時候,將會需要等候 0.53 秒的時間,你的下拉選單控制項與他的下拉選單控制項,才可以看到這些清單項目可以使用。

執行結果

請按下 F5 開始執行這個專案,嘗試進行操作,應該會如同當初所規劃的情境來運作