C# と VB.NET の質問掲示板

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

Re[7]: Asp.net Mvc データ登録方法について


(過去ログ 125 を表示中)

[トピック内 11 記事 (1 - 11 表示)]  << 0 >>

■74261 / inTopicNo.1)  Asp.net Mvc データ登録方法について
  
□投稿者/ ainax (11回)-(2014/12/11(Thu) 16:43:23)

分類:[ASP.NET (C#)] 

MVC を使っています。

ビューにて下記モデルのデータ登録を行いたいと思っております。
そこで、 Family については画面遷移なしで登録と削除を行えるようにし、
Employee の登録時に Family も実際にデータベースへと登録されるようにしたいと思っております。

・モデル

public class Employee{
    public int ID { get; set; } // Controller で処理
    public string Name { get; set; } // Create ページで入力
    public List<Family> Families { get; set; } // Create ページで入力
}

public class Family{
    public int ID { get; set; } // Controller で付与?
    public string Name { get; set; } // Create ページで入力
}

・ビューについては次の様なものを想定しています。

<!-- Employee -->
<form 〜〜〜>
   <input type="hidden" name="ID" value="-1" />
   <input type="text" name="Name" value="" />
   <input type="submit" />
</form>

<!-- Family -->
<form 〜〜〜> <!-- Family 追加処理 コントローラの追加関数へと Name を送る -->
   <input type="text" name="Name" value="" />
   <input type="submit" />
</form>

<div id="family"> <!-- Family リスト表示 ID 番号をコントローラの削除関数へ送る -->
<table>
   <tr><th> Name </th><th></th></tr>

   <tr><td> tanaka </td><td> <input type="button" value="del" onclick='delete(1)' /> </td></tr>
   <tr><td> saitoh </td><td> <input type="button" value="del" onclick='delete(2)' /> </td></tr>
   <tr><td> nakata </td><td> <input type="button" value="del" onclick='delete(3)' /> </td></tr>
   <tr><td> suzuki </td><td> <input type="button" value="del" onclick='delete(4)' /> </td></tr>
</table>
</div>

そこでご質問なのですが、仕様を満たす方法として下記の2つ方法を思いついているのですが、
方針としてあっているかどうかを教えて頂けないでしょうか?

1.Ajax+javascript
Ajax にて <div id="family"> の部分更新をする。 
Family の追加・削除はそれぞれコントローラにメソッドを用意し、Family リストを作成して table を更新する。

2.Ajax+TempData
Ajax にて <div id="family"> の部分更新をする。
コントローラの追加処理で TempData に Family を追加し、削除処理では ID を渡し TempData から ID のFamily を削除する。


1.の場合は資料が豊富で恐らく実現可能だと思うのですが、 javascript に不慣れなのと Family クラスが拡張した際に
javascript の書き換えが面倒かなと思っています。

2.の場合は Family クラスが拡張しても最小限のビューの書き換えで済みそうな気がします。ただし、 TempData を利用した
資料があまりない(?)気がしています。

可能であれば、2.の方針で行きたいのですが、何か問題があるのでしょうか?

アドバイスを頂けると幸いです。

引用返信 編集キー/
■74262 / inTopicNo.2)  Re[1]: Asp.net Mvc データ登録方法について
□投稿者/ WebSurfer (426回)-(2014/12/11(Thu) 18:02:24)
No74261 (ainax さん) に返信

質問する際は、一番最初に、ご自分の環境(OS, .NET, IIS, MVC, EF, Visual Studio の
バージョン、MVC のテンプレートは何か、DB サーバーとそのバージョン、ブラウザは何か
などの情報)を書いていただけませんか?

案 1, 2 とも自分には理解できません。(はっきり言って、何か変です)

以下の MSDN フォーラムのスレッドに、親:子 = 1:n(n はユーザーが指定)のデータを、
ユーザーが入力してポストし、サーバー側で受け取る Model, View, Controller の例があ
りますが、参考になりませんか?

[ASP.NET MVC]親子関係のあるテーブルの同時登録
https://social.msdn.microsoft.com/Forums/ja-JP/645d38d0-5849-4866-a5cc-4027bb97eeea/aspnet-mvc?forum=aspnetja

上記は、データを受け取って検証するまでの例で、DB への登録のコードはありませんが、
ここまでできれば後は問題なく実装できるのでは?

とりあえず、上に紹介した記事を読んでください。その上で、疑問点があれば質問して
いただければと思います。(質問の際にはあなたの環境を書いてくださいね)

引用返信 編集キー/
■74263 / inTopicNo.3)  Re[2]: Asp.net Mvc データ登録方法について
□投稿者/ ainax (12回)-(2014/12/11(Thu) 22:49:19)
No74262 (WebSurfer さん) に返信
お返事頂きありがとうございます。

まずは環境等の情報の不掲載について、申し訳ございません。

Asp.net については自分でも基本ができないない事を自覚しておりまして、
やりたいことの実現についてどのような方法が適切なのかまったく自信がない状態です。

ご提示して頂けました URL 先をまずは読んで見て(まだ読んでいないのですが)その上で
続いて質問をさせて頂ければと思います。

# Asp.net について調べていると WebSurfer さんと同名のブログをされている方が
# いらっしゃる様ですがもしかしてご本人様でしょうか?
# まだ自分では理解できない部分等もありますが、参考にさせて頂いていましたので、
# 重ね重ねありがとうございます。
引用返信 編集キー/
■74336 / inTopicNo.4)  Re[3]: Asp.net Mvc データ登録方法について
□投稿者/ ainax (13回)-(2014/12/18(Thu) 19:41:07)
No74262 (WebSurfer さん) に返信

お返事が遅くなり、申し訳ありません。

やっと紹介して頂いた URL 先を確認し、実現したかった機能について
完成致しました。

まず環境は下記のとおりです。
Windows 7 Pro 64bit, VS Express 2013 for Web, .NET Framework4, ASP.NET MVC 4, インターネットアプリケーションテンプレート使用, 今回はデータベース未使用, EF5, IE11

●やりたいこと
1:nモデルのビューでの登録方法について

@親と子の入力フィールドは1画面で収めたい
A子のレコードは追加、削除をしたい
B親のレコード登録時に子のレコードも追加したい

●モデル
    public class VMParent
    {
        public int ID { get; set; }
        public string Name { get; set; }
        public virtual List<VMChild> Children { get; set; }
    }
    
    public class VMChild
    {
        public int ID { get; set; }
        public string Name { get; set; }
    }

●Create ビュー
@model AspdotNetTest.Model.VMParent

@using (Html.BeginForm()) {
    @Html.AntiForgeryToken()
    @Html.ValidationSummary(true)

    <fieldset>
        <legend>VMParent</legend>

        <div class="editor-label">
            @Html.LabelFor(model => model.Name)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Name)
            @Html.ValidationMessageFor(model => model.Name)
        </div>

        <p>
            <input type="submit" name="cmd" value="Create" />
        </p>
    </fieldset>
}

@* 子の入力フィールドが表示され、部分更新される個所 *@
<div id="AjaxUpdate">
    @Html.Partial("_Children", Model)
</div>

●_Children 部分ビュー
@model AspdotNetTest.Model.VMParent
@{
    ViewBag.Title = "Create";
    TempData["Parent"] = Model;
}
@using (Ajax.BeginForm("editChild", "PartialTest", new AjaxOptions {UpdateTargetId="AjaxUpdate" }))
{
    @Html.AntiForgeryToken()
    @Html.ValidationSummary(true)

    <fieldset>
        <legend>VMParent</legend>

        <div class="editor-label">
            @Html.Label("名前")
        </div>
        <div class="editor-field">
            @Html.Editor("AddName")
            @Html.ValidationMessage("AddName")
            <input type="submit" name="cmd" value="Add" />
        </div>
        

        @foreach (var child in Model.Children.Select((item, index) => new { item, index }))
        {
            @Html.Label(child.item.Name)    
            <input type="submit" name="cmd" value="Delete" onclick="setDeleteIndex(@child.index)" />
        }

        @Html.Hidden("DeleteIndex")

<script language="JavaScript">
    function setDeleteIndex(id) {
        $("input[id='DeleteIndex']").val(id);
    }
</script>

    </fieldset>
}

●コントローラ
public ActionResult Create(VMParent parent = null)
        {
            if (parent.Children == null)
            {
                // テストデータ挿入(通常はインスタンス生成のみ)
                parent = new VMParent()
                {
                    Name = "山田太郎",
                    Children = new List<VMChild>()
                    {
                        new VMChild() { ID = 1, Name="一郎" },
                        new VMChild() { ID = 2, Name="次郎" },
                    }
                };
            }

            return View(parent);
        }

        [HttpPost]
        public ActionResult Create(FormCollection collection, string Name)
        {
            try
            {
                // TempData から VMParent モデルを持ってくる
                var parent = (AspdotNetTest.Model.VMParent)TempData["Parent"];

                parent.Name = Name;

                // DB にデータを記録する処理はここに入れる。

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

        public ActionResult editChild(string cmd, string AddName, string DeleteIndex)
        {
            ModelState.Clear(); // これを入れないと事前に入っている文字が消えない

            // TempData から VMParent モデルを持ってくる
            var parent = (AspdotNetTest.Model.VMParent)TempData["Parent"];

            if (cmd == "Add")
            {
                // 追加ボタンが押されたときは、AddName を追加
                parent.Children.Add(new VMChild() { Name = AddName });
            }
            else if (cmd == "Delete")
            {
                // 削除ボタンが押されたときは、指定のインデックスを削除
                parent.Children.RemoveAt(Convert.ToInt32(DeleteIndex));
            }

            return PartialView("_Children", parent);
        }
以上のような形になりました。


現在 VMParent モデルのビューとコントローラのやり取りを、 TempData を使って
いるのですが、この処理の仕方で問題ないのでしょうか?

通常は BeginForm 内でモデルのフィールドを使って Submit し、
コントローラのメソッドでバインディングが行われる(?)と思うのですが、
うまく取得できなかったため、このような仕様になりました。
(その他にも、 AntiforgeryToken や ValidationSummary の動きもよくわかっていないのですが・・・)

「これで問題ない」や「普通はこうやるべき」というようなご助言がありましたら、
ご教示頂ければ幸いです。

よろしくお願い致します。

引用返信 編集キー/
■74339 / inTopicNo.5)  Re[4]: Asp.net Mvc データ登録方法について
□投稿者/ WebSurfer (435回)-(2014/12/19(Fri) 00:10:06)
No74336 (ainax さん) に返信

その実装では、モデルバインディングとデータアノテーション検証(クライアントサイド
を含む)がうまくいかないように見えますが、いかがですか?

今回のようなコレクションのモデルバインディングがうまく行われるようにするには、以
下のページに書いてあるように、レンダリングされる html 要素の name 属性の設定が問
題になるのですが、そのあたりは考えられているでしょうか?

コレクションのデータアノテーション検証
http://surferonwww.info/BlogEngine/post/2014/09/01/validation-of-collection-data-during-model-binding-using-data-annotation.aspx

モデルバインディングがうまくいかなければ、そのあとの DB への登録もうまくいくはず
はないです。

なので、まずはそこのところが問題なく実装できているか確認してください。

コードは詳しく見てないので、上に言ったことが間違っていたらすみません。でも、そこ
の基本的なことができてないコードを詳しく見る気力が湧かないと言うこともご理解くだ
さい。
引用返信 編集キー/
■74340 / inTopicNo.6)  Re[5]: Asp.net Mvc データ登録方法について
□投稿者/ ainax (14回)-(2014/12/19(Fri) 01:47:28)
No74339 (WebSurfer さん) に返信

夜遅くにも関わらずお返事いただき誠にありがとうございます。

> その実装では、モデルバインディングとデータアノテーション検証(クライアントサイド
> を含む)がうまくいかないように見えますが、いかがですか?
たしかにモデルのバインディングはきちんと出来ていません。
コントローラで VMParent を引数にとり中身を見てみると VMChild が取得できていません。
その回避策として、 VMChild は 部分ビューの中で TempData["Children"] = Model として代入し、
コントローラで TempData から Model を引っ張ってきています。

> 今回のようなコレクションのモデルバインディングがうまく行われるようにするには、以
> 下のページに書いてあるように、レンダリングされる html 要素の name 属性の設定が問
> 題になるのですが、そのあたりは考えられているでしょうか?
>
name 属性の設定については先にご紹介いただいた URL 先にて次のようにすればバインディングされると考えています。
VMParent の Name プロパティ: <input name="Name" 〜〜>
VMParent.List<VMChild> 内のの Name プロパティ: <input name="Children[0].Name 〜〜>

現在は、HTML.BeginForm で VMParent の Name 登録を行っており、
VMChild は Ajax.BeginForm で VMChild.Name を動的に追加削除した結果をフォームの更新に使っております。

HTML.BeginForm 内に VMChild を設定できずそのため、 HTML.BeginForm で呼び出される
Create メソッドにて parent.Children が取得できませんでした。

一応 HTML.BeginForm 内に Ajax.BeginForm を置いてみたのですが、 Form 内の Form は正しく認識されない様で
失敗に終わってしまいました。

(と、ここでいま思いついたのですが、Ajax.BeginForm で VMParent も VMChild も更新してしまうのが正解でしょうか?
明日検証してみたいと思います。)

> コレクションのデータアノテーション検証
> http://surferonwww.info/BlogEngine/post/2014/09/01/validation-of-collection-data-during-model-binding-using-data-annotation.aspx
申し訳ありませんが、ご紹介頂いた URL 先についても明日拝見させて頂ければと思います。

> モデルバインディングがうまくいかなければ、そのあとの DB への登録もうまくいくはず
> はないです。
一応 Create メソッド内でデバッグ中に変数の中をのぞいてみたところ、
VMParent も VMChild も必要なデータを取得していたことを確認致しました。

ただ、きちんとモデルバインディングが可能であれば、やはりそれを実現したいと思います。

取り急ぎ、現状のご報告をさせて頂きましたが、
明日にでもご紹介頂いた URL を拝見後にきちんとご報告させて頂ければと思います。
引用返信 編集キー/
■74361 / inTopicNo.7)  Re[6]: Asp.net Mvc データ登録方法について
□投稿者/ WebSurfer (436回)-(2014/12/19(Fri) 16:56:14)
No74340 (ainax さん) に返信

> たしかにモデルのバインディングはきちんと出来ていません。
> コントローラで VMParent を引数にとり中身を見てみると VMChild が取得できていません。
> その回避策として、 VMChild は 部分ビューの中で TempData["Children"] = Model として代入し、
> コントローラで TempData から Model を引っ張ってきています。

そのような回避策を取る必要はないと思うのですが・・・

デルバインディングをきちんと行うためには、name 属性を、

name="prefix[index].Property"

というパターンすることと、EditorFor のような Html ヘルパーを使うことです。

それを詳しく書いたのが先に紹介した記事「コレクションのデータアノテーション
検証」ですので、そのポイントを抑えてざっと読んでみてください。

で、一番最初に紹介した以下の記事では、上記の 2 つの点を考慮してモデルバイ
ンディングがきちんと行われるようにしてます。

[ASP.NET MVC]親子関係のあるテーブルの同時登録
https://social.msdn.microsoft.com/Forums/ja-JP/645d38d0-5849-4866-a5cc-4027bb97eeea/aspnet-mvc?forum=aspnetja

2014年8月31日 7:19 のレスのコードをよく見てください。
引用返信 編集キー/
■74362 / inTopicNo.8)  Re[4]: Asp.net Mvc データ登録方法について
□投稿者/ WebSurfer (437回)-(2014/12/19(Fri) 17:10:22)
No74336 (ainax さん) に返信

> 今回はデータベース未使用, EF5, IE11

それから、「今回はデータベース未使用」とありましたが、今後 SQL Server + Entity
Framework を使う予定があるなら、最初からその部分を実装することをお勧めします。

たぶん親・子で別々のテーブルになり外部キー制約をかけると思いますが、その場合
階層更新が必要などいろいろ面倒なことがあると思います。

今の問題が解決しても、そこのところで別の問題が出て、後戻りせざるを得なくなるよ
うなことがあると困りますよね。
引用返信 編集キー/
■74398 / inTopicNo.9)  Re[5]: Asp.net Mvc データ登録方法について
□投稿者/ ainax (15回)-(2014/12/23(Tue) 09:35:49)
No74362 (WebSurfer さん) に返信

WebSurfer さんお返事が遅くなり申し訳ありませんでした。

WebSurfer さんにご教示頂いた助言や資料のおかげで、ほぼ思っていた通りの動作をさせることが出来ました。

ソースについては No74361 で示して頂いたコードを基本にして、
以下のようになりました。

●コントローラ
        [HttpPost]
        public ActionResult Create(VMParent parent, string cmd, string arg)
        {
            if(cmd == "Add")
            {
                //var c = parent.Children.Last();

                if(string.IsNullOrEmpty(parent.Children.Last().Name))
                {
                    ModelState.AddModelError(string.Format("Children[{0}].Name",parent.Children.Count - 1), "名前が入力されていません。");
                    return View(parent);
                }

                parent.Children.Add(new VMChild());
            }
            else if(cmd == "Delete")
            {
                ModelState.Clear();
                parent.Children.RemoveAt(int.Parse(arg));
            }
            else if(cmd == "Create")
            {
                // データベースに登録する処理

                return RedirectToAction("Create");
            }

            return View(parent);
        }

●ビュー(子エンティティ部のみ)
        <div class="editor-label">
            @Html.Label("Add Child Name")
        </div>

        @{ var count = Model.Children.Count(); }

        <div class="editor-field">
            @Html.EditorFor(m => m.Children[count - 1].Name, new { name = string.Format("Parent.Children[{0}].Name", count -1)})
            @Html.ValidationMessageFor(model => model.Children[count-1].Name)
            <input type="submit" name="cmd" value="Add" />
        </div>
        
        <div class="editor-label">
            @Html.Label("Added Child Name")
        </div>

        @for (int i = 0; i < count-1; i++)
        {
            <div class="editor-field">
                @Html.EditorFor(m => m.Children[i].Name, new { name = string.Format("Prent.Children[{0}].Name", i) })
                @* 削除クリック時に削除対象のインデックスを #arg に格納 *@
                <input type="submit" name="cmd" value="Delete" onclick="$('#arg').val(@i.ToString());" />
            </div>   
        }
        @* 削除する子エンティティのインデックス格納用 *@
        <input type="hidden" id="arg" name="arg" />

        <p>
            <input type="submit" name="cmd" value="Create" />
        </p>

子エンティティの追加・削除と親エンティティの作成 input タグを name="cmd" で統一し、
コントローラで Value 値を元にどの処理かを振り分けています。

子エンティティの削除のみ、リストの中の削除項目を特定するため、
削除ボタン押下時に $('#arg').val(@i.ToString()); でインデックスを <input type="hidden" id="arg" name="arg" /> 経由で
コントローラに渡しています。

サーバーサイドでのモデル検証もうまく動作しているのを確認できました。

あとは、追加済みの子エンティティの表示で disabled="disabled" 属性を付けたいのですが、
これを付けるとコントローラに値が渡せないので、その処理だけ改良したいと思っています。

@Html.HiddenFor と <input disabled="disabled" 〜> を組みにしてやればいいのではと思っております。
(HtmlHelper を作ってやれば多少楽に記述できるのかな?)

最後に、今回はこのままデータベース未使用で来てしまいましたが、
この後はデータベースに追加するところまできちんと定義し、
ご指摘を受けた問題も確認していきたいと思います。

長々とお相手して頂き、誠にありがとうございます。
一応これで解決済みとさせて頂きたいと思います。

解決済み
引用返信 編集キー/
■74399 / inTopicNo.10)  Re[6]: Asp.net Mvc データ登録方法について
□投稿者/ WebSurfer (442回)-(2014/12/23(Tue) 12:19:18)
No74398 (ainax さん) に返信

解決済みとのことですので今さらながらのレスですが・・・

> サーバーサイドでのモデル検証もうまく動作しているのを確認できました。

それは Create アクションメソッドの中の以下のコードのことを言ってますか?

> if(string.IsNullOrEmpty(parent.Children.Last().Name))

何にせよ、本来、上記のようなコードは不要なんですが・・・

モデルにデーターアノテーション検証用の属性を追加すれば(具体例は先に紹介したコード
を参照)、モデルバインディングの際に検証が行われ、検証結果が ModelStateDictionary
(Controller.ModelState プロパティで取得できます)に格納されます。

なので、Create アクションメソッドでは ModelState.IsValid で判定し、false ならリダ
イレクトせず return View(parent); すればエラーメッセージが表示され、ユーザーに再入
力が促されるようになるはずです。

その前に、クライアント側での検証がうまく動いていれば、クライアント側で jQuery ライ
ブラリにより検証が行われ、検証結果 NG の場合はエラーメッセージが表示され、submit
はキャンセルされます。(Create アクションメソッドには制御は飛びません)

そのあたりも基本ですので、きちんと実装されているか確認してください。

#クライアント側での検証が動いていると、1 : n の n を動的に変化させる場合に不都合が
 あるかもしれませんが、それの解決は次のステップの課題として、まずは基本を押さえる
 ことをお勧めします。

> 最後に、今回はこのままデータベース未使用で来てしまいましたが、
> この後はデータベースに追加するところまできちんと定義し、

もし、MVC では最近流行(?)の Entity Framework Code First の機能を利用する予定なら
特に、そうでなくても DB のスキーマを視野に入れて、できれば今すぐ DB との連携を含め
て開発することをお勧めします。

解決済み
引用返信 編集キー/
■74401 / inTopicNo.11)  Re[7]: Asp.net Mvc データ登録方法について
□投稿者/ ainax (16回)-(2014/12/23(Tue) 13:45:35)
No74399 (WebSurfer さん) に返信

さらにお返事を頂きありがとうございます。

> それは Create アクションメソッドの中の以下のコードのことを言ってますか?

まさに、そのコード部分を考えていました。
が、本来は検証用属性を利用し IsValid で判定することを基本とするのですね。

最初からチェックはアクション内でしようと考えておりまして、
それをモデル検証と考えてしまいました。

> その前に、クライアント側での検証がうまく動いていれば、クライアント側で jQuery ライ
> ブラリにより検証が行われ、検証結果 NG の場合はエラーメッセージが表示され、submit
> はキャンセルされます。(Create アクションメソッドには制御は飛びません)

クライアント側での検証は難しそうとのことで、今回諦めてしまいました。
ただ、

> そのあたりも基本ですので、きちんと実装されているか確認してください。

現在は実装しておりませんので、実装してテストしてみたいと思います。

> もし、MVC では最近流行(?)の Entity Framework Code First の機能を利用する予定なら
> 特に、そうでなくても DB のスキーマを視野に入れて、できれば今すぐ DB との連携を含め
> て開発することをお勧めします。

コードファーストを利用していこうと思っております。
本番環境ではおっしゃる通り DB との連携を元に組んでいきたいと思います。

ご助言ありがとうございます。

解決済み
引用返信 編集キー/


トピック内ページ移動 / << 0 >>

このトピックに書きこむ

過去ログには書き込み不可

管理者用

- Child Tree -