C# HttpClient WebAPI : 6. 使用 POST 要求與 multipart/form-data MIME 類型編碼內容 呼叫 Web API
在這裡,我們來看看另外一種將資料從用戶端傳送到 Web API 伺服器端的方法:multipart/form-data,在這裡,我們需要將要傳送的資料,使用 MIME 格式進行封裝起來,並且透過 POST 的方式傳送到後端 Web API 主機上。這與我們上一篇文章中所用到的 application/x-www-form-urlencoded 方法的差異,在於所將要傳送過去的資料封裝技巧不同,我們可以從原始的 Http 封包內容中,看到兩者的差異。
在這個使用 multipart/form-data 編碼技術要呼叫的 Web API URL 也是 http://vulcanwebapi.azurewebsites.net/api/Values/FormUrlencodedPost,這是因為,後端使用 ASP.NET Core 所開發出來的 Web API,可以同時支援不同編碼技術。
我們使用 var content = new MultipartFormDataContent() 敘述,建立一個 content 物件,不過,因為 MultipartFormDataContent 類別以實作 IDisposable 介面,因此,我們使用 using 陳述式將其包起來,讓這個物件於使用完後,可以儘快的釋放掉非受管理的記憶體資源。
這個範例中,將會指向 URL http://vulcanwebapi.azurewebsites.net/api/Values/FormUrlencodedPost ,此時,將會觸發 Web API 伺服器上的 Values 控制器(Controller)的 public APIResult FormUrlencodedPost([FromForm]APIData value) 動作(Action),其該動作的原始碼如下所示。
這個 Web API 動作,將會回傳一個 APIData 的 JSON 資料。
[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 呼叫失敗。
staticasync Task Main(string[] args)
{
var fooAPIData = new APIData()
{
Id = 777,
Name = "VulcanSource",
Filename = "",
};
var foo = await FormDataPostAsync(fooAPIData);
Console.WriteLine($"使用 multipart/form-data MIME 類型編碼 格式與使用 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 = await FormDataPostAsync(fooAPIData);
Console.WriteLine($"使用 multipart/form-data MIME 類型編碼 格式與使用 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();
}
執行結果
這個測試將會輸出底下內容
使用 multipart/form-data MIME 類型編碼 格式與使用 Post 方法呼叫 Web API 的結果
結果狀態 : True
結果訊息 : 透過 post 方法,接收到 Id=777 資料
Payload : {
"id": 777,
"name": "VulcanSource",
"filename": null
}
Press any key to Exist...
使用 multipart/form-data MIME 類型編碼 格式與使用 Post 方法呼叫 Web API 的結果
結果狀態 : False
結果訊息 : 無法發現到指定的 ID
Payload :
Press any key to Exist...
HTTP 傳送與接收原始封包
讓我們來看看,這個 Web API 的呼叫動作中,在請求 (Request) 與 反應 (Response) 這兩個階段,會在網路上傳送了那些 HTTP 資料
請求 (Request)
在底下為原始的 Http 請求封包內容,我們看到了 Content-Type 這個 Http Header 欄位的值設定為 multipart/form-data ,這就表示了這次 POST 要求動作將會是使用 multipart/form-data 編碼方式進行傳送資料;會有這樣的結果產生,那是因為,我們有產生一個 new MultipartFormDataContent()這樣的物件,並且使用 PostAsync 方法,將這個物件傳送到這個非同步方法內。
multipart/form-data
,在這裡,我們需要將要傳送的資料,使用 MIME 格式進行封裝起來,並且透過 POST 的方式傳送到後端 Web API 主機上。這與我們上一篇文章中所用到的application/x-www-form-urlencoded
方法的差異,在於所將要傳送過去的資料封裝技巧不同,我們可以從原始的 Http 封包內容中,看到兩者的差異。使用 POST 要求與 multipart/form-data 呼叫 Web API
multipart/form-data
編碼技術要呼叫的 Web API URL 也是http://vulcanwebapi.azurewebsites.net/api/Values/FormUrlencodedPost
,這是因為,後端使用 ASP.NET Core 所開發出來的 Web API,可以同時支援不同編碼技術。Dictionary<string, string>
泛型類別物件, formDataDictionary,將要傳送過的欄位名稱與欄位數值,分別加入到這個集合物件內。var content = new MultipartFormDataContent()
敘述,建立一個 content 物件,不過,因為MultipartFormDataContent
類別以實作IDisposable
介面,因此,我們使用 using 陳述式將其包起來,讓這個物件於使用完後,可以儘快的釋放掉非受管理的記憶體資源。MultipartFormDataContent
類別產生的物件,將會用來建立要傳送過去每筆資料紀錄,由於,在這裡我們要傳送過的都是欄位值,所以,我們使用了StringContent
類別,將要傳送過去的欄位名稱與欄位值建立出來,再加入到MultipartFormDataContent
集合物件內。await client.PostAsync(fooFullUrl, content)
方法,將要傳送過去的資料,使用multipart/form-data
編碼格式,送到 Web API 主機上對應的控制器與動作方法內。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"); //https://docs.microsoft.com/zh-tw/dotnet/csharp/language-reference/keywords/nameof #region 使用 MultipartFormDataContent 產生要 Post 的資料 // 準備要 Post 的資料 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.multipartformdatacontent(v=vs.110).aspx using (var content = new MultipartFormDataContent()) { foreach (var keyValuePair in formDataDictionary) { content.Add(new StringContent(keyValuePair.Value), keyValuePair.Key); } response = await client.PostAsync(fooFullUrl, content); } #endregion #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; }
觸發的 Web API 動作
http://vulcanwebapi.azurewebsites.net/api/Values/FormUrlencodedPost
,此時,將會觸發 Web API 伺服器上的 Values 控制器(Controller)的public 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 async Task Main(string[] args) { var fooAPIData = new APIData() { Id = 777, Name = "VulcanSource", Filename = "", }; var foo = await FormDataPostAsync(fooAPIData); Console.WriteLine($"使用 multipart/form-data MIME 類型編碼 格式與使用 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 = await FormDataPostAsync(fooAPIData); Console.WriteLine($"使用 multipart/form-data MIME 類型編碼 格式與使用 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
這個 Http Header 欄位的值設定為multipart/form-data
,這就表示了這次 POST 要求動作將會是使用multipart/form-data
編碼方式進行傳送資料;會有這樣的結果產生,那是因為,我們有產生一個new MultipartFormDataContent()
這樣的物件,並且使用 PostAsync 方法,將這個物件傳送到這個非同步方法內。Content-Length
這個 Http Header 欄位,C# 的 HttpClient 類別,會自動幫我們計算出,此次 Postmultipart/form-data
編碼封包的總共大小。boundary
這個欄位,則是宣告了每個資料的分隔識別代碼是甚麼,這個值,在每次呼叫的時候,都會產生不同的字串出來。text/plain; charset=utf-8
。multipart/form-data
的封包使用與定義規格,請參考相關說明文件。相關文章索引
關於 Xamarin 在台灣的學習技術資源