在 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>

HTML 表單的螢幕擷取畫面,其中顯示 [影像標題] 欄位,其中文字為 [假期] 和 [影像檔案] 檔案選擇器。

此表單包含文字輸入控制項和檔案輸入控制項。 當表單包含檔案輸入控制項時, 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;和文字輸入的值是「Summer Vacation」。

檔案上傳

現在讓我們看看從多部分 MIME 訊息讀取檔案的 Web API 控制器。 控制器會以非同步方式讀取檔案。 Web API 支援使用 工作型程式設計模型進行非同步動作。 首先,如果您的目標是支援asyncawait關鍵字的 .NET Framework 4.5,以下是程式碼。

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>

您可以從MultipartFormDataStreamProviderFormData屬性取得控制項的值。

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);
    }
}

FormDataNameValueCollection ,其中包含表單控制項的名稱/值組。 集合可以包含重複的索引鍵。 請考慮此表單:

<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>

HTML 表單的螢幕擷取畫面,其中已填入Round-Trip圓形,並核取 [僅顯示非停止航班] 和 [我的旅遊日期] 有彈性方塊。

要求本文看起來可能如下:

-----------------------------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 集合會包含下列索引鍵/值組:

  • 行程:來回
  • 選項:非停止
  • 選項:日期
  • 基座:視窗