しばやん雑記

Azure とメイドさんが大好きなフリーランスのプログラマーのブログ

ASP.NET Web API でアップロードされたファイルを扱う方法を知らなかったので調べた

ASP.NET MVC でファイルアップロードを実装する場合は、モデルバインダが HttpPostedFileBase クラスなどに自動的にバインドしてくれていましたが、Web API だとそこまで面倒は見てくれないみたいです。

これまで Web API での実装方法を知らなかったですが、今回ついに観念して調べました。

Sending HTML Form Data in ASP.NET Web API: File Upload and Multipart MIME | The ASP.NET Site

MultipartFormDataStreamProvider クラスを使うとアップロードされたファイルをストレージ上に保存してくれるみたいですね。メモリストリームとして扱い続けるよりも楽な感じがします。

サンプルコードでは App_Data に入れてましたが、個人的に Temp のが良い気がしたので変えました。

public async Task<IHttpActionResult> FileUpload()
{
    // multipart/form-data 以外は 415 を返す
    if (!Request.Content.IsMimeMultipartContent())
    {
        return StatusCode(HttpStatusCode.UnsupportedMediaType);
    }

    // マルチパートデータを一時的に保存する場所を指定
    var rootPath = Path.GetTempPath();
    var provider = new MultipartFormDataStreamProvider(rootPath);

    // 実際に読み込む
    await Request.Content.ReadAsMultipartAsync(provider);

    // ファイルデータを読む
    foreach (var file in provider.FileData)
    {
        // ファイル情報を取得
        var fileInfo = new FileInfo(file.LocalFileName);
    }

    return Ok();
}

アップロード時に渡されたファイル名などの情報は Headers プロパティから参照できます。

MultipartFormDataStreamProvider クラスを使って実際にアップロードしてみると、Temp ディレクトリにユニークなファイル名でアップロードしたファイルが保存されていることが分かりますね。

f:id:shiba-yan:20150319160736p:plain

単純なファイルアップロードが必要な場合はこれで十分だと思います。

他にファイルアップロード時に使えるプロバイダーとしては MultipartFormDataRemoteStreamProvider クラスも用意されています。このプロバイダーは書き込み可能なストリームを返すような実装を用意することで、アップロードされたファイルを直接保存することが出来ます。

実際に Azure Blob へのストリームを返すプロバイダーを実装してみました。

public class AzureBlobRemoteStreamProvider : MultipartFormDataRemoteStreamProvider
{
    public AzureBlobRemoteStreamProvider()
    {
        _storageAccount = CloudStorageAccount.Parse("...");
    }

    private readonly CloudStorageAccount _storageAccount;

    public override RemoteStreamInfo GetRemoteStream(HttpContent parent, HttpContentHeaders headers)
    {
        var fileName = headers.ContentDisposition.FileName.Trim('"');

        var blobClient = _storageAccount.CreateCloudBlobClient();

        var container = blobClient.GetContainerReference("upload");
        var blob = container.GetBlockBlobReference(fileName);

        var stream = blob.OpenWrite();

        return new RemoteStreamInfo(stream, blob.Uri.AbsoluteUri, fileName);
    }
}

アップロードされたファイル名のまま upload というコンテナに保存するだけの簡単なプロバイダーです。

使い方はシンプルで、ReadAsMultipartAsync に渡すプロバイダーを切り替えるだけです。

public async Task<IHttpActionResult> Post()
{
    var provider = new AzureBlobRemoteStreamProvider();

    await Request.Content.ReadAsMultipartAsync(provider);

    foreach (var file in provider.FileData)
    {
        var blobUri = file.Location;
    }

    return Ok();
}

実際にアップロードすると、Blob に書きこまれていることが確認出来ます。

f:id:shiba-yan:20150319162151p:plain

GetRemoteStream メソッドでもうちょっと処理を頑張ると、アップロードしたユーザー別にコンテナを作成して保存するといった処理も実装できると思います。