傳送 ASP.NET Web API 的 HTML 表單資料:檔案上傳和多部分 MIME
第 2 部分:檔案上傳與多部分 MIME
本教學課程示範如何將檔案上傳至 Web API。 它也會說明如何處理多部分 MIME 資料。
注意
以下是上傳檔案的 HTML 表單範例:
<form name="form1" method="post" enctype="multipart/form-data" action="api/upload">
<div>
<label for="caption">Image Caption</label>
<input name="caption" type="text" />
</div>
<div>
<label for="image1">Image File</label>
<input name="image1" type="file" />
</div>
<div>
<input type="submit" value="Submit" />
</div>
</form>
此表單包含文字輸入控制項和檔案輸入控制項。 當表單包含檔案輸入控制項時,enctype 屬性應該一律為「multipart/form-data」,指定表單會以多部分 MIME 訊息的形式傳送。
透過查看範例要求,多部分 MIME 訊息的格式最容易瞭解:
POST http://localhost:50460/api/values/1 HTTP/1.1
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:12.0) Gecko/20100101 Firefox/12.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=---------------------------41184676334
Content-Length: 29278
-----------------------------41184676334
Content-Disposition: form-data; name="caption"
Summer vacation
-----------------------------41184676334
Content-Disposition: form-data; name="image1"; filename="GrandCanyon.jpg"
Content-Type: image/jpeg
(Binary data not shown)
-----------------------------41184676334--
此訊息分成兩個組件,每個表單控制項各一個。 部分界限是由以虛線開頭的線條表示。
注意
組件界限包含隨機元件 (「41184676334」),以確保界限字串不會意外出現在訊息元件內。
每個訊息組件都包含一或多個標頭,後面接著組件內容。
- Content-Disposition 標頭包含控制項的名稱。 如果是檔案,它也會包含檔名。
- Content-Type 標頭說明組件中的資料。 如果省略此標頭,預設值為 text/plain。
在上一個範例中,使用者上傳了名為 GrandCanyon.jpg 的檔案,其內容類型為 image/jpeg;文字輸入的值是「暑假」。
檔案上傳
現在讓我們看看從多部分 MIME 訊息讀取檔案的 Web API 控制器。 控制器會以非同步方式讀取檔案。 Web API 支援使用以任務為基礎的程式設計模型進行非同步動作。 首先,如果您的目標是 .NET Framework 4.5,則這是程式碼,它支援 async 和 await 關鍵字。
using System.Diagnostics;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web;
using System.Web.Http;
public class UploadController : ApiController
{
public async Task<HttpResponseMessage> PostFormData()
{
// Check if the request contains multipart/form-data.
if (!Request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
string root = HttpContext.Current.Server.MapPath("~/App_Data");
var provider = new MultipartFormDataStreamProvider(root);
try
{
// Read the form data.
await Request.Content.ReadAsMultipartAsync(provider);
// This illustrates how to get the file names.
foreach (MultipartFileData file in provider.FileData)
{
Trace.WriteLine(file.Headers.ContentDisposition.FileName);
Trace.WriteLine("Server file path: " + file.LocalFileName);
}
return Request.CreateResponse(HttpStatusCode.OK);
}
catch (System.Exception e)
{
return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, e);
}
}
}
請注意,控制器動作不會採取任何參數。 這是因為我們在動作內處理要求本文,而不叫用媒體類型格式器。
IsMultipartContent 方法會檢查要求是否包含多部分 MIME 訊息。 如果沒有,控制器會傳回 HTTP 狀態代碼 415 (不支援的媒體類型)。
MultipartFormDataStreamProvider 類別是協助程式物件,可配置上傳檔案的檔案資料流。 若要讀取多部分 MIME 訊息,請呼叫 ReadAsMultipartAsync 方法。 此方法會擷取所有訊息組件,並將其寫入 MultipartFormDataStreamProvider 所提供的資料流中。
此方法完成後,您可以從 FileData 屬性獲取有關檔案的資訊,該屬性是 MultipartFileData 物件的集合。
- MultipartFileData.FileName 是儲存檔案之伺服器上的本機檔名。
- MultipartFileData.Headers 包含組件標頭 (而非要求標頭)。 您可以使用此項目來存取 Content_Disposition 和內容類型標頭。
如名稱所示,ReadAsMultipartAsync 是非同步方法。 若要在方法完成後執行工作,請使用接續任務 (.NET 4.0) 或 await 關鍵字 (.NET 4.5)。
以下是先前程式碼的 .NET Framework 4.0 版本:
public Task<HttpResponseMessage> PostFormData()
{
// Check if the request contains multipart/form-data.
if (!Request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
string root = HttpContext.Current.Server.MapPath("~/App_Data");
var provider = new MultipartFormDataStreamProvider(root);
// Read the form data and return an async task.
var task = Request.Content.ReadAsMultipartAsync(provider).
ContinueWith<HttpResponseMessage>(t =>
{
if (t.IsFaulted || t.IsCanceled)
{
Request.CreateErrorResponse(HttpStatusCode.InternalServerError, t.Exception);
}
// This illustrates how to get the file names.
foreach (MultipartFileData file in provider.FileData)
{
Trace.WriteLine(file.Headers.ContentDisposition.FileName);
Trace.WriteLine("Server file path: " + file.LocalFileName);
}
return Request.CreateResponse(HttpStatusCode.OK);
});
return task;
}
讀取表單控制項資料
我稍早顯示的 HTML 表單具有文字輸入控制項。
<div>
<label for="caption">Image Caption</label>
<input name="caption" type="text" />
</div>
您可以從 MultipartFormDataStreamProvider 的 FormData 屬性取得控制項的值。
public async Task<HttpResponseMessage> PostFormData()
{
if (!Request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
string root = HttpContext.Current.Server.MapPath("~/App_Data");
var provider = new MultipartFormDataStreamProvider(root);
try
{
await Request.Content.ReadAsMultipartAsync(provider);
// Show all the key-value pairs.
foreach (var key in provider.FormData.AllKeys)
{
foreach (var val in provider.FormData.GetValues(key))
{
Trace.WriteLine(string.Format("{0}: {1}", key, val));
}
}
return Request.CreateResponse(HttpStatusCode.OK);
}
catch (System.Exception e)
{
return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, e);
}
}
FormData 是 NameValueCollection,其中包含表單控制項的名稱/值組。 集合可以包含重複的索引鍵。 請考慮以下表單:
<form name="trip_search" method="post" enctype="multipart/form-data" action="api/upload">
<div>
<input type="radio" name="trip" value="round-trip"/>
Round-Trip
</div>
<div>
<input type="radio" name="trip" value="one-way"/>
One-Way
</div>
<div>
<input type="checkbox" name="options" value="nonstop" />
Only show non-stop flights
</div>
<div>
<input type="checkbox" name="options" value="airports" />
Compare nearby airports
</div>
<div>
<input type="checkbox" name="options" value="dates" />
My travel dates are flexible
</div>
<div>
<label for="seat">Seating Preference</label>
<select name="seat">
<option value="aisle">Aisle</option>
<option value="window">Window</option>
<option value="center">Center</option>
<option value="none">No Preference</option>
</select>
</div>
</form>
要求本文可能如下所示:
-----------------------------7dc1d13623304d6
Content-Disposition: form-data; name="trip"
round-trip
-----------------------------7dc1d13623304d6
Content-Disposition: form-data; name="options"
nonstop
-----------------------------7dc1d13623304d6
Content-Disposition: form-data; name="options"
dates
-----------------------------7dc1d13623304d6
Content-Disposition: form-data; name="seat"
window
-----------------------------7dc1d13623304d6--
在此情況下,FormData 集合會包含下列索引鍵/值組:
- 行程:往返
- 選項:非停止
- 選項:日期
- 座位:窗戶