在上一篇文章中,我們展示了如何使用 GET 要求,從網站中下載一個圖片檔案到本機電腦上,現在,我們需要進行二進位檔案型上傳的練習,要上傳的圖片檔案,我們已將包含在 Visual Studio 專案內。
了解更多關於 [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", "application/octet-stream");
streamContent.Headers.Add("Content-Disposition", "form-data; name=\"files\"; filename=\"" + fooSt + "\"");
content.Add(streamContent, "file", filename);
最後,使用
PostAsync
方法,把剛剛產生的 MultipartFormDataContent
物件,設定為該方法的引數即可。public static async Task<APIResult> UploadImageAsync(string filename)
{
APIResult fooAPIResult;
using (HttpClientHandler handler = new HttpClientHandler())
{
using (HttpClient client = new HttpClient(handler))
{
try
{
#region 呼叫遠端 Web API
string FooUrl = $"http://vulcanwebapi.azurewebsites.net/api/Upload";
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())
{
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", "application/octet-stream");
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
,此時,將會觸發 Web API 伺服器上的 Upload 控制器(Controller)的 public async Task<APIResult> Post(List<IFormFile> files)
動作(Action),其該動作的原始碼如下所示。
由這個方法參數可以看的出來,這個 Web API 動作 (Action)方法,需要使用 POST 請求來呼叫,並且,可以接受一次上傳多個檔案的請求。(在用戶端若想要上傳多個檔案,可以建立多個
StreamContent
物件,並且將這些物件都加入到 MultipartFormDataContent
物件內即可)
這個 Web API 動作,將會回傳一個 APIData 的 JSON 資料。
[HttpPost]
public async Task<APIResult> Post(List<IFormFile> files)
{
// 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)
{
var filePath = Path.Combine(webDatasRoot, formFile.FileName);
using (var stream = new FileStream(filePath, FileMode.Create))
{
await formFile.CopyToAsync(stream);
}
fooAPIResult.Success = true;
fooAPIResult.Message = "檔案上傳成功";
fooAPIResult.Payload = new APIData
{
Id = 3000,
Name = "Your Name",
Filename = formFile.FileName
};
}
}
}
else
{
fooAPIResult.Success = false;
fooAPIResult.Message = "沒有任何檔案上傳";
fooAPIResult.Payload = null;
}
return fooAPIResult;
}
進行測試
在程式進入點函式,我們直接呼叫 UploadImageAsync 方法,並且指定要上傳圖片檔案的名出,一旦該題片上傳之後,將會使用瀏覽器將這個 Web 伺服器上的圖片開起來。
static async Task Main(string[] args)
{
await UploadImageAsync("vulcan.png");
Process myProcess = new Process();
try
{
// true is the default, but it is important not to set it to false
myProcess.StartInfo.UseShellExecute = true;
myProcess.StartInfo.FileName = "https://vulcanwebapi.azurewebsites.net/Datas/Myvulcan.png";
myProcess.Start();
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
Console.WriteLine($"Press any key to Exist...{Environment.NewLine}");
Console.ReadKey();
}
執行結果
這個測試將會輸出底下內容
Press any key to Exist...
HTTP 傳送與接收原始封包
讓我們來看看,這個 Web API 的呼叫動作中,在請求 (Request) 與 反應 (Response) 這兩個階段,會在網路上傳送了那些 HTTP 資料
- 請求 (Request)在這裡的第三行中,您將會看到
Content-Type
標示為multipart/form-data
,說明了這次 POST 請求的資料編碼封裝方式,而由於採用這樣的編碼方式,所以當然會看到這樣的 Http Headerboundary="cedff4d2-6ac3-492e-8e5a-56ba4fc9ea27"
,這是因為每個要傳遞的欄位資料,都需要使用這個 boundary 值在 Http Body 內進行使用,最後,我們使用了 HttpClient 類別,所以,對於 Http HeaderContent-Length
並不需要做任何的計算,HttpClient 會幫我們計算出來(也就是說,若這個數值計算或者寫入不正確,將會導致這次的 POST 請求無法正常完成。由於是要上傳圖片檔案到遠端 Web API 伺服器上,因此,在 Http Body 的部分會有很多關於該圖片檔案的編碼內容,我們在這裡僅列出一小部分做為參考之用。
POST http://vulcanwebapi.azurewebsites.net/api/Upload HTTP/1.1
Accept: application/json
Content-Type: multipart/form-data; boundary="cedff4d2-6ac3-492e-8e5a-56ba4fc9ea27"
Host: vulcanwebapi.azurewebsites.net
Content-Length: 160584
Expect: 100-continue
Connection: Keep-Alive
--cedff4d2-6ac3-492e-8e5a-56ba4fc9ea27
Content-Type: application/octet-stream
Content-Disposition: form-data; name="files"; filename="Myvulcan.png"
PNG
IHDR u pHYs + tIME 8ǫe tEXtAuthor H tEXtDescription !#
tEXtCopyright : tEXtCreation time 5 tEXtSoftware ]p :
b zBO = ' e Gz! b zBO = ' " * zBO = ' E zBO = 3 \ IEND B`
--cedff4d2-6ac3-492e-8e5a-56ba4fc9ea27--
- 反應 (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:13:20 GMT
72
{"success":true,"message":"檔案上傳成功","payload":{"id":3000,"name":"Your Name","filename":"Myvulcan.png"}}
0
相關文章索引
C# HttpClient WebAPI 系列文章索引
了解更多關於 [HttpClient Class] 的使用方式
了解更多關於 [使用 async 和 await 進行非同步程式設計] 的使用方式