2023年1月31日 星期二

.NET MAUI 用者登入:透過呼叫 Web API 成功取得存取權杖後,進行檔案讀寫操作

用者登入:透過呼叫 Web API 成功取得存取權杖後,進行檔案讀寫操作



在上一個動手實作練習中,修改最初的使用者要進行身分驗證的功能,當按下登入按鈕之後,就會呼叫遠端 RESTful Web API ,把帳號與密碼傳送過去,一旦所提供的身分憑證正確無誤,就會得到此使用者要使用的存取權杖,否則,將會得到相關的錯誤訊息。

在實際應用環境下,一旦取得了存取權杖之後,用戶端的程式需要將這個存取權杖儲存到本機環境內,不論這個 App 是否有重新啟動,日後都可以再將存取權杖重新讀取出來,用來呼叫受到保護的遠端 RESTful Web API。

在 .NET MAUI 開發框架內,有提供 檔案系統協助程式 API,透過這個 API 可以取得 [應用程式資料目錄] 路徑,如此便可以使用 .NET BCL API 的 [File.ReadAllTextAsync 方法] 或 [File.WriteAllTextAsync 方法] 來進行文字內容的讀取與寫入需求,當然,所取得的 [LoginResponseDto] 物件,可以使用 Json.NET 套件進行 JSON 序列化處理,便可以得到這個物件的 JSON 文字內容,緊接著寫入到行動裝置系統內;當要讀取此 JSON 物件的時候,透過讀取原先寫入到檔案的內容,取得當初寫入的文字內容,同樣的還是透過 Json.NET 套件來進行 JSON 物件反序列化處理,便可以得到原先的 [LoginResponseDto] .NET 物件了。

接下來看看如何進行這樣的程式設計

加入 LoginResponseDto 欄位成員

  • 在此將不會另外建立一個新的專案
  • 打開剛剛實作練習完成的專案
  • 在專案內,打開 [ViewModels] > [MainPageViewModel.cs] 檔案
  • 找到 string message = string.Empty; 敘述,在其下方加入宣告一個型別為 LoginResponseDto 欄位,修正後的程式碼如下
LoginResponseDto responseDto = new LoginResponseDto();
  • 這個 responseDto 物件,將會持有呼叫 RESTful Web API 成功後的結果值

加入可以讀取 LoginResponseDto 物件的方法

  • 找到 [async Task GetLoginTokenAsync()] 方法,在這個方法之後,加入一個類別要用到的非同步方法成員,程式碼如下
async Task<bool> ReadLoginResponseAsync()
{
    string filename = Path.Combine(FileSystem.Current.AppDataDirectory,
        "LoginResponse.dat");
    try
    {
        string responseDtoContext = await File.ReadAllTextAsync(filename);
        responseDto = JsonConvert.DeserializeObject<LoginResponseDto>(responseDtoContext);
        return true;
    }
    catch (Exception ex)
    {
        responseDto = new LoginResponseDto();
        return false;
    }
}
  • 這個讀取方法首先要建立該檔案的絕對路徑,這裡透過 [FileSystem.Current.AppDataDirectory] 取得該應用程式在行動裝置內儲存檔案的絕對路徑
  • 接著透過 [Path.Combine] 方法,將這個路徑與要寫入的檔案名稱組合成為一個絕對檔案路徑字串
  • 透過 .NET BCL 提供的 [File.ReadAllTextAsync] API,將這個檔案所有文字內容讀取出來,儲存在 [responseDtoContext] 區域變數內。
  • 此時,要將剛剛讀取出來的文字,也就是一個 JSON 物件內容,反序列化成為 .NET 物件,這裡使用到 [JsonConvert.DeserializeObject] 這個強型別反序列方法來做到
  • 反序列化的物件將會儲存到 [responseDto] 這個類別欄位成員內
  • 若整個過程沒有發生例外異常,則會回傳 true,否則會回傳 false

加入可以寫入 LoginResponseDto 物件的方法

  • 找到剛剛完成的 [ReadLoginResponseAsync()] 方法,在這個方法之後,加入一個類別要用到的非同步方法成員,程式碼如下
async Task<bool> WriteLoginResponseAsync()
{
    string filename = Path.Combine(FileSystem.Current.AppDataDirectory,
        "LoginResponse.dat");
    try
    {
        string responseDtoContext = JsonConvert.SerializeObject(responseDto);
        await File.WriteAllTextAsync(filename, responseDtoContext);
        return true;
    }
    catch (Exception ex)
    {
        return false;
    }
}
  • 在這個方法 [WriteLoginResponseAsync] 內,同樣的要先產生要寫入檔案的路徑內容
  • 這裡透過 [FileSystem.Current.AppDataDirectory] 取得該應用程式在行動裝置內儲存檔案的絕對路徑
  • 接著透過 [Path.Combine] 方法,將這個路徑與要寫入的檔案名稱組合成為一個絕對檔案路徑字串
  • 接下來要將準備寫入檔案的 [responseDto] .NET 物件,序列化成為 JSON 物件,也就是一串文字內容
  • 這裡透過 [JsonConvert.SerializeObject] API ,傳入 [responseDto] 物件,便會得到 JSON 文字內容,這個 JSON 物件將會暫時存放於 [responseDtoContext] 區域變數內
  • 透過 .NET BCL 提供的 [File.WriteAllTextAsync] API,將檔案名稱與 JSON 物件傳入到這個 API 內,如此,將會把所有文字內容寫入到指定檔案內。
  • 若整個過程沒有發生例外異常,則會回傳 true,否則會回傳 false

修正 Login 方法,使其可以將 JWT 存取權杖寫入到檔案內

  • 找到 [async Task Login()] 方法
  • 在 [responseDto = JsonConvert.DeserializeObject(apiResult.Payload.ToString());] 方法之後,加入底下敘述
await WriteLoginResponseAsync();
  • 這裡將會呼叫 [WriteLoginResponseAsync] 方法,將 [LoginResponseDto] .NET 物件,序列化成為 JSON 物件,接著寫入到指定檔案內

加入從檔案讀取 JWT 存取權杖的命令用方法

  • 找到 [async Task Login()] 方法
  • 在其下方加入一個命令使用的委派方法,其程式碼如下
[RelayCommand]
async Task GetLoginTokenAsync()
{
    var success = await ReadLoginResponseAsync();
    if(success == true)
    {
        Message = responseDto.Token;
    }
    else
    {
        Message = $"無法讀取出存取權杖";
    }
}
  • 透過 [RelayCommand] 屬性的宣告,使得可以在 XAML 頁面中,使用命令綁定方式來綁定到這個 [GetLoginTokenAsync] 方法上
  • 所以當使用者在螢幕上點選了一個按鈕,而該按鈕有綁定這個命令物件,此時,這個 [GetLoginTokenAsync] 就會執行
  • 首先將會呼叫 [ReadLoginResponseAsync] 方法,從特定檔案內讀取出含有 JWT 存取權杖的 JSON 文字物件出來,接著使用 JSON 反序列化方法產生出一個 .NET 物件
  • 若該方法回傳 true ,這表示含有 JWT 存取權杖的物件已經成功還原回來,並且儲存在 [respnseDto] 欄位成員內,此時將存取權杖的文字內容指派給 [Message] 屬性,透過資料綁定的機制運作,螢幕上就會看到這個存取權杖內容了。
  • 底下將會是完整的 ViewModel 程式碼
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using MA47.Dtos.Models;
using Newtonsoft.Json;
using System.Net.Http.Json;

namespace MA47.ViewModels;

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

    [ObservableProperty]
    string title = "MA49|使用者登入:存取權杖檔案讀寫";
    [ObservableProperty]
    string account = string.Empty;
    [ObservableProperty]
    string password = string.Empty;
    [ObservableProperty]
    string message = string.Empty;
    LoginResponseDto responseDto = new LoginResponseDto();

    [RelayCommand]
    async Task Login()
    {
        APIResult apiResult = await UserAuthenticationAsync();
        if (apiResult.Status == true)
        {
            responseDto = JsonConvert
                .DeserializeObject<LoginResponseDto>(apiResult.Payload.ToString());
            await WriteLoginResponseAsync();
            Message = $"Token:{responseDto.Token}";
        }
        else
        {
            Message = $"錯誤訊息:{apiResult.Message}";
        }
    }

    [RelayCommand]
    async Task GetLoginTokenAsync()
    {
        var success = await ReadLoginResponseAsync();
        if(success == true)
        {
            Message = responseDto.Token;
        }
        else
        {
            Message = $"無法讀取出存取權杖";
        }
    }


    async Task<bool> ReadLoginResponseAsync()
    {
        string filename = Path.Combine(FileSystem.Current.AppDataDirectory,
            "LoginResponse.dat");
        try
        {
            string responseDtoContext = await File.ReadAllTextAsync(filename);
            responseDto = JsonConvert.DeserializeObject<LoginResponseDto>(responseDtoContext);
            return true;
        }
        catch (Exception ex)
        {
            responseDto = new LoginResponseDto();
            return false;
        }
    }

    async Task<bool> WriteLoginResponseAsync()
    {
        string filename = Path.Combine(FileSystem.Current.AppDataDirectory,
            "LoginResponse.dat");
        try
        {
            string responseDtoContext = JsonConvert.SerializeObject(responseDto);
            await File.WriteAllTextAsync(filename, responseDtoContext);
            return true;
        }
        catch (Exception ex)
        {
            return false;
        }
    }

    public void OnNavigatedFrom(INavigationParameters parameters)
    {
    }

    public void OnNavigatedTo(INavigationParameters parameters)
    {
    }

    async Task<APIResult> UserAuthenticationAsync()
    {
        APIResult apiResult = null;
        LoginRequestDto loginRequestDto = new LoginRequestDto()
        {
            Account = Account,
            Password = Password,
        };

        HttpClient client = new HttpClient();
        client.BaseAddress = new Uri("https://blazortw.azurewebsites.net");
        HttpResponseMessage httpResponse = await client.PostAsJsonAsync("/api/Login", loginRequestDto);
        if (httpResponse.IsSuccessStatusCode)
        {
            apiResult = await httpResponse.Content.ReadFromJsonAsync<APIResult>();
            return apiResult;
        }
        else
        {
            apiResult = await httpResponse.Content.ReadFromJsonAsync<APIResult>();
            return apiResult;
        }
    }
}

加入一個讀取存取權杖的按鈕到頁面上

  • 在專案內,打開 [Views] > [MainPage.xaml] 檔案
  • 找到 <Button Text="登入" Margin="0,40,0,0" Command="{Binding LoginCommand}"/> 宣告標記
  • 在其下方加入另外一個按鈕
<Button Text="讀取存取權杖"
        Margin="0,40,0,0"
        Command="{Binding GetLoginTokenCommand}"/>
  • 底下將會是整個頁面的 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}"/>
            <Button Text="讀取存取權杖"
                    Margin="0,40,0,0"
                    Command="{Binding GetLoginTokenCommand}"/>
        </VerticalStackLayout>
    </ScrollView>

</ContentPage>

執行結果

  • 切換到 [Android Emulator] 模式,選擇一個適合的模擬器,開始執行此專案

  • 現在將會看到螢幕上多了一個 [讀取存取權杖] 按鈕

  • 請在 帳號 與 密碼 欄位輸入正確的使用者憑證,再點選 [登入] 按鈕,就會看到底下成功登入後取得 JWT 存取權杖的畫面

    合法的使用者憑證為

    帳號:god

    密碼:123

  • 現在,可以關閉這個應用程式,接著重新執行此專案

  • 當畫面出現後, [讀取存取權杖] 按鈕,便會看到剛剛寫入的存取權杖了

 






2023年1月30日 星期一

.NET MAUI 使用者登入:使用 HttpClient 呼叫身分認證 RESTful Web API,並且取得 JWT 存取權杖

使用者登入:使用 HttpClient 呼叫身分認證 RESTful Web API,並且取得 JWT 存取權杖



在上一個動手實作中,使用了 [Vulcan Custom Prism .NET MAUI App] 專案範本來建立了一個具有使用者登入功能的 App,該 App 是採用了 Microsoft MVVM Toolkit 這個套件來進行與使用 MVVM 設計模式進行專案開發。

不過,這個練習專案僅可以接受使用將要進行身分驗證的帳號與密碼輸入到 App 內,然而,一般這類應用開發過程中,將會需要把這個使用診憑證傳送到遠端 RESTful Web API 主機內,若所提供的帳號與密碼是正確的,此時將會通過身分驗證程序,後端 Web API 將會根據此憑證資訊,產生出相對應的 JWT 權杖 Token ,並且回傳到呼叫用戶端。

因此,這裡將會接續上一個手動實作練習,繼續進行專案開發,完成這個練習專案程式設計,使其具有取得合法與有效的 JWT 存取權杖 Access Token 能力。

加入 資料傳輸物件 Data Transfer Object 類別

建立需要用到的資料夾

  • 在此將不會另外建立一個新的專案

  • 打開剛剛實作練習完成的 [MA47] 專案

  • 下圖將會是 [MA47] 專案結構螢幕截圖

  • 接下來會先要建立用與後端 RESTful Web API 通訊用到的資料傳輸物件類別,這裡將會包含要進行登入驗證用的憑證資訊、取得 Web API 回傳結果的標準格式類別、和通過身分驗證後的類別

  • 滑鼠右擊專案節點

  • 從彈出功能表中,點選 [加入] > [新增資料夾]

  • 當新增資料夾建立完成後,重新命名此資料夾名稱為 Dtos

  • 滑鼠右擊 [Dtos] 資料夾節點

  • 從彈出功能表中,點選 [加入] > [類別]

  • 當新增資料夾建立完成後,重新命名此資料夾名稱為 Models

建立登入請求 Request 資料模型

  • 滑鼠右擊專案 [Dtos] > [Models] 節點
  • 從彈出功能表中,點選 [加入] > [類別]
  • 當 [新增項目] 對話窗出現之後
  • 在名稱欄位內輸入 [LoginRequestDto.cs]
  • 點選對話窗右下角的 [新增] 按鈕
  • 當這個 [LoginRequestDto] 類別視窗出現之後,使用底下程式碼替換掉原先產生出來的程式碼
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace MA47.Dtos.Models;

public class LoginRequestDto : ICloneable, INotifyPropertyChanged
{
    [Required]
    public string Account { get; set; }
    [Required]
    public string Password { get; set; }

    #region 介面實作
    public event PropertyChangedEventHandler PropertyChanged;

    public LoginRequestDto Clone()
    {
        return ((ICloneable)this).Clone() as LoginRequestDto;
    }
    object ICloneable.Clone()
    {
        return this.MemberwiseClone();
    }
    #endregion
}
  • 原則上,這裡新增的類別會是一個 POCO 類別,裡面包含了兩個 屬性 Property,分別是 Account 帳號 與 Password 密碼,這兩個屬性將會用於將使用者輸入過的帳號與密碼,傳遞到遠端 RESTful Web API 主機服務內

  • 不過,在這裡也因為一些需要,需要在這個類別內實作出很基本的 [ICloneable] 與 [INotifyPropertyChanged] 這兩個介面

    對於想要了解如何實作出具有強行別的物件複製方法,可以參考 ICloneable 介面,對於 INotifyPropertyChanged 介面 則是會用於 MVVM 設計模式中,用來通知用戶端已變更屬性值功能。

建立登入回應 Response 資料模型

  • 滑鼠右擊專案 [Dtos] > [Models] 節點
  • 從彈出功能表中,點選 [加入] > [類別]
  • 當 [新增項目] 對話窗出現之後
  • 在名稱欄位內輸入 [LoginResponseDto.cs]
  • 點選對話窗右下角的 [新增] 按鈕
  • 當這個 [LoginResponseDto] 類別視窗出現之後,使用底下程式碼替換掉原先產生出來的程式碼
using System.ComponentModel;

namespace MA47.Dtos.Models;

public class LoginResponseDto : ICloneable, INotifyPropertyChanged
{
    public int Id { get; set; }
    public string Account { get; set; }
    public string Name { get; set; }
    public string Token { get; set; }
    public int TokenExpireMinutes { get; set; }
    public string RefreshToken { get; set; }
    public int RefreshTokenExpireDays { get; set; }

    #region 介面實作
    public event PropertyChangedEventHandler PropertyChanged;

    public LoginResponseDto Clone()
    {
        return ((ICloneable)this).Clone() as LoginResponseDto;
    }
    object ICloneable.Clone()
    {
        return this.MemberwiseClone();
    }
    #endregion
}
  • 在這個類別類建立了許多屬性,這些屬性將是當使用者成功通過身分驗證之後,並且產生出相對應的 JWT 存取權杖 Access Token 與 更新權杖 Refresh Token ,此時將會需要將特定資訊寫入到這些屬性內,並且回傳到用戶端內。
  • 其中 [Token] 將會是告知 JWT 存取權杖的內容是甚麼
  • [RefreshToken] 將會是告知 JWT 更新權杖的內容是甚麼

建立呼叫 Web API 的制式回應格式資料模型

  • 滑鼠右擊專案 [Dtos] > [Models] 節點
  • 從彈出功能表中,點選 [加入] > [類別]
  • 當 [新增項目] 對話窗出現之後
  • 在名稱欄位內輸入 [APIResult.cs]
  • 點選對話窗右下角的 [新增] 按鈕
  • 當這個 [APIResult] 類別視窗出現之後,使用底下程式碼替換掉原先產生出來的程式碼
using System.ComponentModel;

namespace MA47.Dtos.Models;

/// <summary>
/// 呼叫 API 回傳的制式格式
/// </summary>
public class APIResult : ICloneable, INotifyPropertyChanged
{
    /// <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 event PropertyChangedEventHandler PropertyChanged;

    public APIResult Clone()
    {
        return ((ICloneable)this).Clone() as APIResult;
    }
    object ICloneable.Clone()
    {
        return this.MemberwiseClone();
    }
    #endregion
}
  • 每次用戶端呼叫遠端 Web API,不論是否可以正常執行完畢或者成功與否,都希望能夠回傳一個標準與制式的格式,而這個類別所定義的屬性,將會滿足這樣的需求。

  • 這個類別內已經加入相關程式碼備註說明,可以說相當的容易了解

  • 在這裡特別說明三個屬性,經常在完成 API 呼叫之後,將會透過 [Status] 屬性的檢查,確認此次 Web API 呼叫結果是否已經成功且正確執行完畢,這可以透過其布林型別的物件值來確認

  • 一旦呼叫結果是成功的,呼叫此 Web API 要回傳的 JSON 物件,將會儲存在 [Payload] 這個屬性內,取得這個屬性值,再透過 JSON 反序列化動作,便可以取得其 .NET 物件值。

  • 若呼叫結果不成功,相對應的錯誤訊息將會記錄在 [Message] 屬性內,此時可以顯示這個屬性文字內容到螢幕上,告知用戶發生了甚麼事情,又或者記錄到日誌檔案內。

  • 底下將會是完成後的專案結構螢幕截圖

設計 檢視模型 ViewModel 類別

  • 在專案內,打開 [ViewModels] > [MainPageViewModel.cs] 檔案
  • 找到 Login() 方法,將其修正為底下的非同步方法,修正後的程式碼如下
async Task Login()
{
    APIResult apiResult = await UserAuthenticationAsync();
    if (apiResult.Status == true)
    {
        LoginResponseDto loginResponseDto = JsonConvert
            .DeserializeObject<LoginResponseDto>(apiResult.Payload.ToString());
        Message = $"Token:{loginResponseDto.Token}";
    }
    else
    {
        Message = $"錯誤訊息:{apiResult.Message}";
    }
}
  • 這裡,因為該 Login 方法內有使用到 await 運算子,所以,需要將該方法的回傳 void 宣告,修改成為 async Task,這樣的設計動作將會形成一個非同步方法。

  • 在此方法內,將會呼叫 [UserAuthenticationAsync] ,這個方法將會呼叫 Web API 服務,不論成功與否,都會回傳型別為 [APIResult] 的物件。

  • 透過檢查 APIResult.Status 這個布林值,若此次呼叫 Web API 是成功的,也就是此次使用者輸入的帳號與密碼是正確無誤的,透過 [JsonConvert.DeserializeObject()] 方法,把 [apiResult.Payload.ToString()] 回傳 JSON 物件進行反序列化,此時,就會得到型別為 [LoginResponseDto] 物件

  • 接下來就可以將 [LoginResponseDto.Token] 這個屬性值指派給這個 ViewModel 內的 [Message] 屬性,此時,透過 MVVM 內的 資料綁定 Data Binding 機制,螢幕上就會看到這次取的存取權杖內容了。

  • 在這個類別內加入底下的 [UserAuthenticationAsync] 非同步方法定義

async Task<APIResult> UserAuthenticationAsync()
{
    APIResult apiResult = null;
    LoginRequestDto loginRequestDto = new LoginRequestDto()
    {
        Account = Account,
        Password = Password,
    };
    HttpClient client = new HttpClient();
    client.BaseAddress = new Uri("https://blazortw.azurewebsites.net");
    HttpResponseMessage httpResponse = await client.PostAsJsonAsync("/api/Login", loginRequestDto);
    if (httpResponse.IsSuccessStatusCode)
    {
        apiResult = await httpResponse.Content.ReadFromJsonAsync<APIResult>();
        return apiResult;
    }
    else
    {
        apiResult = await httpResponse.Content.ReadFromJsonAsync<APIResult>();
        return apiResult;
    }
}
  • 在這個 [UserAuthenticationAsync] 方法內,首先建立一個 [LoginRequestDto] 型別的物件,並且指定使用者輸入的帳號與密碼到這個物件內的屬性內。
  • 接著,建立一個 [HttpClient] 型別的物件
  • 使用 HttpClient.BaseAddress 屬性 設定傳送要求時所使用之網際網路資源的統一資源識別元 (URI) 基底位址
  • 之後,使用 [PostAsJsonAsync] 方法對遠端 [/api/Login] 服務端點進行 [Post] 請求,把 [LoginRequestDto] 這個物件之 JSON 內容,傳送到遠端的 RESTful Web API 內,這個 [PostAsJsonAsync] 方法將會回傳一個 [HttpResponseMessage] 物件
  • 現在可以判斷此次 HTTP Post 呼叫動作的回傳狀態值是否為成功的狀態
  • 不論是否為成功狀態,透過 [HttpResponseMessage.Content.ReadFromJsonAsync()] 方法呼叫,取得 HTTP 回應的 Payload 內容,並且反序列化此 JSON 物件成為 [APIResult] 型別的物件
  • 此時,便可以將此物件回傳回去
  • 底下為最終完成的 ViewModel 類別程式碼
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using MA47.Dtos.Models;
using Newtonsoft.Json;
using System.Net.Http.Json;

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]
    async Task Login()
    {
        APIResult apiResult = await UserAuthenticationAsync();
        if (apiResult.Status == true)
        {
            LoginResponseDto loginResponseDto = JsonConvert
                .DeserializeObject<LoginResponseDto>(apiResult.Payload.ToString());
            Message = $"Token:{loginResponseDto.Token}";
        }
        else
        {
            Message = $"錯誤訊息:{apiResult.Message}";
        }
    }

    public void OnNavigatedFrom(INavigationParameters parameters)
    {
    }

    public void OnNavigatedTo(INavigationParameters parameters)
    {
    }

    async Task<APIResult> UserAuthenticationAsync()
    {
        APIResult apiResult = null;
        LoginRequestDto loginRequestDto = new LoginRequestDto()
        {
            Account = Account,
            Password = Password,
        };

        HttpClient client = new HttpClient();
        client.BaseAddress = new Uri("https://blazortw.azurewebsites.net");
        HttpResponseMessage httpResponse = await client.PostAsJsonAsync("/api/Login", loginRequestDto);
        if (httpResponse.IsSuccessStatusCode)
        {
            apiResult = await httpResponse.Content.ReadFromJsonAsync<APIResult>();
            return apiResult;
        }
        else
        {
            apiResult = await httpResponse.Content.ReadFromJsonAsync<APIResult>();
            return apiResult;
        }
    }
}

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

  • 在這個實作練習中,將不需要對 [Views] > [MainPage.xaml] 檔案進行任何修正

執行結果

  • 切換到 [Windows Machine] 模式,開始執行此專案

  • 請在 帳號 與 密碼 欄位輸入正確的使用者憑證,再點選 [登入] 按鈕,就會看到底下成功登入後取得 JWT 存取權杖的畫面

    合法的使用者憑證為

    帳號:god

    密碼:123

  • 現在,請在 帳號 與 密碼 欄位輸入不正確的使用者憑證,再點選 [登入] 按鈕,就會看到底下無法成功通過身分驗證訊息的畫面

  • 切換到 [Android Emulator] 模式,選擇一個適合的模擬器,開始執行此專案

  • 請在 帳號 與 密碼 欄位輸入正確的使用者憑證,再點選 [登入] 按鈕,就會看到底下成功登入後取得 JWT 存取權杖的畫面

    合法的使用者憑證為

    帳號:god

    密碼:123

  • 現在,請在 帳號 與 密碼 欄位輸入不正確的使用者憑證,再點選 [登入] 按鈕,就會看到底下無法成功通過身分驗證訊息的畫面