動くコード図鑑技術記事現場の渡り方キャリア論すべての記事About
技術記事

ASP.NET MVC でフォームの値がモデルにバインドされない時に見る3箇所(プレフィックス / コレクション / Bind)

バイブス父さん
現役の業務SE
2026年7月20日8 min read
ASP.NET MVC でフォームの値がモデルにバインドされない時に見る3箇所(プレフィックス / コレクション / Bind)

みなさんこんにちは!ヒロポンです!

ASP.NET MVC でフォームを POST したのに、モデルバインディングが効かない。コントローラで受け取ったモデルが空っぽ。もしくは一部のプロパティだけ null。

業務系の画面を作ってると、これ定期的に遭遇しますよね??

私も、View では確かに値を入れてるのにサーバー側で model.Name が null で、「え、送ってるよね?」って画面とにらめっこした回数、もう数えきれないです。

ただモデルバインディングって、仕組みさえ押さえれば「どこを見ればいいか」が一発で分かります。今回は値がバインドされない時に見る3箇所を、実際のコードで潰していきましょう。

まず: モデルバインディングはどう動くのか

モデルバインディングとは、リクエストで送られてきた name とモデルのプロパティ名を突き合わせて、値を自動で詰めてくれる ASP.NET MVC の仕組み です。ここが分かってないと、原因の切り分けがブレます。

流れはざっくりこう。

フォーム POST から name 属性とモデルのプロパティ名をマッチングして値をセットするモデルバインディングの流れ

要は name 属性とプロパティ名の名前合わせ が全て。

バインドされないなら、たいていこの名前合わせのどこかがズレてます。見る場所は次の3つ。

見る場所と対処目安

先に地図を出しておきます。上から順に多いやつです。

  1. name のプレフィックス不一致 … だいたい5分で気づける
  2. コレクションのインデックス記法 … 10分くらい
  3. [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枚にまとめておきます。バインドが効かなかったら、上から確認してください。

モデルバインディングで値が入らない3箇所の症状・確認方法・対処をまとめたチートシート表

モデルバインディングの仕組み(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 を書く時はここで詰まります。

次に読むべき記事

以上!

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

この記事のコードと手順は ぜんぶ動作検証済み。 安心して現場で試してくれ。
バイブス父さん

現役の業務SE。C# / SQL Server 保守の現場から、コードも人もキャリアも全部書く。 実体験ベース。

運営者について