質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

87.34%

<input type="file" multiple />で前回選択したファイルを保持しつつ複数回ファイル選択ができるようにしたい

受付中

回答 2

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 3,382

score 13

<input  type="file" multiple/>でファイルを選択後、再度ファイルを選択すると前回選択したファイルの情報が消えてしまいます。
複数のフォルダからファイルを選択できるように選択済みファイルが追加されていく形にしたいのですが上手く行きません。

教えていただけると大変助かります。

開発環境はVisual Studio2019
フレームワークはASP.NET MVC5
プラグインでjQuery v3.3.1が入っています。

以下のようにコードを書いたのですが、Controllerでfilesを受け取っても、直前に選択されたファイルの情報しか受け取れませんでした。

@using (Html.BeginForm( null, "Upload", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
    <input id="files" name="files" type="file" style="display: none;" onchange="checkFile();" multiple />
    <button type="button" class="btn btn-primary" onclick="$('#files').click();">
        <i class="glyphicon glyphicon-file"></i> ファイル選択
    </button>

  <button id="btnUpload" type="submit" class="btn btn-primary">
       <i class="glyphicon glyphicon-cloud-upload"></i> ファイルアップロード
  </button>
}

@section scripts {
    <script>
        var selectedFiles = new Array(0); // これまでに選択された全ファイルを含む配列

        function checkFile() {
           var files = document.getElementById("files").files; // 選択されたファイル

           for (i = 0; i < files.length ; i++) {
                selectedFiles.push(files[i]);
            }

            document.getElementById("files").files = selectedFiles;
        }
    </script>
}
//受け取り側のコントロール
[HttpPost]
public ActionResult FileUpload(HttpPostedFileBase[] files)
{
~~~
}
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • j_t

    2019/09/03 14:28

    質問欄を編集して不足情報を付け加えました。

    キャンセル

  • SurferOnWww

    2019/09/03 16:01

    タグの方もお願いします。ASP.NET, ASP.NET MVC Framework というタグがあります。

    キャンセル

  • SurferOnWww

    2019/09/03 16:04 編集

    自分の環境で MVC5 アプリで検証してみましたが、フォルダが異なる複数のファイルを一度にアップするのは普通のやり方ではどうしてもダメで、nt4c さんがレスに書かれたようにブラウザの HTML5 File API と FormData を利用するのがよさそうです。

    後で、検証に使ったコードその他を整理して回答欄に書いておきます。

    キャンセル

回答 2

+2

input[type=file]要素の持つfilesは配列ではなくFileListですのでdocument.getElementById("files").files = selectedFiles;としてもファイルをセットすることはできません。

ですので、配列にfileを格納するのではなくFileListごと格納しFormDataを使って送信する方法はどうでしょうか。

const selectedFiles = []
$('#input').on('change', function(event) {
  selectedFiles.push(event.target.files)
})

function submit() {
  const formData = new FormData();
  selectedFiles.forEach(function(element) {
    formData.append("data[]", element);
  });
  $.ajax({
    url: '/api',
    method: 'post',
    dataType: 'json',
    data: formData,
    processData: false,
    contentType: false
  }).done(function() {
  }).fail(function() {
  });
}

もし選択されているファイルをユーザー側で削除させたいなどの機能を作る場合はwindow.URL.createObjectURL()の利用を検討されてみてもよいかもしれません。
ページを開いている間のみ有効な一時的なBlobURLを生成しますので、選択ファイルを個別に操作させたい場合は有効です。

var selectedFiles = []
$('#input').on('change', function(event) {
  for(const item of event.target.files) {
   selectedFiles.push(URL.createObjectURL(item))
  }
})

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/09/04 08:40

    丁寧なご回答ありがとうございます。
    参考にさせていただきます。

    キャンセル

0

自分の環境(Visual Studio Community 2015, .NET 4.6.1, MVC5)で検証してみました。

フォルダが異なる複数のファイルを一度にアップするのは普通のやり方(form を submit する方法。下のサンプルコードで言うと[Upload by Submit]ボタンをクリック)ではどうしてもダメでした。

nt4c さんがレスに書かれたようにブラウザの HTML5 File API と FormData を利用し Ajax で送信するのがよさそうです。

以下に検証に使ったコードを書いておきます。説明はコメントにありますのでそれを見てください。手抜きでスミマセン。不明点があれば質問してください。

Model

public class MultipleUploadModels
{
    public string CustomField { get; set; }
    public IList<HttpPostedFileBase> PostedFiles { get; set; }
}

View

@model Mvc5App.Controllers.MultipleUploadModels

@{
    ViewBag.Title = "MultipleUpload";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<h2>MultipleUpload</h2>

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

    // name 属性はモデルのクラスのプロパティ名と同じにしない
    // とサーバー側でモデルバインディングされないので注意。
    // 大文字小文字は区別しない。
    <input id="mutiplefileupload" type="file" name="postedfiles" multiple="multiple" />
    <button type="submit">Upload by Submit</button>
    <br />
    @Html.Raw(ViewBag.Result)
}

<br />
<input type="button" id="ajaxUpload" value="Ajax Upload" />
<br />

<div id="result"></div>


@section Scripts {
    <script type="text/javascript">
        //<![CDATA[
        // FormData オブジェクトの利用
        // https://developer.mozilla.org/ja/docs/Web/Guide/Using_FormData_Objects

        // ブラウザの HTML5 File API サポートを確認
        if (window.File && window.FileReader && window.FileList) {

            // FormData オブジェクトの利用
            // CSRF 用のトークンを取得するため form から FromData を取得
            var fd = new FormData(document.querySelector("form"));

            // input type=file" でファイルの選択が完了すると change
            // イベントが発生するのでそれにリスナをアタッチし、そこ
            // で FileList オブジェクトを取得して選択した各ファイル
            //(File オブジェクト)を FormData に追加
            var fileUpload = document.getElementById("mutiplefileupload");
            fileUpload.addEventListener('change', function (e) {
                // files プロパティで FileList オブジェクトを取得
                var filelist = fileUpload.files;
                for (let i = 0; i < filelist.length; i++) {
                    // File オブジェクトを FormData に追加
                    // 名前 "postedfiles" はモデルのプロパティ名と同じにする
                    fd.append("postedfiles", filelist[i]);
                }
            });

            // [Ajax Upload] ボタンクリックの処置
            $('#ajaxUpload').on('click', function (e) {
                $.ajax({
                    url: '/home/multipleupload',
                    method: 'post',
                    data: fd,
                    processData: false, // jQuery にデータを処理させない
                    contentType: false  // contentType を設定させない
                }).done(function (response) {
                    $("#result").empty;
                    $("#result").html(response);
                }).fail(function (jqXHR, textStatus, errorThrown) {
                    $("#result").empty;
                    $("#result").text('textStatus: ' + textStatus +
                                    ', errorThrown: ' + errorThrown);
                });
            });
        } else {
            $("#result").empty;
            $("#result").text('File API がサポートされてません。');
        }
        //]]>
    </script>
}

Controller / Action Method

public ActionResult MultipleUpload()
{
    return View();
}

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult MultipleUpload(MultipleUploadModels model)
{
    string result = "";
    IList<HttpPostedFileBase> postedFiles = model.PostedFiles;

    if (postedFiles != null && postedFiles.Count > 0)
    {
        foreach (HttpPostedFileBase postedFile in postedFiles)
        {
            if (postedFile != null && postedFile.ContentLength > 0)
            {
                // アップロードされたファイル名を取得。ブラウザが IE の
                // 場合 postedFile.FileName はクライアント側でのフル
                // パスになることがあるので Path.GetFileName を使う
                string filename = Path.GetFileName(postedFile.FileName);

                // 保存ホルダの物理パス\ファイル名
                string path = Server.MapPath("~/UploadedFiles") + "\\" + filename;

                // アップロードされたファイルを保存
                postedFile.SaveAs(path);

                result += filename + " (" + postedFile.ContentType + ") - " +
                          postedFile.ContentLength.ToString() +
                          " bytes アップロード完了<br />";
            }
        }
    }
    else
    {
        result = "ファイルアップロードに失敗しました";
    }

    if (Request.IsAjaxRequest())
    {
        return Content(result);
    }
    else
    {
        ViewBag.Result = result;
        return View();
    }
}

実行結果は以下の画像のようになります。使ったブラウザは Chrome 76.0.3809.132 です。

ファイルの選択

これより先に Migration フォルダの画像を一つ選択しています。

イメージ説明

ファイルアップロード完了後の応答画面

[Ajax Upload]ボタンをクリックして Ajax でファイルを 3 つ送信し、戻ってきた応答を表示したものです。

イメージ説明

アップロードしたファイル

期待通りサーバー側の指定フォルダに保存されています。

イメージ説明

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/09/04 09:21

    お手数をおかけしてすみません。
    大変ご丁寧な回答ありがとうございます。
    参考にさせていただきます。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 87.34%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る

  • トップ
  • C#に関する質問
  • <input type="file" multiple />で前回選択したファイルを保持しつつ複数回ファイル選択ができるようにしたい