みなさんこんにちは!ヒロポンです!
ASP.NET MVC でフォームを POST したのに、モデルバインディングが効かない。コントローラで受け取ったモデルが空っぽ。もしくは一部のプロパティだけ null。
業務系の画面を作ってると、これ定期的に遭遇しますよね??
私も、View では確かに値を入れてるのにサーバー側で model.Name が null で、「え、送ってるよね?」って画面とにらめっこした回数、もう数えきれないです。
ただモデルバインディングって、仕組みさえ押さえれば「どこを見ればいいか」が一発で分かります。今回は値がバインドされない時に見る3箇所を、実際のコードで潰していきましょう。
まず: モデルバインディングはどう動くのか
モデルバインディングとは、リクエストで送られてきた name とモデルのプロパティ名を突き合わせて、値を自動で詰めてくれる ASP.NET MVC の仕組み です。ここが分かってないと、原因の切り分けがブレます。
流れはざっくりこう。

要は name 属性とプロパティ名の名前合わせ が全て。
バインドされないなら、たいていこの名前合わせのどこかがズレてます。見る場所は次の3つ。
見る場所と対処目安
先に地図を出しておきます。上から順に多いやつです。
- name のプレフィックス不一致 … だいたい5分で気づける
- コレクションのインデックス記法 … 10分くらい
- [Bind] と過剰バインディング … 設計判断込みで15分
では1つずつ。
1. name 属性のプレフィックス不一致(一番多い)
まずモデルとコントローラはこんな感じ。ごく普通です。
public class UserForm
{
public string Name { get; set; }
public string Email { get; set; }
}
// コントローラ: POST された値が form に入ってくる想定
public ActionResult Save(UserForm form)
{
// form.Name / form.Email を使う
return View(form);
}
この時、フォーム側の name はプロパティ名とそのまま一致させます。
<input type="text" name="Name" />
<input type="text" name="Email" />
ハマるのは、部分ビューや EditorFor を使った時にプレフィックスが付く ケース。
たとえばモデルを入れ子にして @Html.EditorFor(m => m.User) とすると、送信される name は User.Name になります。受け側の引数名やモデル構造とここがズレると、丸ごと null。
いちばん速い確認方法は、ブラウザの開発者ツールでフォーム送信を見て、実際に飛んでる name を目視する こと。頭の中の想定と、実際に飛んでる name、ちゃんと合ってます??だいたいここが食い違ってるんですよね。
教訓: バインドされない時は、まず「送ってる name」と「受けるプロパティ名」を並べて見る。
2. List・配列などコレクションのインデックス記法
複数行のフォーム(明細行みたいなやつ)をまとめて受け取りたい時。これも定番の詰まりどころです。
コレクションをバインドするには、name にインデックスが要ります。
<input type="text" name="Items[0].Name" />
<input type="text" name="Items[1].Name" />
<input type="text" name="Items[2].Name" />
受け側はこんな感じ。
public class OrderForm
{
public List<Item> Items { get; set; }
}
public class Item
{
public string Name { get; set; }
}
ポイントは、インデックスが 0 から連番 であること。
JavaScript で行を動的に足したり消したりして、Items[0] Items[2] みたいに番号が飛ぶと、飛んだところでバインドが打ち切られて Items[2] 以降が消えます。これ、地味に気づきにくい。
番号を飛ばしたいなら、Index という hidden フィールドを添える手があります。
<input type="hidden" name="Items.Index" value="a" />
<input type="text" name="Items[a].Name" />
こうすると連番じゃない任意のキーでバインドできます。動的フォームを作るなら覚えておくと、いい感じに効きます。
教訓: コレクションが途中で切れたら、まずインデックスの連番が崩れてないか疑う。
3. [Bind] と過剰バインディング(セキュリティ)
3つめは、バインドが「効かない」んじゃなくて「効きすぎる」ワナです。
モデルバインディングは、name が一致するプロパティを片っ端から詰めます。つまり悪意のあるユーザーが、フォームに存在しないはずのフィールドを勝手に足して POST すると、意図しないプロパティまで書き換わる。これが過剰バインディング(Mass Assignment)です。
たとえばこういうモデルがあったとして。
public class User
{
public string Name { get; set; }
public bool IsAdmin { get; set; } // 画面には出してないつもり
}
画面に IsAdmin の入力欄を出してなくても、攻撃者が IsAdmin=true を手で足して送れば、そのままバインドされて管理者に昇格。なんてことが起こり得ます。
対策はいくつかあります。素直なのは、受け取るプロパティを [Bind] で限定 する書き方。
public ActionResult Save([Bind(Include = "Name")] User user)
{
// user.Name しかバインドされない。IsAdmin は無視される
return View(user);
}
ただ、より安全で保守しやすいのは、画面専用の ViewModel を分ける やり方です。
// 画面が触っていいプロパティだけ持つ ViewModel
public class UserEditViewModel
{
public string Name { get; set; }
}
エンティティを直接バインド先にせず、ViewModel を挟む。こうしておくと、そもそも IsAdmin はバインドの土俵に上がりません。
ん? ちょっと手間じゃない?って思うかもですが、これが一番事故らないです。
教訓: エンティティを直接フォームで受けない。ViewModel を挟むか [Bind] で絞る。
そのほか地味に効くやつ
3箇所以外で、私が実際に時間を持っていかれたのを2つ。
- プロパティに setter が無い / private。
{ get; }だけだとバインダーが値をセットできません。読み取り専用にしてたのを忘れてハマったことがあります。 - チェックボックス。HTML のチェックボックスは未チェックだと値が送信されません。
CheckBoxForは同名の hidden フィールドを自動で出して false を送ってくれますが、自前で<input type="checkbox">を書くと、OFF が「送られない=バインドされない」になって混乱します。
まとめ・チートシート
見る場所を1枚にまとめておきます。バインドが効かなかったら、上から確認してください。

モデルバインディングの仕組み(name とプロパティ名の名前合わせ)を押さえておくと、フォーム処理のデバッグがめちゃくちゃ速くなります!!
「送ってる name」と「受けるプロパティ」を並べる。まずはこれだけ覚えて帰ってください。
動作確認メモ: この記事のモデル(POCO)クラスは .NET SDK でコンパイル確認済みですが、
[Bind]やバインドの実挙動は ASP.NET MVC のランタイム(IIS + MVC パイプライン)で動くものです。実際の POST → バインドは、Windows + Visual Studio + IIS Express など実環境での確認をおすすめします。
よくある質問
Q1. モデルの一部プロパティだけ null になるのはなぜですか?
多くは name とプロパティ名が一致してないからです。特に部分ビューや EditorFor でプレフィックスが付くと name がずれます。開発者ツールで実際に送信されている name を確認するのが最短です。
Q2. コレクションがバインドされない時はどうすればいいですか?
name を Items[0].Name のようにインデックス付きにします。インデックスは 0 から連番が基本で、途中が飛ぶとそこで打ち切られます。非連続にしたいなら Index の hidden フィールドを使ってください。
Q3. 過剰バインディングはどう防ぎますか?
画面用の ViewModel を分けるのが一番安全です。手軽にやるなら [Bind(Include="...")] で受け取るプロパティを限定します。エンティティを直接フォームで受けないのが基本方針です。
Q4. チェックボックスの値がバインドされないのはなぜですか?
HTML のチェックボックスは未チェックだと値が送信されないからです。CheckBoxFor は同名の hidden を自動で出して false を送る仕組みになっています。自前で input を書く時はここで詰まります。
次に読むべき記事




以上!
同じところでハマってる人がいたら、どんどんシェア待ってるぜ!!



