2019年5月20日 星期一

取得後端 Web API 的 JWT Token ,並且呼叫相關 API 的做法

取得後端 Web API 的 JWT Token ,並且呼叫相關 API 的做法

在上一篇文章 
使用 C# HttpClient 追蹤與顯示 HTTP Request / Response 封包內容
 文章中,有說明如何呼叫遠端 Web API,進行使用者帳號身分驗證的工作,一旦身分驗證成功之後,將會得到遠端 API 伺服器產生的 JWT 權杖 Token,不過,該權杖有效期限僅有 15 分鐘有效時間。
在這篇文章中,將會使用登入驗證完成之後取得的權杖,呼叫 https://lobworkshop.azurewebsites.net/api/Invoices 這個發票 API,說明如何進行發票的 CRUD ,也就是 Create 新增、Retrive 查詢、Update 更新、Delete 刪除的作業。
在這篇文章所提到的範例程式碼,可以從這裡 GitHub 取得
為了要能夠完成這樣的練習,首先需要建立發票 API 會用到的 DTO 類別,這些類別如下所示:
C Sharp / C#
#region DTO 型別宣告
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 class InvoiceRequestDTO
{
    public int Id { get; set; }
    public string InvoiceNo { get; set; }
    public UserDTO user { get; set; }
    public DateTime Date { get; set; }
    public string Memo { get; set; }
}
public class InvoiceResponseDTO
{
    public int Id { get; set; }
    public string InvoiceNo { get; set; }
    public UserDTO user { get; set; }
    public DateTime Date { get; set; }
    public string Memo { get; set; }
}
public class UserDTO
{
    public int Id { get; set; }
}
/// <summary>
/// 呼叫 API 回傳的制式格式
/// </summary>
public class APIResult
{
    /// <summary>
    /// 此次呼叫 API 是否成功
    /// </summary>
    public bool Status { get; set; } = false;
    /// <summary>
    /// 呼叫 API 失敗的錯誤訊息
    /// </summary>
    public string Message { get; set; } = "";
    /// <summary>
    /// 呼叫此API所得到的其他內容
    /// </summary>
    public object Payload { get; set; }
}
#endregion
首先,來看看如何呼叫新增發票 API,在這裡要先建立要新增發票的物件 InvoiceRequestDTO invoiceRequestDTO ,接著,使用 client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", loginResponseDTO.Token); 敘述,把登入驗證成功後取得的 JWT Token,放入此次新增發票的請求的 HTTP 標頭 Header 內,如此,對於後端 Web Server 收到這個新增發票請求的時候,將會先檢查 JWT 權杖是否存,若存在,將會檢查該權杖是否正確、還在有效期限等等狀況,若沒有問題發生,將會觸發新增發票的 程式碼。
在這裡,將會使用 await client.PostAsync(url, new StringContent(httpJsonPayload, System.Text.Encoding.UTF8, "application/json")); 表示式,呼叫遠端的新增發票 API,第二個參數將會是此次要新增發票的內容。
C Sharp / C#
private static async Task<InvoiceResponseDTO> CreateInvoiceAsync(LoginResponseDTO loginResponseDTO)
{
    InvoiceResponseDTO invoiceResponseDTO = new InvoiceResponseDTO();
    string url = "https://lobworkshop.azurewebsites.net/api/Invoices";
    InvoiceRequestDTO invoiceRequestDTO = new InvoiceRequestDTO()
    {
        InvoiceNo = "123",
        Memo = "查理王",
        Date = new DateTime(2019, 05, 20),
        user = new UserDTO()
        {
            Id = loginResponseDTO.Id
        }
    };
    var httpJsonPayload = JsonConvert.SerializeObject(invoiceRequestDTO);
    HttpClient client = new HttpClient();
    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", loginResponseDTO.Token);
    HttpResponseMessage response = await client.PostAsync(url,
        new StringContent(httpJsonPayload, System.Text.Encoding.UTF8, "application/json"));

    if (response.IsSuccessStatusCode)
    {
        String strResult = await response.Content.ReadAsStringAsync();
        APIResult apiResult = JsonConvert.DeserializeObject<APIResult>(strResult, new JsonSerializerSettings { MetadataPropertyHandling = MetadataPropertyHandling.Ignore });
        if (apiResult.Status == true)
        {
            string itemJsonContent = apiResult.Payload.ToString();
            Console.WriteLine($"成功新增一筆發票 : {itemJsonContent}");
            invoiceResponseDTO = JsonConvert.DeserializeObject<InvoiceResponseDTO>(itemJsonContent, new JsonSerializerSettings { MetadataPropertyHandling = MetadataPropertyHandling.Ignore });
        }
    }
    return invoiceResponseDTO;
}
對於想要修改遠端 Web API 資料庫內的發票紀錄,當然需要先取得該發票紀錄內容,在此,將會透過剛剛呼叫玩新增發票 API 後所得到的該發票紀錄,緊接著來呼叫修改 API。
在底下的呼叫修改發票方法中,第二個參數將會這次要修改發票的物件,而根據 REST 規範,當我們要進行修改某個紀錄的時候,需要提供一個 URI,該 URI 可以指向到該筆發票紀錄,因此,當要進行修改發票的時候,所要使用的 URL ,將會設定為 string url = $"https://lobworkshop.azurewebsites.net/api/Invoices/{UpdateItem.Id}"; ,也就是要把該發票紀錄的 ID 放到 URL 路徑。
接下來將會進行任意修改該發票紀錄,當然也需要將 JWT Token,使用這個敘述 client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", loginResponseDTO.Token); ,設定到此次 HTTP REST API 呼叫過程中,如此,就可以接著使用 await client.PutAsync(url, new StringContent(httpJsonPayload, System.Text.Encoding.UTF8, "application/json")); 敘述,呼叫遠端的發票更新 API。
C Sharp / C#
private static async Task<InvoiceResponseDTO> UpdateInvoiceAsync(LoginResponseDTO loginResponseDTO, InvoiceResponseDTO UpdateItem)
{
    InvoiceResponseDTO invoiceResponseDTO = new InvoiceResponseDTO();
    string url = $"https://lobworkshop.azurewebsites.net/api/Invoices/{UpdateItem.Id}";
    InvoiceRequestDTO invoiceRequestDTO = new InvoiceRequestDTO()
    {
        Id = UpdateItem.Id,
        InvoiceNo = UpdateItem.InvoiceNo,
        Memo = "修正" +UpdateItem.Memo,
        Date = UpdateItem.Date.AddDays(5),
        user = UpdateItem.user
    };
    var httpJsonPayload = JsonConvert.SerializeObject(invoiceRequestDTO);
    HttpClient client = new HttpClient();
    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", loginResponseDTO.Token);
    HttpResponseMessage response = await client.PutAsync(url,
        new StringContent(httpJsonPayload, System.Text.Encoding.UTF8, "application/json"));

    String strResult = await response.Content.ReadAsStringAsync();
    if (response.IsSuccessStatusCode)
    {
        APIResult apiResult = JsonConvert.DeserializeObject<APIResult>(strResult, new JsonSerializerSettings { MetadataPropertyHandling = MetadataPropertyHandling.Ignore });
        if (apiResult.Status == true)
        {
            string itemJsonContent = apiResult.Payload.ToString();
            Console.WriteLine($"成功修改一筆發票 : {itemJsonContent}");
            invoiceResponseDTO = JsonConvert.DeserializeObject<InvoiceResponseDTO>(itemJsonContent, new JsonSerializerSettings { MetadataPropertyHandling = MetadataPropertyHandling.Ignore });
        }
    }
    return invoiceResponseDTO;
}
當想要查詢遠端 Web API 上的發票紀錄,可以使用 HTTP Get 方法,當然,也是要將 JWT Token 放到 HTTP 標頭 Header 上;在這個發票查詢的 API,將會回傳 List ,也就是 InvoiceResponseDTO 的集合物件。
C Sharp / C#
private static async Task<List<InvoiceResponseDTO>> QueryInvoiceAsync(LoginResponseDTO loginResponseDTO)
{
    List<InvoiceResponseDTO> invoiceResponseDTOs = new List<InvoiceResponseDTO>();
    string url = "https://lobworkshop.azurewebsites.net/api/Invoices";
    HttpClient client = new HttpClient();
    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", loginResponseDTO.Token);
    HttpResponseMessage response = await client.GetAsync(url);

    if (response.IsSuccessStatusCode)
    {
        String strResult = await response.Content.ReadAsStringAsync();
        APIResult apiResult = JsonConvert.DeserializeObject<APIResult>(strResult, new JsonSerializerSettings { MetadataPropertyHandling = MetadataPropertyHandling.Ignore });
        String strResult = await response.Content.ReadAsStringAsync();
        if (apiResult.Status == true)
        {
            Console.WriteLine($"成功查詢發票 : {itemJsonContent}");
            invoiceResponseDTOs = JsonConvert.DeserializeObject<List<InvoiceResponseDTO>>(itemJsonContent, new JsonSerializerSettings { MetadataPropertyHandling = MetadataPropertyHandling.Ignore });
        }
    }
    return invoiceResponseDTOs;
}
當要刪除某個發票紀錄的時候,根據 REST 規範,一樣需要使用 URI 標示出該發票紀錄所在的地方,在這裡將會使用這樣的敘述 string url = $"https://lobworkshop.azurewebsites.net/api/Invoices/{Id}"; ,當然,同樣也需要把 JWT Token 放到此次的 HTTP 呼叫標頭上。
最後,使用 await client.DeleteAsync(url); 敘述,呼叫遠端刪除的 API。
C Sharp / C#
private static async Task<InvoiceResponseDTO> DeleteInvoiceAsync(LoginResponseDTO loginResponseDTO, int Id)
{
    InvoiceResponseDTO invoiceResponseDTO = new InvoiceResponseDTO();
    string url = $"https://lobworkshop.azurewebsites.net/api/Invoices/{Id}";
    HttpClient client = new HttpClient();
    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", loginResponseDTO.Token);
    HttpResponseMessage response = await client.DeleteAsync(url);

    String strResult = await response.Content.ReadAsStringAsync();
    if (response.IsSuccessStatusCode)
    {
        APIResult apiResult = JsonConvert.DeserializeObject<APIResult>(strResult, new JsonSerializerSettings { MetadataPropertyHandling = MetadataPropertyHandling.Ignore });
        if (apiResult.Status == true)
        {
            string itemJsonContent = apiResult.Payload.ToString();
            Console.WriteLine($"成功刪除一筆發票 : {itemJsonContent}");
            invoiceResponseDTO = JsonConvert.DeserializeObject<InvoiceResponseDTO>(itemJsonContent, new JsonSerializerSettings { MetadataPropertyHandling = MetadataPropertyHandling.Ignore });
        }
    }
    return invoiceResponseDTO;
}
現在,可以在 Main 方法內,使用底下的方法來進行發票的 CRUD 呼叫
C Sharp / C#
static async Task Main(string[] args)
{
    // 登入系統,取得 JTW Token
    await LoginAsync();

    // 從檔案中取得 JWT 權杖 Token
    string fileContent = await StorageUtility.ReadFromDataFileAsync("", "MyDataFolder", "MyFilename.txt");
    LoginResponseDTO loginResponseDTO = JsonConvert.DeserializeObject<LoginResponseDTO>(fileContent);

    #region CRUD => Retrive 取得該使用者的發票資料
    List<InvoiceResponseDTO> invoiceResponseDTOs = await QueryInvoiceAsync(loginResponseDTO);
    PrintAllInvoice(invoiceResponseDTOs);
    #endregion

    #region CRUD => Create 新增發票資料
    InvoiceResponseDTO invoiceResponseDTO = await CreateInvoiceAsync(loginResponseDTO);
    #endregion

    #region CRUD => Retrive 取得該使用者的發票資料
    invoiceResponseDTOs = await QueryInvoiceAsync(loginResponseDTO);
    PrintAllInvoice(invoiceResponseDTOs);
    #endregion

    #region CRUD => Update 修改發票資料
    invoiceResponseDTO = await UpdateInvoiceAsync(loginResponseDTO, invoiceResponseDTO);
    #endregion

    #region CRUD => Retrive 取得該使用者的發票資料
    invoiceResponseDTOs = await QueryInvoiceAsync(loginResponseDTO);
    PrintAllInvoice(invoiceResponseDTOs);
    #endregion

    #region CRUD => Delete 刪除發票資料
    foreach (var item in invoiceResponseDTOs)
    {
        await DeleteInvoiceAsync(loginResponseDTO, item.Id);
    }
    #endregion

    #region CRUD => Retrive 取得該使用者的發票資料
    invoiceResponseDTOs = await QueryInvoiceAsync(loginResponseDTO);
    PrintAllInvoice(invoiceResponseDTOs);
    #endregion

    Console.WriteLine("Press any key for continuing...");
    Console.ReadKey();
}
由於後端 Web API 所發出的 JWT Token 僅僅會有 15 分鐘的有效期限,因此,當您成功執行過該專案後,等候 15 分鐘之後,可以把 Main 方法內的 await LoginAsync(); 註解起來,再度執行看看,不過,先在 QueryInvoiceAsync 方法內的 if (apiResult.Status == true) 敘述上設定一個中斷點,當執行到這個中斷點,先查詢看看 apiResult.Status 屬性值,應該是 false,而在該敘述的上一行,那就是 String strResult = await response.Content.ReadAsStringAsync(); 這個敘述,請檢查看看 strResult 變數值,將會看到底下的內容,後端 Web API 將會因為這次呼叫查詢發票 API,因為所提供的 JWT 權杖 Token 已經逾期,因此,將會得到 401 的錯誤訊息。
{"Status":false,"HTTPStatus":401,"ErrorCode":1,"Message":"錯誤代碼 1, 存取權杖可用期限已經逾期超過","Payload":null}



沒有留言:

張貼留言