ASP.NET Web API で HTML フォーム データを送信する: ファイル アップロードとマルチパート MIME
パート 2: ファイルのアップロードとマルチパート MIME
このチュートリアルでは、Web API にファイルをアップロードする方法について説明します。 また、マルチパート MIME データを処理する方法についても説明します。
Note
ファイルをアップロードするための 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--
このメッセージは、フォーム コントロールごとにそれぞれ 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>
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 コレクションには次のキーと値のペアが含まれます。
- trip: round-trip (往復)
- options: nonstop (直行便)
- options: dates (日程)
- seat: window (窓側座席)