ROMANCE DAWN for the new world

Microsoft Azure を中心とした技術情報を書いています。

ASP.NET Web API で multipart / form-data を使ってファイルをアップロードする

ASP.NET Web API でファイルをアップロードする際に、Content-Type の異なるデータを送信したかったので、multipart / form-data を使う方法を試してみました。画像ファイルのバイナリデータとファイル名を送信して、Azure Blob Storage にアップロードするシナリオで実装しました。

クライアント側

Windows ストア アプリ で、FileOpenPicker から選択した画像ファイルをアップロードします。

#MainPage.xaml.cs
private async void Button_Click(object sender, RoutedEventArgs e)
{
    var picker = new FileOpenPicker();
    picker.ViewMode = PickerViewMode.Thumbnail;
    picker.SuggestedStartLocation = PickerLocationId.PicturesLibrary;
    picker.FileTypeFilter.Add(".jpg");
    picker.FileTypeFilter.Add(".jpeg");
    var storageFile = await picker.PickSingleFileAsync();
    if (storageFile != null)
    {
        var dialog = new MessageDialog("アップロードする?");
        dialog.Commands.Add(new UICommand("OK"));
        dialog.Commands.Add(new UICommand("Cansel"));
        var stream = await storageFile.OpenStreamForReadAsync();
        var byteArray = new byte[stream.Length];
        var result = await dialog.ShowAsync();
        if (result.Label == "OK")
        {
            var multipart = new MultipartFormDataContent();
 
            stream.Read(byteArray, 0, (int)stream.Length);
            var fileContent = new ByteArrayContent(byteArray);
            fileContent.Headers.ContentType = new MediaTypeHeaderValue("image/jpeg");
            multipart.Add(fileContent, JsonConvert.SerializeObject("buffer"));
 
            var addContent = new StringContent(JsonConvert.SerializeObject("test.jpeg"));
            addContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
            multipart.Add(addContent, JsonConvert.SerializeObject("fileName"));
 
            var response = await new HttpClient().PostAsync("http://localhost:23436/api/blob/", multipart);
            response.EnsureSuccessStatusCode();
            await new MessageDialog("成功!").ShowAsync();
        }
    }
}

MultipartFormDataContent に ByteArrayContent(画像ファイル)と StringContent(ファイル名)を含めて、HttpClient クラスで POST しています。Add メソッドで指定している名前(buffer と fileName)は、サーバー側で値を取り出すときに使います。

サーバー側

Azure Blob Storage にファイルをアップロードする BlobController を用意します。

#BlobController.cs
[HttpPost]
public async Task<IHttpActionResult> Upload()
{
    if (Request.Content.IsMimeMultipartContent() == false)
    {
        throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
    }
 
    var provider = await Request.Content.ReadAsMultipartAsync();
    var fileContent = provider.Contents.First(x => x.Headers.ContentDisposition.Name == JsonConvert.SerializeObject("buffer"));
    var buffer = await fileContent.ReadAsByteArrayAsync();
    var json = await provider.Contents.First(x => x.Headers.ContentDisposition.Name == JsonConvert.SerializeObject("fileName")).ReadAsStringAsync();
    var fileName = JsonConvert.DeserializeObject<string>(json);
 
    var blob = this.container.GetBlockBlobReference(fileName);
    blob.Properties.ContentType = fileContent.Headers.ContentType.MediaType;
    await blob.UploadFromByteArrayAsync(buffer, 0, buffer.Length);
 
    return Ok();
}

ReadAsMultipartAsync メソッドで、マルチパートのコンテンツを読み取るプロバイダークラスを取得します。このプロバイダークラスを使って、クライアント側で指定した名前(buffer と fileName)をキーに、ByteArrayContent(画像ファイル)と StringContent(ファイル名)を取り出すことができます。

まとめ

ASP.NET Web API で Content-Type の異なるデータを送信するには、multipart / form-data を使います。複数のファイルをまとめてアップロードすることも可能です。ちなみに、ファイル名であれば、multipart / form-data ではなく、Content-Disposition ヘッダーの FileName を使う方法もあります。

追記:ASP.NET Core の場合

gooner.hateblo.jp