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

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

ただいまの
回答率

88.61%

スキャフォールディングで生成された値の表示形式を変更したい

解決済

回答 2

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 1,274

pomu.pomupomu

score 18

 前提・実現したいこと

ASP.NET MVC5 で掲示板を作成している初学者です。
スレッドでレスを投稿をする際にASP.NET Identityで獲得したユーザーIdやレスを返そうとしているスレッドのIdを初期値として持ったままレスをできるようにしたいと考えましたが、うまくいかないので投稿しました。

(コード添付に当たって、全体を貼った後に自分で書き直したところ等をまとめています)

 発生している問題・エラーメッセージ

スキャフォールディングしたままの状態では、次のような見栄えになっています。
イメージ説明
この状態では、誰がレスを書き込むのかを示すResOwneridがデータベース中にある全件がドロップダウンリストで表示されてしまいます。そこで、ResOwnerIdをTextBox等に変更して初期値を現在ログインしているユーザーIdにすることはできないか、と思い投稿しました。

以前、同じような質問をしたのですが、TextBoxに値は入ったもののそのあとsubmitしてもうまくDBに値が反映されなかったため再度投稿しました。
(ちなみに問題解決時に、TextBoxのname属性に問題があったためにバインディングがうまくいかなかったのだろうということが推定できました)
https://teratail.com/questions/135216

現在、上記Urlの教えを取り込み、コードを書きなおしたところ、次のようになりました。
添付画像中のテキストボックスに入っている6aa・・・の文字列はResOwnerIdのEditor欄に初期値として入って欲しいASP.NET Identityで設定したログイン中のUserIdです。
(この時点では、Textboxではモデルを経由してデータベースに書き込むすべはないのであろうと考えていたため、EditorForにこだわっていました。)
イメージ説明

 該当のソースコード と 試したこと

ControllerとViewの該当部分を記します。(Modelはどこをはるかわからなかったので指摘あれば貼ります)

Controller(2行目の"int? Id"のIdには、スレタイをクリックした際に渡される、スレッドのIdが含まれる予定です。)

        // GET: Responses/Create
        public ActionResult Create(int? Id)
        {
            var UserId = User.Identity.GetUserId();
            ViewBag.ResOwnerId = UserId;
            // ViewBag.ResOwnerId = new SelectList(db.AspNetUsers, "Id", "Email");
            ViewBag.TopicId = new SelectList(db.Topics, "Id", "Title");
            return View();
        }

View(SortNoは見やすさと文字数の関係で省略してます)

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

<div class="form-horizontal">
    <h4>Responses</h4>
    <hr />
    @Html.ValidationSummary(true, "", new { @class = "text-danger" })

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


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

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


    <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>
}

以上のうち、Controllerは次のように記すことによって、ResOwnerIdに現在ログイン中のユーザーIdを格納しました。(ViewのTextBoxでユーザーIdを表示できました。)

            var UserId = User.Identity.GetUserId();
            ViewBag.ResOwnerId = UserId;

また、ViewはResOwneridについて、

            @Html.DropDownList("ResOwnerId", null, htmlAttributes: new { @class = "form-control" })


を次のように修正しました。その結果、EditorForは空欄でvalueとしてResOwnerIdが格納されませんでした。(TextBoxにResOwnerIdは表示されたので、ViewBag.ResOwnerIdに値はControllerから渡されていると考えています。)

       @Html.EditorFor(model => model.ResOwnerId, new { htmlAttributes = new { @class = "form-control", value = ViewBag.ResOwnerId } })

 補足情報(FW/ツールのバージョンなど)

VS2017   
Windows7   
.Net Framework 4.6.1
MVC5

問題解決がEditorForではなく、TextBoxでも行うことができたため、質問の内容を修正しました。
TextBoxではなくEditorForでも行えるとは思うので、出来次第再度加筆しようとおもいます。

  • 気になる質問をクリップする

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

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

  • pomu.pomupomu

    2018/08/23 15:00

    ざっくりですがここをみてそう思いました。 http://zecl.hatenablog.com/entry/20120227/p1

    キャンセル

  • pomu.pomupomu

    2018/08/23 15:04

    もし、そうであれば、Viewのテキストボックスで記したResOwnerIdがADO.NETで作成したModelの.edmx拡張子のファイル(最後の追記した画像)ResponsesテーブルのResOwnerIdと名前が同じなのでマッチングし、データベースのResponsesテーブルのResOwnerIdとマッチしてるため、name属性はうまくいってるのではと考えています。

    キャンセル

  • SurferOnWww

    2018/08/23 17:24

    コメント欄には書ききれないので回答欄に書きます。

    キャンセル

回答 2

checkベストアンサー

+1

(1) DropDownList になってしまう理由

質問にアップされた Entity Data Model の画像を見ての想像ですが(想像と言っても自信度 90% ぐらいはあります)・・・

スキャフォールディング機能で View を自動生成する際に使ったモデル(クラス)は画像の中の Responses ですよね。その中の ResOwnerId から AspNetUsers の Id に外部キーが張られているからです。

そうなっていると、スキャフォールディングで自動的に、アクションメソッドには、前のスレッドに書いてあった、

ViewBag.ResOwnerId = new SelectList(db.AspNetUsers, "Id", "UserName");


というコードが生成され(なぜ自動的に UserName が選ばれるのかは謎ですが)、View には、

@Html.DropDownList("ResOwnerId", null, htmlAttributes: new { @class = "form-control" })

というコードが生成され、結果 DropDownList になるという仕組みになっているようです。

ResOwnerId から AspNetUsers の Id に外部キーを張るのを止めればそのようなことにはならないはずです。と言うか、外部キーなど張るべきではない、張らないで済むように考えるべきと思うのですが。

そもそも、ASP.NET Identity が EF Code First で作る各テーブルと、質問者さんが自分で作ったと思われる Responses 他のテーブルを一緒にして EDM を作るというのは、自分に言わせてもらえれば普通ではないと思います。ましてや外部キーを張るなんて・・・ 考え直した方がよさそうです。

(2) モデルバインディングが上手くいかない理由

最終的に以下のようにしてうまくいったということですが、うまくいかなかったときは "ResOwnerId" が違っていたのではないかと思います。

@Html.TextBox("ResOwnerId", (string)ViewBag.ResOwnerId, new { @class = "form-control", @readonly = "readonly"  })


上記が ASP.NET によって html ソースに変換されると以下のようになるはずです。

<input ... name="ResOwnerId" type="text" value="6aa17057-..." />

私がコメントで「name 属性が重要ですが、どうなってましたか?」と言ったのは上の name のことです。

ボタンクリックで上記 input 要素からは ResOwnerId=6aa17057-... という形(左辺が name 属性、右辺が value 属性の値)でテキストボックス入力がサーバーに送信されます。

今回のケースでテキストボックスは複数ありますが、全て同様で <name 属性の値>=<value 属性の値> という形でデータがサーバーに送信されます。

アクションメソッドは Create(Responses responses) のようにしていると思いますが、その引数の responses に、送信されてきた複数の <name 属性の値>=<value 属性の値> というデータから Responses のインスタンスを作って渡すのがモデルバインディングです。

Responses クラスには複数のプロパティ(SortNo, TopivId, Contents, ResOwnerId 他)がありますが、送信されてきた複数の <name 属性の値>=<value 属性の値> というデータから、プロパティ名と同名の <name 属性の値> を探して、その <value 属性の値> をプロパティに代入します。

なので、@Html.TextBox("ResOwnerId" ... の "ResOwnerId" が違っていたらバインドできません。それが最初の状態だったと思います。

(3) TextBox を EditoFor に変更する方法

アクションメソッドで return View(); とするのではなく、var model = new Responses(); で Responses クラスのインスタンスを作り、その ResOwnerId プロパティに値を代入してから return View(model); とすれば EditorFor に値が表示されるはずです。

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2018/08/23 17:53

    (1)
    ドロップダウンリストになるのが必然なのはそこのせいかな・・・とふわっと思い返していたため、「ひえっ」って声が出たぐらいその通りだと思います。
    外部キーを張った理由として「スレッドを投稿する際に誰が投稿したのかわかるようにするにはResOwnerIdは、ユーザー名変更を考慮してResOwnerIdとAspNetUsersテーブルの(ユーザー)Idが常にマッチするものであるべきで、それなら外部キーでくっつけたらいいな?」と考えていました。
    が、しかし、Controller側でデータベースからマッチするものを探す際にAspNetUsersテーブルもIncludeし、.where()を加えたら外部キーを張り巡らせなくてもいいのではないか、と思いました。
    たしかに、今になって修正して壊れないかは不安ですが、他の手を打った方がいいということ多分理解しました。

    キャンセル

  • 2018/08/23 17:58

    (2)
    「HTMLタグでのname属性はパッと見ればわかるようになってるけど、Razorってどうやって書いてどうやってどのデータがどこにいくか判断して授受してるの・・・。」という状態だったので、とてもわかりやすい説明とミスの指摘の予想で理解しました。ありがとうございます。

    キャンセル

  • 2018/08/23 18:01

    (3)
    ずっとViewのEditorForに対して Value=ResOwnerId といった記述等をして錯乱してたのでスッキリしました。どこを探しても見つからなかった解決策を教えてくださってありがとうございます・・・!
    Controller側のアクションにインスタンスと返り値を書くとは思いもしてなかったです。

    キャンセル

0

スキャフォールディングした際に生成される次の項目を変更する

@Html.DropDownList("ResOwnerId", null, htmlAttributes: new { @class = "form-control" })
 @Html.TextBox("ResOwnerId", (string)ViewBag.ResOwnerId, new { @class = "form-control", @readonly = "readonly"  })

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2018/08/23 14:19

    それは質問(TextBox に代えて EditorFor を使にはどうしたらいいか)の回答になってないですよ。質問者さんがそもそもの問題としていたモデルバインディングが上手くいかないことの解決策とは言えるかもしれませんが。

    質問する際は、本質的なことを(モデルバインディングが上手くされるようにするにはどうしたらいいか)質問するようにしましょう。

    その前に、スキャフォールディングで意に反して DropDownList になってしまうことを解決する(EditorFor になるようにする)のが先だと思いますが。そうすれば、たぶん、今回のような問題に悩むことはなかったと思います。

    キャンセル

  • 2018/08/23 15:10

    質問の本質の見極めができなかったばっかりにこのような回答を勝手に書いてまい、申し訳ありません。次からは質問の本質に触れるように投稿できるよう心がけます。

    なぜ、ドロップダウンリストの項目とEditorForの項目に分かれているのかというのが正直ふわっとしかわかってないので、そこを調べてみます。(というか、サンプルでも誰かが作ったDBを見たことがないので探して、自分のものと対比してみようとおもいます)

    キャンセル

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

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

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