使用 HttpClient 進行 JWT 身分驗證與呼叫需要授權的 API 和重新更新 Token 權杖的程式設計範例
設計符合 RESTful API 設計原則的回傳格式
#region APIResult
public class APIResult
{
    
    
    
    public bool Status { get; set; } = false;
    
    
    
    public string Message { get; set; } = "";
    
    
    
    public object Payload { get; set; }
}
#endregion
設計一個進行身分驗證的 Login 服務類別
public class LoginManager : BaseWebAPI<LoginResponseDTO>
{
    public LoginManager()
        : base()
    {
        this.restURL = "/LoginShort";
        this.host = Constants.HostAPI;
        IsCollectionType = false;
        EncodingType = EnctypeMethod.JSON;
        NeedSaveData = true;
    }
    public async Task<APIResult> PostAsync(LoginRequestDTO loginRequestDTO, CancellationToken cancellationToken = default(CancellationToken))
    {
        #region 要傳遞的參數
        HTTPPayloadDictionary dic = new HTTPPayloadDictionary();
        dic.Add(Constants.JSONDataKeyName, JsonConvert.SerializeObject(loginRequestDTO));
        #endregion
        var mr = await this.SendAsync(dic, HttpMethod.Post, cancellationToken);
        return mr;
    }
}
public class LoginRequestDTO
{
    public string Account { get; set; }
    public string Password { get; set; }
}
public class LoginResponseDTO
{
    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; }
    public string Image { get; set; }
    public virtual DepartmentDTO Department { get; set; }
}
呼叫 RESTfule API 的基底服務類別

 
#region BaseWebAPI
/// <summary>
/// 存取Http服務的Base Class
/// </summary>
/// <typeparam name="T"></typeparam>
public abstract class BaseWebAPI<T>
{
    #region Field
    /// <summary>
    /// WebAPI主機位置
    /// </summary>
    public string host = Constants.HostAPI;
    /// <summary>
    /// WebAPI方法網址 
    /// </summary>
    public string restURL = "";
    //public string AuthenticationHeaderBearerTokenValue = "";
    /// <summary>
    /// 指定 HTTP 標頭 Bearer 內,要放置的 JWT Token 值
    /// </summary>
    public string AuthenticationHeaderBearerTokenValue { get; set; }
    /// <summary>
    /// 要呼叫 REST API 的額外路由路徑
    /// </summary>
    public string RouteURL { get; set; } = "";
    /// <summary>
    /// 成功呼叫完成 API 之後,是否要儲存到本機檔案系統內
    /// </summary>
    public bool NeedSaveData { get; set; }
    /// <summary>
    /// 是否為集合型別的物件
    /// </summary>
    public bool IsCollectionType { get; set; } = true;
    /// <summary>
    /// 要傳遞的 HTTP Payload 使用的編碼格式
    /// </summary>
    public EnctypeMethod EncodingType;
    /// <summary>
    /// 資料夾名稱
    /// </summary>
    //public string CurrentFolderName = "";
    public string CurrentFolderName { get; set; } = "";
    public string SubFolderName { get; set; } = "";
    public string TopDataFolderName { get; set; } = "Data";
    /// <summary>
    /// 檔案名稱
    /// </summary>
    public string DataFileName { get; set; } = "";
    #region 系統用到的訊息字串
    public static readonly string APIInternalError = "System Exception = null, Result = null";
    #endregion
    #endregion
    // =========================================================================================================
    #region protected
    #endregion
    // =========================================================================================================
    #region Public
    /// <summary>
    /// 透過Http取得的資料,也許是一個物件,也許是List
    /// </summary>
    public List<T> Items { get; set; }
    public T SingleItem { get; set; }
    /// <summary>
    /// 此次呼叫的處理結果
    /// </summary>
    public APIResult ManagerResult { get; set; }
    #endregion
    // =========================================================================================================
    /// <summary>
    /// 建構子,經由繼承後使用反射取得類別的名稱當作,檔案名稱及WebAPI的方法名稱
    /// </summary>
    public BaseWebAPI()
    {
        CurrentFolderName = TopDataFolderName;
        restURL = "";
        DataFileName = this.GetType().Name;
        //子資料夾名稱 = 資料檔案名稱;
        this.ManagerResult = new APIResult();
        EncodingType = EnctypeMethod.JSON;
    }
    ///// <summary>
    ///// 建立存取 Web 服務的參數
    ///// </summary>
    ///// <param name="_url">存取服務的URL</param>
    ///// <param name="_DataFileName">儲存資料的名稱</param>
    ///// <param name="_DataFolderName">資料要儲存的目錄</param>
    ///// <param name="_className">類別名稱</param>
    //public void SetWebAccessCondition(string _url, string _DataFileName, string _DataFolderName, string _className = "")
    //{
    //    string className = _className;
    //    this.restURL = string.Format("{0}{1}", _url, _className);
    //    this.資料檔案名稱 = _DataFileName;
    //    this.現在資料夾名稱 = _DataFolderName;
    //    this.managerResult = new APIResult();
    //}
    /// <summary>
    /// 從網路取得相對應WebAPI的資料
    /// </summary>
    /// <param name="dic">所要傳遞的參數 Dictionary </param>
    /// <param name="httpMethod">Get Or Post</param>
    /// <returns></returns>
    protected virtual async Task<APIResult> SendAsync(Dictionary<string, string> dic, HttpMethod httpMethod,
        CancellationToken token = default(CancellationToken))
    {
        this.ManagerResult = new APIResult();
        APIResult mr = this.ManagerResult;
        string jsonPayload = "";
        //檢查網路狀態
        //if (UtilityHelper.IsConnected() == false)
        //{
        //    mr.Status = false;
        //    mr.Message = "無網路連線可用,請檢查網路狀態";
        //    return mr;
        //}
        if (dic.ContainsKey(Constants.JSONDataKeyName))
        {
            jsonPayload = dic[Constants.JSONDataKeyName];
            dic.Remove(Constants.JSONDataKeyName);
        }
        HttpClientHandler handler = new HttpClientHandler();
        using (HttpClient client = new HttpClient(handler))
        {
            try
            {
                //client.Timeout = TimeSpan.FromMinutes(3);
                string fooQueryString = dic.ToQueryString();
                string fooUrl = $"{host}{restURL}{RouteURL}" + fooQueryString;
                UriBuilder ub = new UriBuilder(fooUrl);
                HttpResponseMessage response = null;
                #region 檢查是否要將 JWT Token 放入 HTTP 標頭 Bearer 內
                if (string.IsNullOrEmpty(this.AuthenticationHeaderBearerTokenValue) == false)
                {
                    client.DefaultRequestHeaders.Authorization =
                        new AuthenticationHeaderValue("Bearer", this.AuthenticationHeaderBearerTokenValue);
                }
                #endregion
                #region  執行 HTTP 動詞 (Action) : Get, Post, Put, Delete
                if (httpMethod == HttpMethod.Get)
                {
                    // 使用 Get 方式來呼叫
                    response = await client.GetAsync(ub.Uri, token);
                }
                else if (httpMethod == HttpMethod.Post)
                {
                    // 使用 Post 方式來呼叫
                    switch (EncodingType)
                    {
                        case EnctypeMethod.MULTIPART:
                            // 使用 MULTIPART 方式來進行傳遞資料的編碼
                            response = await client.PostAsync(ub.Uri, dic.ToMultipartFormDataContent(), token);
                            break;
                        case EnctypeMethod.FORMURLENCODED:
                            // 使用 FormUrlEncoded 方式來進行傳遞資料的編碼
                            response = await client.PostAsync(ub.Uri, dic.ToFormUrlEncodedContent(), token);
                            break;
                        case EnctypeMethod.JSON:
                            client.DefaultRequestHeaders.Accept.TryParseAdd("application/json");
                            response = await client.PostAsync(ub.Uri, new StringContent(jsonPayload, Encoding.UTF8, "application/json"));
                            break;
                        default:
                            throw new Exception("不正確的 HTTP Payload 編碼設定");
                                break;
                        }
                }
                else if (httpMethod == HttpMethod.Put)
                {
                    // 使用 Post 方式來呼叫
                    switch (EncodingType)
                    {
                        case EnctypeMethod.MULTIPART:
                            // 使用 MULTIPART 方式來進行傳遞資料的編碼
                            response = await client.PutAsync(ub.Uri, dic.ToMultipartFormDataContent(), token);
                            break;
                        case EnctypeMethod.FORMURLENCODED:
                            // 使用 FormUrlEncoded 方式來進行傳遞資料的編碼
                            response = await client.PutAsync(ub.Uri, dic.ToFormUrlEncodedContent(), token);
                            break;
                        case EnctypeMethod.JSON:
                            client.DefaultRequestHeaders.Accept.TryParseAdd("application/json");
                            response = await client.PutAsync(ub.Uri, new StringContent(jsonPayload, Encoding.UTF8, "application/json"));
                            break;
                        default:
                            throw new Exception("不正確的 HTTP Payload 編碼設定");
                                break;
                        }
                }
                else if (httpMethod == HttpMethod.Delete)
                {
                    response = await client.DeleteAsync(ub.Uri, token);
                }
                else
                {
                    throw new NotImplementedException("Not Found HttpMethod");
                }
                #endregion
                #region Response
                if (response != null)
                {
                    String strResult = await response.Content.ReadAsStringAsync();
                    if (response.IsSuccessStatusCode == true)
                    {
                        mr = JsonConvert.DeserializeObject<APIResult>(strResult, new JsonSerializerSettings { MetadataPropertyHandling = MetadataPropertyHandling.Ignore });
                        if (mr.Status == true)
                        {
                            var fooDataString = mr.Payload.ToString();
                            if (IsCollectionType == false)
                            {
                                SingleItem = JsonConvert.DeserializeObject<T>(fooDataString, new JsonSerializerSettings { MetadataPropertyHandling = MetadataPropertyHandling.Ignore });
                                if (NeedSaveData == true)
                                {
                                    if (SingleItem == null)
                                    {
                                        SingleItem = (T)Activator.CreateInstance(typeof(T));
                                    }
                                    await this.WriteToFileAsync();
                                }
                            }
                            else
                            {
                                Items = JsonConvert.DeserializeObject<List<T>>(fooDataString, new JsonSerializerSettings { MetadataPropertyHandling = MetadataPropertyHandling.Ignore });
                                if (NeedSaveData == true)
                                {
                                    if (Items == null)
                                    {
                                        Items = (List<T>)Activator.CreateInstance(typeof(List<T>));
                                    }
                                    await this.WriteToFileAsync();
                                }
                            }
                        }
                    }
                    else
                    {
                        APIResult fooAPIResult = JsonConvert.DeserializeObject<APIResult>(strResult, new JsonSerializerSettings { MetadataPropertyHandling = MetadataPropertyHandling.Ignore });
                        if (fooAPIResult != null)
                        {
                            mr = fooAPIResult;
                        }
                        else
                        {
                            mr.Status = false;
                            mr.Message = string.Format("Error Code:{0}, Error Message:{1}", response.StatusCode, response.Content);
                        }
                    }
                }
                else
                {
                    mr.Status = false;
                    mr.Message = APIInternalError;
                }
                #endregion
            }
            catch (Exception ex)
            {
                mr.Status = false;
                mr.Message = ex.Message;
            }
        }
        return mr;
    }
    /// <summary>
    /// 將物件資料從檔案中讀取出來
    /// </summary>
    public virtual async Task ReadFromFileAsync()
    {
        #region 先將建立該資料模型的物件,避免檔案讀取不出來之後, Items / SingleItem 的物件值為 null
        if (IsCollectionType == false)
        {
            SingleItem = (T)Activator.CreateInstance(typeof(T));
        }
        else
        {
            Items = (List<T>)Activator.CreateInstance(typeof(List<T>));
        }
        #endregion
        string data = await StorageUtility.ReadFromDataFileAsync(this.CurrentFolderName, this.DataFileName);
        if (string.IsNullOrEmpty(data) == true)
        {
        }
        else
        {
            try
            {
                if (IsCollectionType == false)
                {
                    this.SingleItem = JsonConvert.DeserializeObject<T>(data, new JsonSerializerSettings { MetadataPropertyHandling = MetadataPropertyHandling.Ignore });
                }
                else
                {
                    this.Items = JsonConvert.DeserializeObject<List<T>>(data, new JsonSerializerSettings { MetadataPropertyHandling = MetadataPropertyHandling.Ignore });
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.Message);
            }
        }
    }
    /// <summary>
    /// 將物件資料寫入到檔案中
    /// </summary>
    public virtual async Task WriteToFileAsync()
    {
        string data = "";
        if (IsCollectionType == false)
        {
            data = JsonConvert.SerializeObject(this.SingleItem);
        }
        else
        {
            data = JsonConvert.SerializeObject(this.Items);
        }
        await StorageUtility.WriteToDataFileAsync(this.CurrentFolderName, this.DataFileName, data);
    }
}
#endregion
開始進行測試

 
{
  "id": 25,
  "account": "user50",
  "name": "Account50",
  "token": "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzaWQiOiIyNSIsImh0dHA6Ly9zY2hlbWFzLnhtbHNvYXAub3JnL3dzLzIwMDUvMDUvaWRlbnRpdHkvY2xhaW1zL25hbWUiOiJ1c2VyNTAiLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL3JvbGUiOlsiVXNlciIsIkRlcHQxIl0sImh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd3MvMjAwOC8wNi9pZGVudGl0eS9jbGFpbXMvdmVyc2lvbiI6IjAiLCJleHAiOjE1NjQzOTk5OTUsImlzcyI6IlhhbWFyaW5Gb3Jtc1dTLnZ1bGNhbi5uZXQiLCJhdWQiOiJYYW1hcmluLkZvcm1zIEFwcCJ9.XpO0wCvNVBvNpZT4AaBz83VK-xFMvVMjxTSQ0MMSOVycz-LwVk71BQT25c1OPRcnxhX4B0jBNBRlbmAJT4WldQ",
  "tokenExpireMinutes": 15,
  "refreshToken": "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzaWQiOiIyNSIsImh0dHA6Ly9zY2hlbWFzLnhtbHNvYXAub3JnL3dzLzIwMDUvMDUvaWRlbnRpdHkvY2xhaW1zL25hbWUiOiJ1c2VyNTAiLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL3JvbGUiOlsiVXNlciIsIlJlZnJlc2hUb2tlbiJdLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL3ZlcnNpb24iOiIwIiwiZXhwIjoxNTY1MDA1MDg1LCJpc3MiOiJYYW1hcmluRm9ybXNXUy52dWxjYW4ubmV0IiwiYXVkIjoiWGFtYXJpbi5Gb3JtcyBBcHAifQ.Gisq0bSRTRIzci19nNy83wfi0fMJyVPvBjyoRfWNWbca2maQwC0lWsvpBKKu5ZlZQBWWVH7JMrKH_CpvI5Tksw",
  "refreshTokenExpireDays": 7,
  "image": "",
  "level": 0,
  "department": {
    "id": 1
  }
}
錯誤代碼 1, 存取權杖可用期限已經逾期超過

 
{
  "id": 25,
  "account": "user50",
  "name": "Account50",
  "token": "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzaWQiOiIyNSIsImh0dHA6Ly9zY2hlbWFzLnhtbHNvYXAub3JnL3dzLzIwMDUvMDUvaWRlbnRpdHkvY2xhaW1zL25hbWUiOiJ1c2VyNTAiLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL3JvbGUiOlsiVXNlciIsIkRlcHQxIl0sImh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd3MvMjAwOC8wNi9pZGVudGl0eS9jbGFpbXMvdmVyc2lvbiI6IjAiLCJleHAiOjE1NjQ0MDAwMTUsImlzcyI6IlhhbWFyaW5Gb3Jtc1dTLnZ1bGNhbi5uZXQiLCJhdWQiOiJYYW1hcmluLkZvcm1zIEFwcCJ9.0oJSRSu0zqFuE66HRRB_ulJlT8y8dcTJj01p-t7htngMtODmVCn8XUfvD3PMz0CN8F7tPOwJfqveJUFD3Xezvw",
  "tokenExpireMinutes": 15,
  "refreshToken": "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzaWQiOiIyNSIsImh0dHA6Ly9zY2hlbWFzLnhtbHNvYXAub3JnL3dzLzIwMDUvMDUvaWRlbnRpdHkvY2xhaW1zL25hbWUiOiJ1c2VyNTAiLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL3JvbGUiOlsiVXNlciIsIlJlZnJlc2hUb2tlbiJdLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL3ZlcnNpb24iOiIwIiwiZXhwIjoxNTY1MDA1MTA1LCJpc3MiOiJYYW1hcmluRm9ybXNXUy52dWxjYW4ubmV0IiwiYXVkIjoiWGFtYXJpbi5Gb3JtcyBBcHAifQ.Zxy_f5A3JY20c5XsQp5vUJlm901dy0jwW-rbOfOQXWld6TaCh4oPp-NuiavWdcjyt9oV8B8CtLHzxeZLVDsbhw",
  "refreshTokenExpireDays": 7,
  "image": "",
  "level": 0,
  "department": {
    "id": 1
  }
}

 
static async Task Main(string[] args)
{
    LoginManager loginManager = new LoginManager();
    Output("進行登入身分驗證");
    APIResult result = await loginManager.PostAsync(new LoginRequestDTO()
    {
        Account = "user50",
        Password = "password50"
    });
    if (result.Status == true)
    {
        Console.WriteLine($"登入成功");
        Console.WriteLine($"{result.Payload}");
    }
    else
    {
        Console.WriteLine($"登入失敗");
        Console.WriteLine($"{result.Message}");
    }
    Thread.Sleep(2000);
    Output("利用取得的 JTW Token 呼叫取得部門資訊 Web API");
    DepartmentsManager departmentsManager = new DepartmentsManager();
    result = await departmentsManager.GetAsync(loginManager.SingleItem.Token);
    if (result.Status == true)
    {
        Console.WriteLine($"取得部門資料成功");
        Console.WriteLine($"{result.Payload}");
    }
    else
    {
        Console.WriteLine($"取得部門資料失敗");
        Console.WriteLine($"{result.Message}");
    }
    Console.WriteLine("等候10秒鐘,等待 JWT Token 失效");
    await Task.Delay(10000);
    departmentsManager = new DepartmentsManager();
    Output("再次呼叫取得部門資訊 Web API,不過,該 JWT Token已經失效了");
    result = await departmentsManager.GetAsync(loginManager.SingleItem.Token);
    if (result.Status == true)
    {
        Console.WriteLine($"取得部門資料成功");
        Console.WriteLine($"{result.Payload}");
    }
    else
    {
        Console.WriteLine($"取得部門資料失敗");
        Console.WriteLine($"{result.Message}");
    }
    Thread.Sleep(2000);
    RefreshTokenService refreshTokenService = new RefreshTokenService();
    Output("呼叫更新 JWT Token API,取得更新的 JWT Token");
    result = await refreshTokenService.GetAsync(loginManager.SingleItem.RefreshToken);
    if (result.Status == true)
    {
        Console.WriteLine($"更新 JWT Token 成功");
        Console.WriteLine($"{result.Payload}");
    }
    else
    {
        Console.WriteLine($"更新 JWT Token 失敗");
        Console.WriteLine($"{result.Message}");
    }
    Thread.Sleep(2000);
    departmentsManager = new DepartmentsManager();
    Output("再次呼叫取得部門資訊 Web API,不過,使用剛剛取得的更新 JWT Token");
    result = await departmentsManager.GetAsync(refreshTokenService.SingleItem.Token);
    if (result.Status == true)
    {
        Console.WriteLine($"取得部門資料成功");
        Console.WriteLine($"{result.Payload}");
    }
    else
    {
        Console.WriteLine($"取得部門資料失敗");
        Console.WriteLine($"{result.Message}");
    }
    Thread.Sleep(2000);
    Console.WriteLine("Press any key for continuing...");
    Console.ReadKey();
}