2023年1月27日 星期五

.NET MAUI 使用者登入 : 使用 MVVM 設計模式進行專案開發

使用者登入 : 使用 MVVM 設計模式進行專案開發



在進行行動應用 App 專案開發的時候,必定會須要有使用者身分驗證這樣的頁面設計需求,這包含了如何設計一個具有使用者可以輸入帳號與密碼的頁面 (View) 、 如何取得使用者輸入帳號與密碼的商業邏輯處理之檢視模型 (ViewModel),所以,就先針對這樣的需求來進行操作。

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

  • 打開 Visual Studio 2022 IDE 應用程式

  • 從 [Visual Studio 2022] 對話窗中,點選右下方的 [建立新的專案] 按鈕

  • 在 [建立新專案] 對話窗右半部

    • 切換 [所有語言 (L)] 下拉選單控制項為 [C#]
    • 切換 [所有專案類型 (T)] 下拉選單控制項為 [MAUI]
  • 在中間的專案範本清單中,找到並且點選 [Vulcan Custom Prism .NET MAUI App] 專案範本選項

    若沒有看到這個專案範本,請參考 使用 Vulcan.Maui.Template 專案範本來進行 MAUI for Prism 專案開發 文章,進行安裝這個專案範本到 Visual Studio 2022 內

  • 點選右下角的 [下一步] 按鈕

  • 在 [設定新的專案] 對話窗

  • 在 [專案名稱] 欄位內輸入 MA47 做為這個專案名稱

  • 請點選右下角的 [建立] 按鈕

  • 此時,將會建立一個可以用於 MAUI 開發的專案

設計 檢視模型 ViewModel 類別

  • 在專案內,打開 [ViewModels] > [MainPageViewModel.cs] 檔案
  • 移除原先專案範本所建立的檢視模型類別內不用的程式碼,完成結果如下所示
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;

namespace MA47.ViewModels;

public partial class MainPageViewModel : ObservableObject, INavigatedAware
{
    public MainPageViewModel()
    {
    }

    [ObservableProperty]
    string title = "MA47|使用者登入 MVVM";

    public void OnNavigatedFrom(INavigationParameters parameters)
    {
    }

    public void OnNavigatedTo(INavigationParameters parameters)
    {
    }
}
  • 這裡的程式碼將會是最基礎的檢視模型 ViewModel 類別會用到的程式碼,接下來就要來進行擴充這個類別
  • 找到 string title = "MA47|使用者登入 MVVM"; 敘述,在此下方加入此頁面會用到的資料綁定物件,這裡將會需要加入 [帳號]、[密碼]、[訊息] 這三個 欄位 Field 成員,程式碼如下
string account = string.Empty;
string password = string.Empty;
string message = string.Empty;
  • 注意事項,因為這裡使用 CommunityToolkit.Mvvm 這個套件做為簡化 MVVM 設計過程,此套件透過了 Roslyn Source Generators 原始碼產生器 功能,自動為這個專案產生出許多的程式碼,而這些程式碼將會於 MVVM 設計模式內會使用到的,透過這樣的功能,將會在進行 檢視模型 ViewModel 類別的時候,可以減少撰寫許多程式碼,讓整個 C# 類別變得更加清爽,當然,也就更加好維護。

  • 這裡加入三個類別成員,都是型別為字串的欄位成員,可以注意到的是

    • 這三個成員不需要為 公開 public ,因此,在這裡將會宣告為私有 private
    • 對於物件名稱,需要遵循 .NET C# 欄位命名慣例,這部分請參考 C# 編碼慣例 也就是不能夠採用 Pascal 命名法的大小寫 命名做法,而是要採用 Camel 大小寫 命名方式,也就是第一個字母必須為小寫,否則編譯器將產生錯誤

      要這麼做的原因除了因為這個成員是個欄位,本來就需要採用以小寫為開頭的命名作法,另外一個原因在於當使用了 [ObservableProperty] 這個屬性,宣告這個欄位成員將會用於 MVVM 資料綁定之用,此時,編譯器將會自動生成該欄位的 屬性 Property 成員,當然,這個屬性將會採用 Pascal 命名法,第一個字母將會是大小。

  • 在這三個欄位成員前面,分別加入 [ObservableProperty]` 這個屬性宣告,讓編譯器自動產生用於資料綁定機制需要用的程式碼與屬性成員。

  • 完成的程式碼如下

[ObservableProperty]
string account = string.Empty;
[ObservableProperty]
string password = string.Empty;
[ObservableProperty]
string message = string.Empty;
  • 最後,要加入一個方法,用於當使用點選螢幕上 [登入] 按鈕之後,需要在螢幕上顯示一段文字
  • 請在該類別內加入此方法
void Login()
{
    Message = $"輸入帳號:{Account} , 密碼:{Password}";
}
  • 因為在此使用 MVVM 設計模式,因此,在設計 C# 程式碼的時候,無須關注 檢視/頁面 內的 XAML 標記,或者稱之為使用者控制項,使用者在螢幕上輸入帳號或者密碼之後,將會自動儲存到剛剛設計的 account 與 password 欄位成員內。

  • 在這個 [Login()] 方法內,僅有一個敘述 Message = $"輸入帳號:{Account} , 密碼:{Password}" ,這表示要將使用者輸入的帳號與密碼內容,組合成為一個字串,並且指定給 [Message] 這個屬性。

    特別注意,除了在宣告資料的時候,要使用欄位成員方式進行設計,其他的時候,若要使用這些具有資料綁定成員的時候,請一定需要使用 屬性 Property (這些屬性是由編譯器自動產生的)來操作,否則,將會發生與造成莫名異常現象問題

  • 請在這個方法上方,加入 [RelayCommand] 這個屬性宣告,標示這個方法可以用於命令綁定

  • 完成後的程式碼如下

[RelayCommand]
void Login()
{
    Message = $"輸入帳號:{Account} , 密碼:{Password}";
}
  • 這裡的方法也不需要宣告為 public 公開

  • 加入了 [RelayCommand] 屬性之後,編譯器將會自動生成一段原始碼,將會產生出一個型別為 [RelayCommand] 的物件 (此物件將會實作 ICommand 這個介面),因為有實作 [ICommand] 介面,因此,這個物件便可以用於 XAML 內的命令綁定之用。

    編譯器產生的 [RelayCommand] 物件,將會使用該函式名稱附加上 Command 這個文字,以這裡的例子而言,所產生的 [RelayCommand] 物件的名稱將會是 [LoginCommand],因此,在 XAML 進行命令綁定的時候,需要使用這個 [LoginCommand] 物件名稱。

  • 底下為最終完成的 ViewModel 類別程式碼

using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;

namespace MA47.ViewModels;

public partial class MainPageViewModel : ObservableObject, INavigatedAware
{
    public MainPageViewModel()
    {
    }

    [ObservableProperty]
    string title = "MA47|使用者登入 MVVM";
    [ObservableProperty]
    string account = string.Empty;
    [ObservableProperty]
    string password = string.Empty;
    [ObservableProperty]
    string message = string.Empty;

    [RelayCommand]
    void Login()
    {
        Message = $"輸入帳號:{Account} , 密碼:{Password}";
    }

    public void OnNavigatedFrom(INavigationParameters parameters)
    {
    }

    public void OnNavigatedTo(INavigationParameters parameters)
    {
    }
}

設計 檢視 View (也就是 頁面) XAML 檔案

  • 在專案內,打開 [Views] > [MainPage.xaml] 檔案
  • 移除原先專案範本所建立的檢視檔案內不用的標記,完成結果如下所示
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             Title="{Binding Title}"
             x:Class="MA47.Views.MainPage"
             xmlns:viewModel="clr-namespace:MA47.ViewModels"
             x:DataType="viewModel:MainPageViewModel">

  <ScrollView>
  </ScrollView>

</ContentPage>
  • 將 [ScrollView] 標記修正為底下內容
<ScrollView>
      <VerticalStackLayout
          Margin="20">
          <Label Text="帳號"/>
          <Entry Text="{Binding Account}"/>
          <Label Text="密碼"
                 Margin="0,20,0,0"/>
          <Entry Text="{Binding Password}"
                 IsPassword="True"/>
          <Label Text="{Binding Message}"
                 Margin="0,20,0,0"
                 FontSize="20"
                 TextColor="Red"/>
          <Button Text="登入"
                  Margin="0,40,0,0"
                  Command="{Binding LoginCommand}"/>
      </VerticalStackLayout>
</ScrollView>
  • 在帳號 [Entry] 標記項目 Element (也可以稱之為 控制項 Control) 的 [Text] 屬性內,使用 {Binding ...} 這樣的取用 XAML 標記延伸 Markup Extnesion 用法,宣告這個 [Entry] 項目的 [Text] 屬性,將會使用資料綁定的方式,將 [Text] 屬性與 ViewModel 內的 [Account] 屬性進行資料綁定連結,有了這樣的宣告,一旦使用者在此頁面的帳號欄位輸入任何文字,便可以在 ViewModel 內,透過 [Account] 這個 ViewModel 類別屬性成員,取得使用輸入的文字內容。

  • 同樣的將密碼 [Entry] 標記項目 Element 的 [Text] 屬性與 ViewModel 內的 [Password] 屬性,使用 Text="{Binding Password}" 方式,進行資料綁定宣告

  • 對於訊息 [Label] 項目,則是將其 [Text] 屬性與 ViewModel 內的 [Message] 屬性,宣告要進行資料綁定,如此,當在 ViewModel 內有變更 [Message] 屬性物件文字內容的時候,透過 MVVM 所提供的機制,螢幕上將會自動顯示出該文字內容。

  • 最後,對於 [Button] 項目,透過 [Command] 屬性與 ViewModel 內型別為 [RelayCommand] 的 [LoginCommand] 物件,使用 Command="{Binding LoginCommand}" 標記宣告,進行命令的綁定;當使用者在螢幕上點選這個按鈕之後,將會自動觸發 ViewModel 內的 [Login()] 方法

  • 在 [Login()] 方法內,將會變更 [Message] 文字內容,如此將會造成螢幕上顯示出最新的 [Message] 文字內容

  • 底下將會是完成後的頁面 XAML 檔案內容

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             Title="{Binding Title}"
             x:Class="MA47.Views.MainPage"
             xmlns:viewModel="clr-namespace:MA47.ViewModels"
             x:DataType="viewModel:MainPageViewModel">

    <ScrollView>
        <VerticalStackLayout
            Margin="20">
            <Label Text="帳號"/>
            <Entry Text="{Binding Account}"/>

            <Label Text="密碼"
                   Margin="0,20,0,0"/>
            <Entry Text="{Binding Password}"
                   IsPassword="True"/>

            <Label Text="{Binding Message}"
                   Margin="0,20,0,0"
                   FontSize="20"
                   TextColor="Red"/>

            <Button Text="登入"
                    Margin="0,40,0,0"
                    Command="{Binding LoginCommand}"/>
        </VerticalStackLayout>
    </ScrollView>

</ContentPage>

執行結果

  • 切換到 [Windows Machine] 模式,開始執行此專案,將會看到底下結果

    請在 帳號 與 密碼 欄位輸入任何文字,再點選 [登入] 按鈕,就會看到底下畫面

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

    請在 帳號 與 密碼 欄位輸入任何文字,再點選 [登入] 按鈕,就會看到底下畫面

 




2023年1月26日 星期四

使用 Vulcan.Maui.Template 專案範本來進行 MAUI for Prism 專案開發

使用 Vulcan.Maui.Template 專案範本來進行 MAUI for Prism 專案開發


早期在進行 Xamarin.Forms 專案開發的時候,通常會搭配 Prism 這套開發框架來進行 MVVM 設計模式的程式碼開發,由於 Prism 是一個 Framework 開發框架,因此,當要第一次建立專案的時候,若採用 Xamarin.Forms 所提供的預設專案範本所建立的專案,將會需要做許多的修正,才能夠在專案內使用 Prism 開發框架所提供的功能;然而,Prism 團隊也提供了一個 Prism Template Pack 擴充工具,可以安裝在 Visual Studio IDE 內,一旦想要進行 Xamarin.Forms 專案開發的時候,便可以透過 Prism Template Pack 所提供的專案範本來進行專案開發。

隨著 MAUI 開發工具的到來,Prism Template Pack 並沒有隨之提供最新版本的 MAUI 支援,想要在 MAUI 專案內使用 Prism 所提供的功能,需要安裝 Prism.Maui 這個套件,當然,同樣的也需要將使用原生 MAUI 專案範本所建立的專案,進行調整。

Prism 開發團隊內的 Dan Siegel 提供了一個暫時性的替代方案,那就是 Prism.Templates ,安裝好這個套件之後,就會在 Visual Studio 2022 開發工具內,出現可以使用 Prism 來開發 MAUI 的專案範本,可是, [Prism.Maui] 這個套件有升級,且使用方式有所不同,而且是會造成 Break Change ,可是,使用 [Prism.Templates] 所建立的專案範本卻沒有隨之升級,造成每次建立新的 MAUI 專案的時候,為了使用最新的 [Prism.Maui] 套件所提供的最新功能,又需要手動進行變更專案內的原始碼,使用起來相當的不方便。

有鑑於此,我自己進行開發出的可以採用最新 [Prism.Maui] 套件的專案範本套件,也就是 Vulcan.Maui.Template ,透過這個套件所產生出來的專案原始碼,已經預設安裝好 CommunityToolkit.Mvvm 這個套件來進行 MVVM 的程式碼設計 (之前都是使用 PropertyChanged.Fody 這個套件來支援 MVVM 的開發),使用起來便會更加方便了。

安裝

  • 打開命令提示字元視窗
  • 輸入 dotnet new install Vulcan.Maui.Template
  • 底下將會是解除安裝過程出現的內容
C:\>dotnet new install Vulcan.Maui.Template
將安裝下列範本套件:
   Vulcan.Maui.Template

成功: Vulcan.Maui.Template::0.2.2 已安裝下列範本:
範本名稱                                 簡短名稱     語言  標記
---------------------------------------  -----------  ----  -------------------------------------------
Vulcan Custom Full Prism .NET MAUI App   Full-Maui    [C#]  MAUI/Android/iOS/macOS/Mac Catalyst/Windows
Vulcan Custom Prism .NET MAUI App        Vulcan-Maui  [C#]  MAUI/Android/iOS/macOS/Mac Catalyst/Windows
Vulcan Custom Prism View and View Model  MVVMItem     [C#]  MAUI/Android/iOS/macOS/Mac Catalyst/Windows

解除安裝

  • 打開命令提示字元視窗
  • 輸入 dotnet new uninstall Vulcan.Maui.Template
  • 底下將會是解除安裝過程出現的內容
C:\>dotnet new uninstall Vulcan.Maui.Template
成功: Vulcan.Maui.Template::0.2.2 已解除安裝。

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

  • 打開 Visual Studio 2022 IDE 應用程式

  • 從 [Visual Studio 2022] 對話窗中,點選右下方的 [建立新的專案] 按鈕

  • 在 [建立新專案] 對話窗右半部

    • 切換 [所有語言 (L)] 下拉選單控制項為 [C#]
    • 切換 [所有專案類型 (T)] 下拉選單控制項為 [MAUI]
  • 在中間的專案範本清單中,找到並且點選 [Vulcan Custom Prism .NET MAUI App] 專案範本選項

    Visual Studio 2022 建立新專案

  • 點選右下角的 [下一步] 按鈕

  • 在 [設定新的專案] 對話窗

    Visual Studio 2022 設定新的專案

  • 請點選右下角的 [建立] 按鈕

  • 此時,將會建立一個可以用於 MAUI 開發的專案

專案結構內容說明

  • 底下是建立好的專案結構螢幕截圖

  • 在此專案內,已經預先建立好 [Views] 與 [ViewModels] 這兩個資料夾

  • 底下將會是 [MauiProgram.cs] 的檔案內容

using Prism.Ioc;
using Project5.ViewModels;
using Project5.Views;

namespace Project5;

public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
    {
        var builder = MauiApp.CreateBuilder();
        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();
    }
}
  • 這個專案將會使用 [Prism.DryIoc.Maui] 8.1.273-pre 這個現在最新的本版

  • 在 [CreateMauiApp] 方法內,將會使用 [UsePrism] 這個擴充方法來進行對 Prism 開發框架的宣告

  • 從 [RegisterTypes] 這個委派方法內,將會進行此專案會用到的各種服務、頁面的 IoC 容器註冊動作

  • 從 [OnAppStart] 這個委派方法內,將會看到在此設定需要第一個開啟的頁面

  • 底下將會是 [MainPage.xaml] 的檔案內容

  • 在這裡加入一個 [viewModel] 命名空間宣告與使用 [x:DataType] 宣告需要使用編譯時期的資料綁定行為

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             Title="{Binding Title}"
             x:Class="Project5.Views.MainPage"
             xmlns:viewModel="clr-namespace:Project5.ViewModels"
             x:DataType="viewModel:MainPageViewModel">

  <ScrollView>
    <VerticalStackLayout
            Spacing="25"
            Padding="30,0"
            VerticalOptions="Center">

      <Image Source="prism.png"
             SemanticProperties.Description="Cute dot net bot waving hi to you!"
             HeightRequest="150"
             HorizontalOptions="Center" />

      <Label Text="Hello, World!"
             SemanticProperties.HeadingLevel="Level1"
             FontSize="32"
             HorizontalOptions="Center" />

      <Label Text="Welcome to Prism for .NET MAUI"
             SemanticProperties.HeadingLevel="Level2"
             SemanticProperties.Description="Welcome to Prism for dot net Multi platform App U I"
             FontSize="18"
             HorizontalOptions="Center" />

      <Button Text="{Binding Text}"
              SemanticProperties.Hint="Counts the number of times you click"
              Command="{Binding CountCommand}"
              HorizontalOptions="Center" />

    </VerticalStackLayout>
  </ScrollView>

</ContentPage>
  • 在這裡加入一個 [viewModel] 命名空間宣告與使用 [x:DataType] 宣告需要使用編譯時期的資料綁定行為

  • 底下將會是 [MainPage.xaml] 的檔案內容

using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;

namespace Project5.ViewModels;

public partial class MainPageViewModel : ObservableObject, INavigatedAware
{
    private int _count;

    public MainPageViewModel()
    {
    }

    [ObservableProperty]
    string title = "Main Page";

    [ObservableProperty]
    string text = "Click me";



    [RelayCommand]
    private void Count()
    {
        _count++;
        if (_count == 1)
            Text = "Clicked 1 time";
        else if (_count > 1)
            Text = $"Clicked {_count} times";
    }

    public void OnNavigatedFrom(INavigationParameters parameters)
    {
    }

    public void OnNavigatedTo(INavigationParameters parameters)
    {
    }
}
  • 從最上方看出,這個 ViewModel 類別內,已經加入 Microsoft MVVM Toolkit 的命名空間宣告
  • 這個 [MainPageViewModel] 類別也繼承了 [ObservableObject] 類別,並且該類別也宣告了 [partial],表示了在這個類別內,可以開始使用 Microsoft MVVM Toolkit 所提供的功能
  • 若想要設計一個資料綁定 Data Binding 的屬性,在這個類別內僅需要以 欄位 Field 的方式來設計此一成員,並且此欄位的名稱,需要符合欄位名稱命名慣例,也就是第一個字需要為小寫,最後,在該欄位上方加入 [ObservableProperty] 這個屬性宣告,如此,編譯器所觸發的原始碼產生器,將會自動產生出一個 屬性 Property 成員出來,並且也會實作出 [INotifyPropertyChanged] 會用到的各種方法
  • 對於想要設計一個可用於命令綁定的物件,在此僅需要設計一個方法(並不一定需要 public),接著,在此方法上方加入 [RelayCommand] 這個屬性宣告,如此,編譯器所觸發的原始碼產生器,將會自動產生出一個以這個方法名稱加入 [Command] 的 RelayCommand 物件,有了這個物件,便可以在相對應的頁面上,使用這個物件來進行命令的綁定。

執行結果

  • 切換到 [Windows Machine] 模式,開始執行此專案,將會看到底下結果

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