當我們要進行圖片等二進位類型檔案的上傳時候,通常也想要將一些該應用程式的資料或者狀態,上傳到後端 Web API上,若您有關看到這一系列的前面文章,您已經想到了,這可以使用查詢字串、Header、Cookie來做到。
不過,在這裡,我們將會展示另外一種做法,因為我們在用戶端使用了
multipart/form-data
資料編碼技術,所以,我們可以把我們要上傳的各種欄位,物件、狀態等資料,也透過 MultipartFormDataContent
類別物件,一並傳送過伺服器那哩,在上一篇文章中,我們使用到了 StreamContent
的物件,在更早之前的文章,我們有展示過如何使用 StringContent
的物件 (還有一種式 ByteArrayContent
類別,各位可以自行試用看看),我們將會在這篇文章中一起使用。
https://imququ.com/post/four-ways-to-post-data-in-http.html
了解更多關於 [HttpClient Class] 的使用方式
了解更多關於 [使用 async 和 await 進行非同步程式設計] 的使用方式
上傳本機文字檔案與相關欄位值到遠端伺服器上
為了簡化練習與說明,在這裡我們上傳的檔案類行為文字檔案。
當要從用戶端上傳檔案到後端 Web API 伺服器上,我們需要使用
multipart/form-data
編碼方式,所以,我們使用 var content = new MultipartFormDataContent()
敘述,建立一個 MultipartFormDataContent
類別物件。
接者,我們使用
File.Open
方法,得到要上傳文字檔案的 FileStream
物件,然後,我們建立一個 StreamContent
的物件(需要把剛剛取得的 FileStream 物件設定為建構函式的引數),設定這個物件的 Content-Type
與 Content-Disposition
描述定義,這樣,就完成了上傳圖片檔案的準備工作。var streamContent = new StreamContent(fs);
streamContent.Headers.Add("Content-Type", "text/plain");
streamContent.Headers.Add("Content-Disposition", "form-data; name=\"files\"; filename=\"" + fooSt + "\"");
content.Add(streamContent, "file", filename);
response = await client.PostAsync(fooFullUrl, content);
另外,我們也建立了許多
StringContent
物件,把要傳送的不同欄位名稱與欄位數值都設定到 StringContent
物件內,最後加入到 MultipartFormDataContent
物件內。
最後,使用
PostAsync
方法,把剛剛產生的 MultipartFormDataContent
物件,設定為該方法的引數即可。public static async Task<APIResult> UploadImageAsync(string filename, LoginInformation loginInformation)
{
APIResult fooAPIResult;
using (HttpClientHandler handler = new HttpClientHandler())
{
using (HttpClient client = new HttpClient(handler))
{
try
{
#region 呼叫遠端 Web API
//string FooUrl = $"http://localhost:53495/api/Upload/FileAndData";
string FooUrl = $"http://vulcanwebapi.azurewebsites.net/api/Upload/FileAndData";
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 將剛剛拍照的檔案,上傳到網路伺服器上(使用 Multipart 的規範)
// 規格說明請參考 https://www.w3.org/Protocols/rfc1341/7_2_Multipart.html
using (var content = new MultipartFormDataContent())
{
Dictionary<string, string> formDataDictionary = new Dictionary<string, string>()
{
{ nameof(loginInformation.Account), loginInformation.Account },
{ nameof(loginInformation.Password), loginInformation.Password },
{ nameof(loginInformation.VerifyCode), loginInformation.VerifyCode }
};
foreach (var keyValuePair in formDataDictionary)
{
content.Add(new StringContent(keyValuePair.Value), keyValuePair.Key);
}
var rootPath = Directory.GetCurrentDirectory();
// 取得這個圖片檔案的完整路徑
var path = Path.Combine(rootPath, filename);
// 開啟這個圖片檔案,並且讀取其內容
using (var fs = File.Open(path, FileMode.Open))
{
var fooSt = $"My{filename}";
var streamContent = new StreamContent(fs);
streamContent.Headers.Add("Content-Type", "text/plain");
streamContent.Headers.Add("Content-Disposition", "form-data; name=\"files\"; filename=\"" + fooSt + "\"");
content.Add(streamContent, "file", filename);
// 上傳到遠端伺服器上
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 動作
這個範例中,將會指向 URL
http://vulcanwebapi.azurewebsites.net/api/Upload/FileAndData
,此時,將會觸發 Web API 伺服器上的 Upload 控制器(Controller)的 public async Task<APIResult> FileAndData(List<IFormFile> files, LoginInformation loginInformation)
動作(Action),其該動作的原始碼如下所示。
這個 Web API 動作,將會回傳一個 APIData 的 JSON 資料。
[HttpPost("FileAndData")]
public async Task<APIResult> FileAndData(List<IFormFile> files, LoginInformation loginInformation)
{
// https://docs.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads
string webDatasRoot = Path.Combine(_HostingEnvironment.WebRootPath, "Datas");
long size = files.Sum(f => f.Length);
// full path to file in temp location
if (files.Count > 0)
{
foreach (var formFile in files)
{
if (formFile.Length > 0)
{
using (var memoryStream = new MemoryStream())
{
await formFile.CopyToAsync(memoryStream);
memoryStream.Seek(0, SeekOrigin.Begin);
var streamReader = new StreamReader(memoryStream);
var fooContent = streamReader.ReadToEnd();
fooAPIResult.Success = true;
fooAPIResult.Message = "檔案上傳成功";
fooAPIResult.Payload = new LoginInformation
{
Account = $">> {loginInformation.Account}",
Password = $">> {loginInformation.Account}",
VerifyCode = fooContent
};
}
}
}
}
else
{
fooAPIResult.Success = false;
fooAPIResult.Message = "沒有任何檔案上傳";
fooAPIResult.Payload = null;
}
return fooAPIResult;
}
進行測試
在程式進入點函式,我們建立一個
LoginInformation
型別的物件,接著,設定該物件的相關屬性,這些屬性值,是我們要傳送到遠端伺服器端的資料,另外,也會指明要傳送到遠端伺服器上的文字檔案名稱。static async Task Main(string[] args)
{
var fooLoginInformation = new LoginInformation()
{
Account = "Vulcan",
Password = "123",
VerifyCode = "abc"
};
var foo = await UploadImageAsync("Readme.txt", fooLoginInformation);
fooLoginInformation = JsonConvert.DeserializeObject<LoginInformation>(foo.Payload.ToString());
Console.WriteLine($"使用 MultiPart/Form-Data 格式傳送文字檔案與資料、使用 Post 方法呼叫 Web API 的結果");
Console.WriteLine($"結果狀態 : {foo.Success}");
Console.WriteLine($"結果訊息 : {foo.Message}");
Console.WriteLine($"Payload : {foo.Payload}");
Console.WriteLine($"Account : {fooLoginInformation.Account}");
Console.WriteLine($"Password : {fooLoginInformation.Password}");
Console.WriteLine($"文字檔案內容 : {fooLoginInformation.VerifyCode}");
Console.WriteLine($"Press any key to Exist...{Environment.NewLine}");
Console.ReadKey();
}
執行結果
這個測試將會輸出底下內容
使用 MultiPart/Form-Data 格式傳送文字檔案與資料、使用 Post 方法呼叫 Web API 的結果
結果狀態 : True
結果訊息 : 檔案上傳成功
Payload : {
"account": ">> Vulcan",
"password": ">> Vulcan",
"verifyCode": "這是一個文字類型檔案內容"
}
Account : >> Vulcan
Password : >> Vulcan
文字檔案內容 : 這是一個文字類型檔案內容
Press any key to Exist...
HTTP 傳送與接收原始封包
讓我們來看看,這個 Web API 的呼叫動作中,在請求 (Request) 與 反應 (Response) 這兩個階段,會在網路上傳送了那些 HTTP 資料
- 請求 (Request)在這裡的第三行中,您將會看到 Http Header
Content-Type
其設定值為multipart/form-data;
,這表示了 這次的 POST 請求動作中,在 Http Body 封包內的資料,將會使用multipart/form-data
編碼技術。從底下的 Http 封包中,我們可以看到了總共有四個欄位資料,使用了multipart/form-data
編碼技術,而這四個欄位的定義內容,都會使用 Http Header 的boundary
值,進行分割,這樣,後端伺服器才能夠清楚的分辨不同欄位的資料。
POST http://vulcanwebapi.azurewebsites.net/api/Upload/FileAndData HTTP/1.1
Accept: application/json
Content-Type: multipart/form-data; boundary="7e83396d-cea9-4d3f-9f03-02fb0dd30274"
Host: vulcanwebapi.azurewebsites.net
Content-Length: 631
Expect: 100-continue
Connection: Keep-Alive
--7e83396d-cea9-4d3f-9f03-02fb0dd30274
Content-Type: text/plain; charset=utf-8
Content-Disposition: form-data; name=Account
Vulcan
--7e83396d-cea9-4d3f-9f03-02fb0dd30274
Content-Type: text/plain; charset=utf-8
Content-Disposition: form-data; name=Password
123
--7e83396d-cea9-4d3f-9f03-02fb0dd30274
Content-Type: text/plain; charset=utf-8
Content-Disposition: form-data; name=VerifyCode
abc
--7e83396d-cea9-4d3f-9f03-02fb0dd30274
Content-Type: text/plain
Content-Disposition: form-data; name="files"; filename="MyReadme.txt"
這是一個文字類型檔案內容
--7e83396d-cea9-4d3f-9f03-02fb0dd30274--
- 反應 (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: Mon, 23 Oct 2017 02:15:14 GMT
9c
{"success":true,"message":"檔案上傳成功","payload":{"account":">> Vulcan","password":">> Vulcan","verifyCode":"這是一個文字類型檔案內容"}}
0
相關文章索引
C# HttpClient WebAPI 系列文章索引
了解更多關於 [HttpClient Class] 的使用方式
了解更多關於 [使用 async 和 await 進行非同步程式設計] 的使用方式
關於 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 課程