asp.net MVCとjQueryでファイルアップロードするメモ

asp.net MVCとjqueryで非同期にファイルをアップロードしようと思います。

仕様上、フォームや入力の数が多いので、何か便宜を図れないか?ということになり、

要はスマホで片手間にちょこちょこ編集できるようにということなので、

先に空のデータを一時保存しておいて、それに紐つく写真(画像)はその都度登録するようにしました。

非同期通信は他の言語(phpとか)なら特に問題なくやっていました。

結論からいうと同じような実装イメージでやれます。ただし、いろいろ失敗や勘違いもチラホラ・・・

コントローラ側に注目しつつメモ。

処理のイメージ

部分的な処理のイメージはこうなります。

  1. 画像アップロードフォームを用意
  2. 画像選択(change) イベントで自動的にアップロード
  3. MVCコントローラでファイルの処理及びDB登録
  4. 結果を返却して画面処理する

4の画面処理は、ボタン位置にサムネール表示などを考えてます。

2の処理中のローディング画像(グルグルまわるやつ)なども。

ビュー側

ビュー側のフォームです。※Razorです。

スタイルはBootstrapを使用しています。

本来は、同様のファイル登録フォームが動的に複数出力される想定です。

・・・
@* フォームの一部分です *@
@if (row.form_type != 1)
{
  <input id="upload" type="file" name="upload" style="display:none" />
  <button id="upbtn" type="button" class="btn-block" onclick="$('#upload').click();">写真UP</button>
}

・レイアウト上「inpput/file」は非表示にして、別途ボタンでファイルを選択するようにする。

JavaScript

JavaScript側の実装です。

フォームに対してイベント処理を指定します。

$(function () {
    "use strict";
  // 動的に複数行ある想定
    $("input[id^=upload]").each(function (idx, elem) {
    // 送信先URL
        var base_url = "/Input/UpdatePhotos/";
        // ①ファイル選択で発生
        $(this).on("change", function () {
            var file_path = $(this).val();
            // 対象ボタンを一時的に使えないようにする処理
            $("#upbtn_" + ident_cd).prop("disabled", true);
            // フォームインスタンスを作成
            var fd = new FormData();
            if (file_path != "") {
                // ②ココの指定がややこしかった。
                fd.append($(this).attr("name"), $(this)[0].files[0]);
            }
            $.ajax({
                url: base_url,
                type: "POST",
                dataType: "text",
                processData: false,
                contentType: false,
                data: fd,
                success: function (res) {
                    alert("写真を保存しました。");
                    // サムネイルを切り替える処理など
                },
                error: function (res) {
                    alert("写真登録に失敗しました。");
                    // 対象ボタンを使えるようにする
                    $("#upbtn").prop("disabled", false);
                }
            });
        });
    });
});

①複数行ある想定なのでループで回しながらイベント処理定義します。→ファイル選択時

②ここの指定が最初うまくいってなくて、コントローラ側が原因か?とか悩んだ挙句、フォームインスタンスが作成されていなかった!ということでした。

$(this)[0].files[0]

 ↑これ、配列で指定とはなかなかわからなかったです。よく考えればわかりますよ「files」ですからねえ。。。

成功時、の処理は「success」に指定します。

コントローラ

JavaScript側から通信されるコントローラ側です。

※基本的に記載は詳細な処理は省いています。

[HttpPost]
public ActionResult UpdatePhotos(string id)
{
    try
    {
        // ①ファイル取得失敗
        if (this.Request.Files.Count == 0 || id == null)
        {
            throw new HttpException(500, "ファイル取得を失敗しました。");
        }
        // ②送信ファイルを取得
        HttpPostedFileBase photo = this.Request.Files[0];
        // 拡張子チェック
        var ext = Path.GetExtension(photo.FileName).ToLower();
        if (ext != ".jpg" && ext != ".png")
        {
            throw new HttpException(500, "不正なファイルです。");
        }
        // 保存パス
        string savedir = @"\photo\";
        string File_name = photo.FileName;
        // 保存する
        photo.SaveAs(savedir + File_name);
        // DB処理。画像を登録する
        using (var con = new SysEntities())
        {
      ・・・
            // ③DB登録処理
        con.SaveChanges();
      // con.SaveChangesAsync();
        }
    }
    catch (Exception ex)
    {
        // 失敗
        throw new HttpException(500, ex.Message);
    }
    // 成功
    return Content("Success", "text/plain");
}

おおまかにDB処理周りなどははしょりましたが、要点は、、、

①最初はここで失敗していました。Javascriptでインスタンスがnullだったので、

送信ファイルも無い、ということでしたがね。。。

②送信ファイルを取得。引数で、「HttpPostedFileWrapper filename」でいけるかな~とか思っていましたがダメでした。JSでデータ渡すときに配列にしてもダメでした。POSTデータをRequestプロパティから取得するのですがコイツがなかなかはっきりしない。ソレはまた後日。

③始めは、JSからの非同期通信なんだからコッチもasync-awaitで非同期に処理じゃい!とか思って、「SaveChangesAsync()」でDB登録していましたがそれが失敗。現象としては、画像ファイル保存は成功→DB登録はせず(遅延?)ということが頻発。結局普通に実装しました。なんでもやってみようという意気込みだけですねえ。。。

JavaScript側でエラー判定させようと、例外をスローしています。

当初は、async-awaitで実装していて、不具合発生時補足できないので強引に例外発生させてムキになっていました。その名残です。

他のやり方などでレスポンスコードで判別できるかな?次の課題です。

まとめ

動いてみればphpとかと大差ないです。

asp.net MVCはActionResultを返却しないといけないので、その選別と使い方がイマイチ

わかっていないかも、ですな。