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>

Screenshot of an HTML form showing a Image Caption field with the text Summer Vacation and an Image File file picker.

このフォームには、テキスト入力コントロールとファイル入力コントロールが含まれています。 フォームにファイル入力コントロールが含まれている場合、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--

このメッセージは、フォーム コントロールごとにそれぞれ 2 つのパーツに分かれています。 パーツの境界は、ダッシュで始まる線で示されます。

Note

パーツの境界には、境界文字列が誤ってメッセージ パーツ内に表示されないようにするためのランダム なコンポーネント ("41184676334") が含まれています。

メッセージの各パーツには 1 つ以上のヘッダーと、その後にパーツの内容が含まれます。

  • Content-Disposition ヘッダーには、コントロールの名前が含まれています。 ファイルの場合は、ファイル名も含まれます。
  • Content-Type ヘッダーは、パーツ内のデータを記述します。 このヘッダーを省略すると、既定値は text/plain になります。

前の例では、ユーザーはコンテンツタイプが image/jpeg の GrandCanyon.jpg というファイルをアップロードしました。テキスト入力の値は "Summer Vacation" でした。

ファイルのアップロード

次に、マルチパート MIME メッセージからファイルを読み取る Web API コントローラーを見てみましょう。 コントローラーはファイルを非同期的に読み取ります。 Web API では、タスク ベースのプログラミング モデルを使用した非同期アクションがサポートされています。 まず、.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 によって提供されるストリームに書き込みます。

メソッドが完了すると、MultipartFileData オブジェクトのコレクションである FileData プロパティからファイルに関する情報を取得できます。

  • MultipartFileData.FileName は、ファイルが保存されたサーバー上のローカル ファイル名です。
  • MultipartFileData.Headers には、(要求ヘッダーではなく) パーツ ヘッダーが含まれています。 これを使用して、Content_Disposition ヘッダーと Content-Type ヘッダーにアクセスできます。

名前が示すように、ReadAsMultipartAsync は非同期メソッドです。 メソッドの完了後に作業を実行するには、継続タスク (.NET 4.0) または await キーワード (keyword) (.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);
    }
}

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>

Screenshot of the HTML form with the Round-Trip circle filled in and the Only show non-stop flights and My travel dates are flexible boxes checked.

要求本文は次のようになります。

-----------------------------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 コレクションには次のキーと値のペアが含まれます。

  • trip: round-trip (往復)
  • options: nonstop (直行便)
  • options: dates (日程)
  • seat: window (窓側座席)