1.21 jigowatts

Great Scott!

ASP.NET MVC ファイルアップロード

概要

久しぶりにASP.NET MVCです。以前いくつかMVC2についてポストしましたが、いい加減Razor使いたい!ということでASP.NET MVC5にしてみました。まったく仕事でMVC5を使う予定はありませんがね!

それはさておき、ファイルのアップロードです。最初に完成イメージ。
f:id:sh_yoshida:20160607211237p:plain

環境

Visual Studio Community 2015 Update 2
ASP.NET MVC5

実装

アップロードする画面と結果を表示する画面に対応したコントローラー、モデル、ビューで構成されています。

コントローラー

ファイルをアップロード後にファイル一覧を表示する画面にリダイレクトしています。
Controllers\HomeController.cs

[HttpGet]
public ActionResult Upload()
{
    return View();
}

[HttpPost]
public ActionResult Upload(UserData inputModel)
{
    var destinationFolder = Server.MapPath("~/Users");

    if (!Directory.Exists(destinationFolder))
    {
        Directory.CreateDirectory(destinationFolder);
    }

    var postedFile = inputModel.Picture;
    if (postedFile.ContentLength > 0)
    {
        var fileName = Path.GetFileName(postedFile.FileName);
        var path = Path.Combine(destinationFolder, fileName);

        postedFile.SaveAs(path);
    }

    return RedirectToAction("FileIndex", "Home");
}

[HttpGet]
public ActionResult FileIndex()
{
    var destinationFolder = Server.MapPath("~/Users");
    string[] files = Directory.GetFiles(destinationFolder, "*", SearchOption.AllDirectories);
    ViewBag.files = files;

    return View();
}

モデル

アップロードファイルのモデルバインディングに対応するにはHttpPostedFileBase型として宣言する必要があります。
Models\UserData.cs

public class UserData
{
    public string Name { get; set; }
    public string Email { get; set; }
    public HttpPostedFileBase Picture { get; set; }
}

ビュー

(念願のRazorです!)
ファイルアップロードするにはformタグにenctype = "multipart/form-data"が必要です。
Views\Home\Upload.cshtml

@using (Html.BeginForm("Upload", "Home", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
    @Html.AntiForgeryToken()

    <div class="form-horizontal">
        <h4>UserData</h4>
        <hr />
        @Html.ValidationSummary(true, "", new { @class = "text-danger" })
        <div class="form-group">
            @Html.LabelFor(model => model.Name, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Name, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Name, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.Email, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Email, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Email, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.Picture, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                <input type="file" id="picture" name="picture" class="form-control" />
            </div>
        </div>

        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Create" class="btn btn-default" />
            </div>
        </div>
    </div>
}

結果はViewBagに入れて表示するだけ。
Views\Home\FileIndex.cshtml

@foreach (var file in ViewBag.files)
{
    <div>@file</div>
}

実行結果

Pictureを選択して[Create]ボタンを押下すると…

f:id:sh_yoshida:20160607211237p:plain

保存されました!これはAzure App ServiceのWebサイトにアップした結果です。こんなパスになるんですね。

f:id:sh_yoshida:20160607231524p:plain

Kuduでも確認してみました(初めて使ってみる)。ちゃんと保存されているようです。

f:id:sh_yoshida:20160607231532p:plain

実際にはセキュリティだとかエラーハンドリングだとかもっとしっかりやらないとダメですよ!(‘А`)

Server.MapPathについて

参考書には以下のように指定されてまして、この通りでもローカルで実行した場合はうまく動きました。

Server.MapPath("/Users")

ただ、IISにデプロイするときにアプリケーション名(このサンプルではvoyager02)を指定するとパスが通らなくなってしまいました。
f:id:sh_yoshida:20160607235039p:plain

これは/(スラッシュ)を指定するとWebサイト(ドメインのルート)の物理パスを取得するため一個上の階層になってるんですね。また、ドメインのルートとアプリケーションのルートは必ずしも階層関係にある必要はないため余計にややこしいですね。

f:id:sh_yoshida:20160607235053p:plain

Webサイト直下に配置するのであればスラッシュでもいいのかもしれませんが、ルート演算子(~)をつけてあげるとアプリケーションルートの物理パスが取れるので、アプリケーション名に依存しなくなります。

Server.MapPath("~/Users")

参考

stackoverflow.com

プログラミングASP.NET MVC 第3版 ASP.NET MVC 5対応版

プログラミングASP.NET MVC 第3版 ASP.NET MVC 5対応版