前回の投稿では、multipart / form-data を使ってファイルをアップロードしました。ASP.NET Web API において、クライアントから POST されたコンテンツが POST や PUT メソッドの引数にバインディングされるのは、JSON や XML 用の MediaTypeFormatter がデフォルトで実装されているからです。
カスタムの MediaTypeFormatter クラスを実装して、multipart / form-data のコンテンツを引数にバインディングしてみました。前回と同じく、画像ファイルのバイナリデータとファイル名を送信して、Azure Blob Storage にアップロードするシナリオです。
クライアント側
Windows ストア アプリ で、FileOpenPicker から選択した画像ファイルをアップロードします。
#MainPage.xaml.cs private async void Button_Click_2(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"); fileContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment"); fileContent.Headers.ContentDisposition.FileName = "test.jpeg"; multipart.Add(fileContent); var response = await new HttpClient().PostAsync("http://localhost:23436/api/blob/", multipart); response.EnsureSuccessStatusCode(); await new MessageDialog("成功!").ShowAsync(); } } }
MultipartFormDataContent に ByteArrayContent(画像ファイル)を含めて、HttpClient クラスで POST しています。ファイル名は、Content-Disposition ヘッダーの FileName にセットしています。
サーバー側
multipart / form-data のコンテンツを引数にバインディングする型を用意します。
#ImageMedia.cs public class ImageMedia { public byte[] Buffer { get; set; } public string FileName { get; set; } public string MediaType { get; set; } public ImageMedia(byte[] buffer, string fileName, string mediaType) { this.Buffer = buffer; this.FileName = fileName; this.MediaType = mediaType; } }
MediaTypeFormatter クラスの派生クラスを実装します。
#MultipartMediaTypeFormatter.cs public class MultipartMediaTypeFormatter : BufferedMediaTypeFormatter { public MultipartMediaTypeFormatter() { base.SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/jpeg")); base.SupportedMediaTypes.Add(new MediaTypeHeaderValue("multipart/form-data")); } public override bool CanReadType(Type type) { return (type == typeof(ImageMedia)); } public override bool CanWriteType(Type type) { return false; } public override object ReadFromStream(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger) { if (content.IsMimeMultipartContent() == false) { throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType); } var provider = content.ReadAsMultipartAsync().Result; var fileContent = provider.Contents.First(x => base.SupportedMediaTypes.Contains(x.Headers.ContentType)); var buffer = fileContent.ReadAsByteArrayAsync().Result; var fileName = fileContent.Headers.ContentDisposition.FileName; var mediaType = fileContent.Headers.ContentType.MediaType; return new ImageMedia(buffer, fileName, mediaType); } }
ReadAsMultipartAsync メソッドで、マルチパートのコンテンツを読み取るプロバイダークラスを取得します。このプロバイダークラスを使って、ByteArrayContent(画像ファイル)を取り出し、ImageMedia クラスに変換します。
WebApiConfig クラスで、カスタムの MediaTypeFormatter クラスを登録します。
#WebApiConfig.cs public static void Register(HttpConfiguration config) { config.Formatters.Add(new MultipartMediaTypeFormatter()); config.MapHttpAttributeRoutes(); }
Azure Blob Storage にファイルをアップロードする BlobController を用意します。
#BlobController.cs [HttpPost] public async Task<IHttpActionResult> Upload([FromBody]ImageMedia imageMedia) { var blob = this.container.GetBlockBlobReference(imageMedia.FileName); blob.Properties.ContentType = imageMedia.MediaType; await blob.UploadFromByteArrayAsync(imageMedia.Buffer, 0, imageMedia.Buffer.Length); return Ok(); }
引数のImageMedia クラスから画像ファイルのデータを受け取って、Blob にアップロードします。
まとめ
ASP.NET Web API では、カスタムの MediaTypeFormatter クラスを実装することで、さまざまなフォーマットのデータを POST や PUT メソッドの引数にマッピングできます。コンテンツのフォーマットやシリアライズを意識することなく、Controller クラスを実装できるようになります。
また、MediaTypeFormatter クラスの WriteToStream メソッドを実装することで、GET メソッドでカスタムの結果を返すことも可能です。