しばやん雑記

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

ASP.NET MVC でアップロードされたファイルを検証する

写真のアップロード機能を実装していた時に、そういえば ASP.NET MVC ってファイルのアップロードへの対応はモデルバインダ周りだけだと気が付きました。

HTML ヘルパーでアップロード用のフォームは作成できないですし、当然ながらファイル周りの検証はサポートされていません。しかし、ファイルの種類やサイズを制限したいことは多々あると思いますので、アップロードされたファイルの検証を行ってみました。

ASP.NET MVC で検証を行うにはデータアノテーションを使うか、IValidatableObject インタフェースを実装する必要があります。IValidatableObject を実装するとプロパティ間をまたいだ検証などが出来るのですが、そこまで必要になるケースは少ないと思いますので普通はデータアノテーションで問題ないと思います。

今回は他でも使えるようにするために、検証属性を実装してアップロードされたファイルの検証を行います。

独自の検証属性を作る

ざっくり言ってしまうと、ValidationAttribute を継承したクラスを作って IsValid メソッドをオーバーライドするだけです。非常に簡単ですよね!

独自の検証属性の詳しい作り方は ナオキにASP.NET(仮) : ASP.NET MVC 2 でカスタム属性の作成と、クライアントサイド検証のポイント が MVC 2 向けですがまとまっていておすすめです。クライアント検証に関しては IClientValidatable なるインターフェースが MVC 3 から追加されているようなので、それはまた後日検証してみたいと思います。

実際にアップロードされたファイルの検証属性を作成してみました。

// アップロードされたファイルの種類とサイズを検証する属性
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public sealed class UploadFileAttribute : ValidationAttribute
{
    public UploadFileAttribute()
    {
        // 最大のファイルサイズは 2147483647 バイト (= 2 ギガバイト)
        MaxLength = int.MaxValue;

        // デフォルトのエラーメッセージを設定する
        ErrorMessage = "ファイル形式が不正です。";
    }

    // 許可する最大ファイルサイズ
    public int MaxLength { get; set; }

    // 許可する拡張子
    public string Extensions { get; set; }

    public override bool IsValid(object value)
    {
        // 値が null の時には何もしない
        if (value == null)
        {
            return true;
        }

        // 値がアップロードされたファイルか確認
        var postedFile = value as HttpPostedFileBase;

        if (postedFile == null)
        {
            return true;
        }

        // ファイルサイズを検証
        if (postedFile.ContentLength > MaxLength)
        {
            // MaxLength より大きいのでエラー
            return false;
        }

        // 拡張子を検証
        var ext = Path.GetExtension(postedFile.FileName).Replace(".", "");

        if (!string.IsNullOrEmpty(Extensions) && !Extensions.Split(';').Any(p => p == ext))
        {
            // 許可されていない拡張子なのでエラー
            return false;
        }

        // 検証が成功
        return true;
    }
}

この UploadFile 属性を使うことでアップロードされたファイルのサイズと拡張子の検証が行えるようになります。それでは実際に使ってみましょう。

モデル

// データアノテーションを使うためにモデルを作成する
public class FileModel
{
    // ファイルサイズが 64KB 以下で拡張子が txt なファイルのみアップロード可能
    [DisplayName("plain text")]
    [UploadFile(Extensions = "txt", MaxLength = 65536)]
    public HttpPostedFileBase UploadFile { get; set; }
}

コントローラ

public class FileController : Controller
{
    public ActionResult Upload()
    {
        return View();
    }

    [HttpPost]
    public ActionResult Upload(FileModel model)
    {
        // 検証はモデルバインダが行ってくれるので何もしなくてもいい
        return View();
    }
}

ビュー

@model FileUploadSample.Models.FileModel

@{
    ViewBag.Title = "Upload";
}

<h2>Upload</h2>

@using (Html.BeginForm("Upload", "File", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
    @Html.ValidationSummary(true)
    <fieldset>
        <legend>File Upload</legend>

        <div class="editor-label">
            @Html.LabelFor(m => m.UploadFile)
        </div>
        <div class="editor-field">
            <input type="file" name="UploadFile" size="50" />
            @Html.ValidationMessageFor(m => m.UploadFile)
        </div>

        <p>
            <input type="submit" value="upload" />
        </p>
    </fieldset>
}

準備が完了したので、実際に動かして試してみました。アップロードしたファイルがわかりやすいように、POST した後にもう一度同じファイルを選択してあります。

まずはテキストファイルをアップロードした時です。エラーは表示されません。

次は Excel ファイルをアップロードした時です。拡張子は txt しか許可していないので、ちゃんとエラーメッセージが表示されました!

最後にサイズが 64KB 以上のファイルをアップロードした時です。これもちゃんとエラーメッセージが表示されました!!

実は FileExtensionsAttribute という検証属性が MvcFutures に含まれているんですが、今回は検証属性の作り方も兼ねて書いてみました。これからも ASP.NET MVC の開発に期待したいですね。