しばやん雑記

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

ASP.NET MVC 3 開発入門 (10) - 動画アップロードの実装

ASP.NET MVC 3 開発入門 - インデックス

前回、実装を行わなかった動画のアップロードを今回で実装していきます。動画のアップロードを実装しなかった理由ですが、ASP.NET MVC 3 ではファイルのアップロードへの対応が完全には出来ていないからでした。
具体的にどう対応していないかをまとめました。

  • <input type="file"> を生成する HTML ヘルパーが無い
    • タグを手書きするか自分でヘルパーを作成する必要がある
  • アップロードされたファイルの検証属性が無い
    • Required ぐらいは使える
  • HttpPostedFileBase 型をスキャフォールディングで認識しない
    • これは HTML ヘルパーが存在しないことに依存している予感

あまり知られていないのかもしれないですが、致命的なほどに機能が欠けていたりします。しかしモデルバインダは HttpPostedFileBase 型を認識して、アップロードされたファイルをちゃんとバインドしてくれますので、自分で検証を行うコードを書く必要がありますが、今までよりも楽にかけることは間違いないです。

ぐだぐだ言うのはこれぐらいにして、実際にコードを書いていきましょう。前述したように、ファイルアップロード自体はモデルバインダのおかげでお手軽に使えるようになりましたが、検証属性が用意されていないので今回は Request.Files プロパティを使うことにします。

アクションの引数に以下のように追加してもいいのですが、非常に不恰好ですね。

public ActionResult Create(Video video, HttpPostedFileBase file)
{
    // 実装は省略
}

まずはアップロードされたファイルを取得します。Request.Files は ASP.NET で利用できるプロパティで、input タグの name に指定したキー名を指定すると取得できます。これからファイルアップロードに使用するキー名は "File" で共通とします。

// file の型は HttpPostedFileBase になる
var file = Request.Files["File"];

注意する点としてはファイルがアップロードされなかったときには null が返ってくることですね。これでアップロードされたファイルが取得できましたので、ファイルの検証を実装しましょう。今回はアップロードされているかどうかとファイルの種類に対して検証を行います。

エラーメッセージは ModelState クラスの AddModelError メソッドで追加すると、Html.ValidationMessage などで出力することが出来ます。

if (file == null)
{
    ModelState.AddModelError("File", "ファイルを選択してください。");
    return View(video);
}
if (file.ContentType != "video/mp4" && file.ContentType != "video/x-m4v")
{
    ModelState.AddModelError("File", "ファイルの形式が不正です。");
    return View(video);
}

file が null の時はアップロードされていないので ModelState.AddModelError メソッドを呼び出してエラーメッセージを出力します。同様に ContentType が "video/mp4" でも "video/x-m4v" でもない時にはファイルの形式が不正というエラーを出力するようにします。

最後にアップロードされたファイルの保存を実装します。ファイルの保存したいは HttpPostedFileBase.SaveAs メソッドを使うだけで指定したパスに保存することが出来ますが、保存するディレクトリとファイル名の規約を決める必要があります。

今回はシンプルに ~/Videos ディレクトリを作成して、「(プライマリキー) + .mp4」 というファイル名で保存することにします。

// Video ディレクトリに 1.mp4, 2.mp4, ... のように名前を付けて保存する
var path = Server.MapPath(string.Format("~/Videos/{0}.mp4", video.VideoId));

file.SaveAs(path);

プライマリキーを必要としますので、リポジトリへの追加と反映が終わってから呼び出す必要があります。Server.MapPath メソッドは仮想パスをサーバの物理パスに変換してくれますので、それを利用して保存先のパスを作成しています。

それでは実装したアクション全体を確認してみましょう。ファイルの検証は手作業で行っているので、検証属性の便利さを実感するようなコードですね。ファイルアップロード用の検証属性は ASP.NET MVC 3 Futures に FileExtensionsAttribute という拡張子を調べるものはあるのですが、どう考えても機能不足なので使いませんでした。

//
// POST: /Video/Create

[HttpPost]
public ActionResult Create(Video video)
{
    try
    {
        if (!ModelState.IsValid)
        {
            return View(video);
        }

        // アップロードされたファイルを取得
        var file = Request.Files["File"];

        // ファイルを検証
        if (file == null)
        {
            ModelState.AddModelError("File", "ファイルを選択してください。");
            return View(video);
        }
        if (file.ContentType != "video/mp4" && file.ContentType != "video/x-m4v")
        {
            ModelState.AddModelError("File", "ファイルの形式が不正です。");
            return View(video);
        }

        // 作成、更新日時をセット
        video.CreatedAt = video.UpdatedAt = DateTime.Now;

        // データベースへ追加、反映
        _videoRepository.Add(video);
        _videoRepository.Save();

        // アップロードされた動画に名前を付けて保存
        var path = Server.MapPath(string.Format("~/Videos/{0}.mp4", video.VideoId));

        file.SaveAs(path);

        return RedirectToAction("Index");
    }
    catch
    {
        return View(video);
    }
}

ファイルのサイズとコンテントタイプを検証する属性は、以前私が作成したものもありますので参考にしていただけると嬉しいです。

ASP.NET MVC でアップロードされたファイルを検証する - しばやん雑記
ASP.NET MVC 3 と File API を利用してアップロード前に検証を行う - しばやん雑記

あとは Web.config の system.web セクション内で、処理できる最大の HTTP リクエストサイズをそれなりに大きい値に指定しておきましょう。デフォルトでは 5MB などの小さい値になっているので、動画などのサイズの大きなファイルをアップロードしようとするとエラーになってしまいます。

<httpRuntime maxRequestLength="52428800"/>

これで Create アクションが動画アップロード機能含めて完成しました。Edit アクションも同様に実装することが出来ますので省略させていただきますが、ファイルは基本的に上書きされることに注意してください。

それでは次回から ASP.NET MVC 3 の目玉機能である Razor を利用してビューを作成していきたいと思います。お疲れ様でした。