針對 .NET / CLR / C# / Blazor / MAUI / Xamarin / .NET Core / .NET Framework / OOP / Design Pattern 等相關程式開發議題進行研究與寫成相關心得筆記。
2017年10月19日 星期四
2017年10月18日 星期三
C# HttpClient WebAPI : 4. 使用 GET 要求與 QueryString 呼叫 Web API
當我們在進行 Web API 呼叫的時候,絕大部分的時候,我們需要把用戶端 (Client) 這裡的一些資料,傳送到後端的 Web API 伺服器上,這樣,所觸發的控制器動作,將會依據我們傳送過的資料,進行處理之後,並且回報給用戶端。
當然,要傳送資料到伺服器上,有著不同的方法,在這裡,我們先來看看,在使用 GET 這類要求的時候,最常用的方式,就是使用 查詢字串
Query String
的方法;至於其他傳送資料的使用技術方法,會在其他文章中做說明,在這裡,我們將會說明如何在 GET 要求的時候,使用查詢字串,呼叫遠端 Web API 的指定動作。
了解更多關於 [HttpClient Class] 的使用方式
使用 GET 要求與 QueryString 呼叫 Web API
在這裡,我們撰寫出底下的非同步方法,GetQueryStringAsync,這個方法的大部分程式碼架構與前面的文章 C# HttpClient WebAPI : 3. GET 要求傳送至指定的 URI 類似,這個方法的原始碼如下所示:
private static async Task<APIResult> GetQueryStringAsync(APIData apiData)
{
APIResult fooAPIResult;
using (HttpClientHandler handler = new HttpClientHandler())
{
using (HttpClient client = new HttpClient(handler))
{
try
{
#region 呼叫遠端 Web API
string FooUrl = $"http://vulcanwebapi.azurewebsites.net/api/values/QueryStringGet";
HttpResponseMessage response = null;
#region 設定相關網址內容
var fooFullUrl = $"{FooUrl}";
// Accept 用於宣告客戶端要求服務端回應的文件型態 (底下兩種方法皆可任選其一來使用)
//client.DefaultRequestHeaders.Accept.TryParseAdd("application/json");
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// Content-Type 用於宣告遞送給對方的文件型態
//client.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/json");
#region 組合 QueryString
Dictionary<string, string> dic = new Dictionary<string, string>();
dic.Add(nameof(apiData.Id), apiData.Id.ToString());
dic.Add(nameof(apiData.Name), apiData.Name.ToString());
dic.Add(nameof(apiData.Filename), apiData.Filename.ToString());
string queryString = "?";
foreach (string key in dic.Keys)
{
queryString += key + "=" + dic[key] + "&";
}
queryString = queryString.Remove(queryString.Length - 1, 1);
#endregion
response = await client.GetAsync(fooFullUrl+queryString);
#endregion
#endregion
#region 處理呼叫完成 Web API 之後的回報結果
if (response != null)
{
if (response.IsSuccessStatusCode == true)
{
// 取得呼叫完成 API 後的回報內容
String strResult = await response.Content.ReadAsStringAsync();
fooAPIResult = JsonConvert.DeserializeObject<APIResult>(strResult, new JsonSerializerSettings { MetadataPropertyHandling = MetadataPropertyHandling.Ignore });
}
else
{
fooAPIResult = new APIResult
{
Success = false,
Message = string.Format("Error Code:{0}, Error Message:{1}", response.StatusCode, response.RequestMessage),
Payload = null,
};
}
}
else
{
fooAPIResult = new APIResult
{
Success = false,
Message = "應用程式呼叫 API 發生異常",
Payload = null,
};
}
#endregion
}
catch (Exception ex)
{
fooAPIResult = new APIResult
{
Success = false,
Message = ex.Message,
Payload = ex,
};
}
}
}
return fooAPIResult;
}
在這個非同步方法中,我們將會接收到型別
APIData
的參數物件,我們需要將這個物件組合成為查詢字串 (Query String),並且組合到我們要呼叫的 Web API URL 後;從底下的程式碼中,我們宣告了 Dictionary<string, string>
型別變數,dic
,我們使用這樣的程式碼敘述: Dictionary<string, string> dic = new Dictionary<string, string>();
,接著我們收到的方法參數物件內的每個屬性,加入到 dic 中。
在這個
Dictionary<string, string>
型別變數中,他的鍵值 (Key),我們需要指定這個參數屬性的名稱,我們採用強型別的方式寫法,以避免撰寫這類程式碼而造成執行時期的錯誤,所以,我們使用 nameof(apiData.Id)
這樣的寫法,其中,nameof 運算子關鍵字的功用將是:用來取得變數、類型或成員的簡單字串名稱;而在鍵值的相對應的數值,我們就需要設定這個屬性的實際物件內容。
最後,我們使用底下的片段程式碼,就可以把剛剛建立好的 dic 物件,產生出查詢字串來;一旦取得查詢字串之後,就可以把這個字串附加到 URL 之後,這樣,當使用 GET 要求呼叫遠端 Web API 動作的時候,遠端伺服器的控制項動作,就會接收到用戶端傳送過去的內容。
string queryString = "?";
foreach (string key in dic.Keys)
{
queryString += key + "=" + dic[key] + "&";
}
queryString = queryString.Remove(queryString.Length - 1, 1);
觸發的 Web API 動作
這個範例中,將會指向 URL
http://vulcanwebapi.azurewebsites.net/api/values/QueryStringGet
,此時,將會觸發 Web API 伺服器上的 Values 控制器(Controller)的 public APIResult QueryStringGet([FromQuery] APIData value)
動作(Action),其該動作的原始碼如下所示。
這個 Web API 動作,將會回傳一個 APIData 的 JSON 資料。
[HttpGet("QueryStringGet")]
public APIResult QueryStringGet([FromQuery] APIData value)
{
APIResult foo;
if (value.Id == 777)
{
foo = new APIResult()
{
Success = true,
Message = "透過 Get 方法,接收到 Id=777",
Payload = new APIData()
{
Id = 777,
Name = "Vulcan by QueryStringGet"
}
};
}
else
{
foo = new APIResult()
{
Success = false,
Message = "無法發現到指定的 ID",
Payload = null
};
}
return foo;
}
進行測試
在程式進入點函式,我們建立一個
APIData
型別的物件,接著,設定該物件的相關屬性,這些屬性值,是我們要傳送到遠端伺服器端的資料,由上面的程式碼中,我們可以知道,當 Id 這個屬性值為 777 的時候,該 Web API 動作將會回覆通知,這次的呼叫是成功的,否則,會回覆此次 Web API 呼叫失敗。static async Task Main(string[] args)
{
var fooAPIData = new APIData()
{
Id = 777,
Name = "Vulcan",
Filename = "",
};
var foo = await GetQueryStringAsync(fooAPIData);
Console.WriteLine($"使用 Get 與 QueryString 方法呼叫 Web API 的結果");
Console.WriteLine($"結果狀態 : {foo.Success}");
Console.WriteLine($"結果訊息 : {foo.Message}");
Console.WriteLine($"Payload : {foo.Payload}");
Console.WriteLine($"");
Console.WriteLine($"Press any key to Exist...{Environment.NewLine}");
Console.ReadKey();
fooAPIData = new APIData()
{
Id = 123,
Name = "Vulcan",
Filename = "",
};
foo = await GetQueryStringAsync(fooAPIData);
Console.WriteLine($"使用 Get 與 QueryString 方法呼叫 Web API 的結果");
Console.WriteLine($"結果狀態 : {foo.Success}");
Console.WriteLine($"結果訊息 : {foo.Message}");
Console.WriteLine($"Payload : {foo.Payload}");
Console.WriteLine($"");
Console.WriteLine($"Press any key to Exist...{Environment.NewLine}");
Console.ReadKey();
}
執行結果
這個測試將會輸出底下內容
使用 Get 與 QueryString 方法呼叫 Web API 的結果
結果狀態 : True
結果訊息 : 透過 Get 方法,接收到 Id=777
Payload : {
"id": 777,
"name": "Vulcan by QueryStringGet",
"filename": null
}
Press any key to Exist...
使用 Get 與 QueryString 方法呼叫 Web API 的結果
結果狀態 : False
結果訊息 : 無法發現到指定的 ID
Payload :
Press any key to Exist...
HTTP 傳送與接收原始封包
讓我們來看看,這個 Web API 的呼叫動作中,在請求 (Request) 與 反應 (Response) 這兩個階段,會在網路上傳送了那些 HTTP 資料
- 請求 (Request)在這裡的第一行中,您將會看到了完整有查詢字串 (Query String) 的 URL,在問號之後的內容,將會傳送到後端 Web API 動作函式的參數內,此時的 Id值為 777。
GET http://vulcanwebapi.azurewebsites.net/api/values/QueryStringGet?Id=777&Name=Vulcan&Filename= HTTP/1.1
Accept: application/json
Host: vulcanwebapi.azurewebsites.net
Connection: Keep-Alive
- 反應 (Response)
HTTP/1.1 200 OK
Transfer-Encoding: chunked
Content-Type: application/json; charset=utf-8
Server: Kestrel
X-Powered-By: ASP.NET
Set-Cookie: ARRAffinity=9d3635139ab6649f453417d1e9047b7ed7a79b7bef031b04afeb6a2c58b33d4e;Path=/;HttpOnly;Domain=vulcanwebapi.azurewebsites.net
Date: Wed, 18 Oct 2017 16:04:41 GMT
88
{"success":true,"message":"透過 Get 方法,接收到 Id=777","payload":{"id":777,"name":"Vulcan by QueryStringGet","filename":null}}
0
- 請求 (Request)在這裡的第一行中,您將會看到了完整有查詢字串 (Query String) 的 URL,在問號之後的內容,將會傳送到後端 Web API 動作函式的參數內,此時的 Id值為 123。
GET http://vulcanwebapi.azurewebsites.net/api/values/QueryStringGet?Id=123&Name=Vulcan&Filename= HTTP/1.1
Accept: application/json
Host: vulcanwebapi.azurewebsites.net
- 反應 (Response)
HTTP/1.1 200 OK
Transfer-Encoding: chunked
Content-Type: application/json; charset=utf-8
Server: Kestrel
X-Powered-By: ASP.NET
Set-Cookie: ARRAffinity=9d3635139ab6649f453417d1e9047b7ed7a79b7bef031b04afeb6a2c58b33d4e;Path=/;HttpOnly;Domain=vulcanwebapi.azurewebsites.net
Date: Wed, 18 Oct 2017 16:04:42 GMT
48
{"success":false,"message":"無法發現到指定的 ID","payload":null}
0
相關文章索引
C# HttpClient WebAPI 系列文章索引
了解更多關於 [HttpClient Class] 的使用方式
關於 Xamarin 在台灣的學習技術資源
歡迎加入 Xamarin 實驗室 粉絲團,在這裡,將會經常性的貼出各種關於 Xamarin / Visual Studio / .NET 的相關消息、文章、技術開發等文件,讓您可以隨時掌握第一手的 Xamarin 方面消息。
歡迎加入 Xamarin.Forms @ Taiwan,這是台灣的 Xamarin User Group,若您有任何關於 Xamarin / Visual Studio / .NET 上的問題,都可以在這裡來與各方高手來進行討論、交流。
Xamarin 實驗室 部落格 是作者本身的部落格,這個部落格將會專注於 Xamarin 之跨平台 (Android / iOS / UWP) 方面的各類開技術探討、研究與分享的文章,最重要的是,它是全繁體中文。
Xamarin.Forms 系列課程 想要快速進入到 Xamarin.Forms 的開發領域,學會各種 Xamarin.Forms 跨平台開發技術,例如:MVVM、Prism、Data Binding、各種 頁面 Page / 版面配置 Layout / 控制項 Control 的用法等等,千萬不要錯過這些 Xamarin.Forms 課程
訂閱:
文章 (Atom)
application/x-www-form-urlencoded
/multipart/form-data
/application/json
;這些不同的方式在於要將提交的資料所進行的資料封裝過程中,使用不同的編碼技術,不論使用哪種編碼技術,這些要傳送的資料,需要放置到 Http Body 內。application/x-www-form-urlencoded
編碼技術。使用 POST 要求與 application/x-www-form-urlencoded
application/x-www-form-urlencoded
這樣的編碼技術來提交資料到遠端 Web 伺服器上。Content-Type
的 Header 值為application/x-www-form-urlencoded
application/x-www-form-urlencoded
編碼之程式碼設計,可以參考FormDataPostAsync
方法中的使用 FormUrlEncodedContent 產生要 Post 的資料
region 區塊程式碼;在這個 region 程式碼區段中,我們標示了兩種做法,在 方法一: 使用字串名稱用法 中,我們使用字串的方式標示出各個資料欄位的名稱,在 方法二: 強型別用法 中,我們使用了強型別的設計技巧,也就是使用nameof
運算子取得這個欄位屬性的名稱,這樣,我們就不需要自行填入該欄位名稱字串,也就會減少了因為打錯欄位名字而造成執行時期錯的機會。Dictionary<string, string>
的變數,formDataDictionary,並且將欄位名稱使用鍵值方式填入,而該欄位的數值,則填入該 Dictionary 的數值部分。一旦都填入完成之後,我們使用基底類別庫提供的FormUrlEncodedContent
方法,將其轉換成為具有application/x-www-form-urlencoded
編碼表示格式。application/x-www-form-urlencoded
編碼方式,傳送到遠端伺服器上,我們僅需要在 PostAsync 方法的第二個引數,設定為FormUrlEncodedContent
物件,如同我們寫的程式碼:await client.PostAsync(fooFullUrl, formData)
private static async Task<APIResult> FormDataPostAsync(APIData apiData) { APIResult fooAPIResult; using (HttpClientHandler handler = new HttpClientHandler()) { using (HttpClient client = new HttpClient(handler)) { try { #region 呼叫遠端 Web API string FooUrl = $"http://vulcanwebapi.azurewebsites.net/api/Values/FormUrlencodedPost"; HttpResponseMessage response = null; #region 設定相關網址內容 var fooFullUrl = $"{FooUrl}"; // Accept 用於宣告客戶端要求服務端回應的文件型態 (底下兩種方法皆可任選其一來使用) //client.DefaultRequestHeaders.Accept.TryParseAdd("application/json"); client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); // Content-Type 用於宣告遞送給對方的文件型態 //client.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/json"); #region 使用 FormUrlEncodedContent 產生要 Post 的資料 //// 方法一: 使用字串名稱用法 //var formData = new FormUrlEncodedContent(new[] { // new KeyValuePair<string, string>("Id", apiData.Id.ToString()), // new KeyValuePair<string, string>("Name", apiData.Name), // new KeyValuePair<string, string>("Filename", apiData.Filename) //}); // 方法二: 強型別用法 // https://docs.microsoft.com/zh-tw/dotnet/csharp/language-reference/keywords/nameof Dictionary<string, string> formDataDictionary = new Dictionary<string, string>() { {nameof(APIData.Id), apiData.Id.ToString() }, {nameof(APIData.Name), apiData.Name }, {nameof(APIData.Filename), apiData.Filename } }; // https://msdn.microsoft.com/zh-tw/library/system.net.http.formurlencodedcontent(v=vs.110).aspx var formData = new FormUrlEncodedContent(formDataDictionary); #endregion response = await client.PostAsync(fooFullUrl, formData); #endregion #endregion #region 處理呼叫完成 Web API 之後的回報結果 if (response != null) { if (response.IsSuccessStatusCode == true) { // 取得呼叫完成 API 後的回報內容 String strResult = await response.Content.ReadAsStringAsync(); fooAPIResult = JsonConvert.DeserializeObject<APIResult>(strResult, new JsonSerializerSettings { MetadataPropertyHandling = MetadataPropertyHandling.Ignore }); } else { fooAPIResult = new APIResult { Success = false, Message = string.Format("Error Code:{0}, Error Message:{1}", response.StatusCode, response.RequestMessage), Payload = null, }; } } else { fooAPIResult = new APIResult { Success = false, Message = "應用程式呼叫 API 發生異常", Payload = null, }; } #endregion } catch (Exception ex) { fooAPIResult = new APIResult { Success = false, Message = ex.Message, Payload = ex, }; } } } return fooAPIResult; }
Content-Type
的需求,這些關於 Post 要求要處理地的相關動作與設定 Http 封包格式的內容,都會由 PostAsync 這個方法幫我們處理好,我們僅需要傳入該 POST 要求的 URL與FormUrlEncodedContent
物件。觸發的 Web API 動作
http://vulcanwebapi.azurewebsites.net/api/Values/FormUrlencodedPost
,此時,將會觸發 Web API 伺服器上的 Values 控制器(Controller)的APIResult FormUrlencodedPost([FromForm]APIData value)
動作(Action),其該動作的原始碼如下所示。[HttpPost("FormUrlencodedPost")] public APIResult FormUrlencodedPost([FromForm]APIData value) { APIResult foo; if (value.Id == 777) { foo = new APIResult() { Success = true, Message = "透過 post 方法,接收到 Id=777 資料", Payload = value }; } else { foo = new APIResult() { Success = false, Message = "無法發現到指定的 ID", Payload = null }; } return foo; }
進行測試
APIData
型別的物件,接著,設定該物件的相關屬性,這些屬性值,是我們要傳送到遠端伺服器端的資料,由上面的程式碼中,我們可以知道,當 Id 這個屬性值為 777 的時候,該 Web API 動作將會回覆通知,這次的呼叫是成功的,否則,會回覆此次 Web API 呼叫失敗。static void Main(string[] args) { var fooAPIData = new APIData() { Id = 777, Name = "VulcanSource", Filename = "", }; var foo = FormDataPostAsync(fooAPIData).Result; Console.WriteLine($"使用 form-urlencoded 格式與使用 Post 方法呼叫 Web API 的結果"); Console.WriteLine($"結果狀態 : {foo.Success}"); Console.WriteLine($"結果訊息 : {foo.Message}"); Console.WriteLine($"Payload : {foo.Payload}"); Console.WriteLine($""); Console.WriteLine($"Press any key to Exist...{Environment.NewLine}"); Console.ReadKey(); fooAPIData = new APIData() { Id = 123, Name = "VulcanSource", Filename = "", }; foo = FormDataPostAsync(fooAPIData).Result; Console.WriteLine($"使用 form-urlencoded 格式與使用 Post 方法呼叫 Web API 的結果"); Console.WriteLine($"結果狀態 : {foo.Success}"); Console.WriteLine($"結果訊息 : {foo.Message}"); Console.WriteLine($"Payload : {foo.Payload}"); Console.WriteLine($""); Console.WriteLine($"Press any key to Exist...{Environment.NewLine}"); Console.ReadKey(); }
執行結果
HTTP 傳送與接收原始封包
Content-Type
的 Header,其值為application/x-www-form-urlencoded
,這表示了,當把這個 Http 封包送達 Web API 伺服器的時候,也通知該伺服器,這個 Http 封包的 Body 部分,是使用application/x-www-form-urlencoded
MIME 格式進行封裝的;會有這樣的結果產生,那是因為,我們有產生一個new FormUrlEncodedContent(formDataDictionary)
這樣的物件,並且使用 PostAsync 方法,將這個物件傳送到這個非同步方法內。Content-Length
Htpp Header,說明該 Http Body 的實際資料長度是多少?Id=777&Name=VulcanSource&Filename=
,這部分的內容是屬於 Http 請求封包中的 Body 部分,也就是我們實際要送出的資料內容,這些資料是採用 Form Url Encoder 的方式來進行編碼的,此時的 Id值為 777。Content-Type
的 Header,其值為application/x-www-form-urlencoded
,這表示了,當把這個 Http 封包送達 Web API 伺服器的時候,也通知該伺服器,這個 Http 封包的 Body 部分,是使用application/x-www-form-urlencoded
MIME 格式進行封裝的;會有這樣的結果產生,那是因為,我們有產生一個new FormUrlEncodedContent(formDataDictionary)
這樣的物件,並且使用 PostAsync 方法,將這個物件傳送到這個非同步方法內。Content-Length
Htpp Header,說明該 Http Body 的實際資料長度是多少?Id=123&Name=VulcanSource&Filename=
,這部分的內容是屬於 Http 請求封包中的 Body 部分,也就是我們實際要送出的資料內容,這些資料是採用 Form Url Encoder 的方式來進行編碼的,此時的 Id值為 123。相關文章索引
Xamarin.Forms 快速上手
電子書,請點選 這裡XAML in Xamarin.Forms 基礎篇
電子書,請點選 這裡