Model Binding
Model binding is the quiet helper that turns text from the URL and form fields into typed C# objects. Knowing how it works explains half of the “why is my property null?” questions in MVC.
On this page · 6 min · Intermediate
On this page
- Learning objectives
- Explanation
- Syntax
- Code example
- Step by step
- Real-world use
- Common mistakes
- Best practices
- Remember this
- Practice exercise
- Mini quiz
- Related topics
- My notes
- Reading time
- 6 min
- Difficulty
- Intermediate
- Updated
- Oct 29, 2026
Related tutorials
Learning objectives#
- Bind simple and complex parameters
- Bind lists using indexed field names
- Read conversion errors from ModelState
- Prevent over-posting with [Bind] or view models
Explanation#
For each action parameter, the default model binder looks for a value with the same name in the form fields, then route values, then the query string. Names are matched case-insensitively.
For a complex type (a class), the binder creates the object and fills each public property with a matching name: a field named Price fills product.Price. Nested properties use dots (Address.City) and lists use indexes (Lines[0].Qty).
Values that cannot be converted (Price=abc) do not throw. The property keeps its default value and ModelState gets an error: “The value 'abc' is not valid for Price.”
The binder fills every property it finds a value for — including ones you did not put in the form, such as IsAdmin. This is over-posting. Use [Bind(Include = "Name,Price")] or, better, a view model that only has the editable fields.
UpdateModel(entity) and TryUpdateModel(entity, new[] { "Name", "Price" }) bind into an existing object, which is handy for edit pages.
Syntax#
public ActionResult Search(string q, int page = 1) // ?q=pen&page=2
public ActionResult Create(Product product) // form fields → properties
public ActionResult Save(List<OrderLine> lines) // lines[0].Qty, lines[1].Qty
public ActionResult Edit([Bind(Include = "Name,Price")] Product product)
Code example: Binding a product, its lines and a bad value#
// Models/Product.cs
using System.Collections.Generic;
namespace ShopSite.Models
{
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public bool IsFeatured { get; set; } // must NOT be set by customers
public List<string> Tags { get; set; }
}
}
// Controllers/BindingController.cs
using System.Linq;
using System.Web.Mvc;
using ShopSite.Models;
namespace ShopSite.Controllers
{
public class BindingController : Controller
{
// POST /Binding/Create/7
[HttpPost]
public ActionResult Create(int id, [Bind(Include = "Name,Price,Tags")] Product product)
{
var errors = ModelState
.Where(kv => kv.Value.Errors.Any())
.Select(kv => quot;{kv.Key}: {kv.Value.Errors[0].ErrorMessage}");
return Content(
quot;id (from route) = {id}\n" +
quot;Name = {product.Name}, Price = {product.Price}, IsFeatured = {product.IsFeatured}\n" +
quot;Tags = {string.Join(" | ", product.Tags ?? new System.Collections.Generic.List<string>())}\n" +
quot;Valid = {ModelState.IsValid}; " + string.Join("; ", errors),
"text/plain");
}
}
}
POST /Binding/Create/7 name=Gel+Pen&price=20.50&isFeatured=true&tags[0]=blue&tags[1]=smooth 200 OK id (from route) = 7 Name = Gel Pen, Price = 20.50, IsFeatured = False Tags = blue | smooth Valid = True; POST /Binding/Create/7 Name=Ruler&Price=abc 200 OK id (from route) = 7 Name = Ruler, Price = 0, IsFeatured = False Tags = Valid = False; Price: The value 'abc' is not valid for Price.
This example is illustrative. Copy or download it and run it in your own environment; the expected output is shown above.
Step-by-step explanation#
- The id comes from the route segment /7; the other values come from the form body.
- Field names are matched case-insensitively, so name and price fill Name and Price, and tags[0], tags[1] become a two-item list.
- isFeatured=true was posted, but [Bind(Include = …)] does not list IsFeatured, so it stays false — the over-posting attempt fails.
- Price=abc cannot be converted to decimal: Price keeps 0 and ModelState records the error, so IsValid is false.
Real-world use case#
An online shop’s product form was abused by a seller who added IsFeatured=true to the POST with browser tools, putting his products on the home page for free. Switching the action to a ProductEditViewModel without IsFeatured closed the hole.
Common mistakes#
<input name="productName"> never fills Name. Use Html.TextBoxFor(m => m.Name) so names always match.
A bad number silently becomes 0. Always check ModelState.IsValid before saving.
Every public property of the entity can be set by the client. Bind into a view model and copy the allowed fields.
Best practices#
- Bind into view models that contain only editable fields
- Generate inputs with strongly typed helpers so names match
- Check ModelState.IsValid in every POST action
- Use TryUpdateModel with an explicit property list for edits
Remember this#
Form → route → query string, matched by name.
Bad conversion = default value + ModelState error.
Over-posting: bind to a view model or use [Bind(Include)].
Practice exercise#
Your task: Write an action that binds a list of OrderLine { Sku, Qty } posted as lines[0].Sku, lines[0].Qty, lines[1].Sku, lines[1].Qty and returns the total quantity.
Need a hint?
Show solution
// Controllers/CartController.cs
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
namespace ShopSite.Controllers
{
public class OrderLine
{
public string Sku { get; set; }
public int Qty { get; set; }
}
public class CartController : Controller
{
[HttpPost]
public ActionResult Total(List<OrderLine> lines)
{
var count = lines?.Count ?? 0;
var total = lines?.Sum(l => l.Qty) ?? 0;
return Content(quot;{count} lines, {total} items", "text/plain");
}
}
}
POST /Cart/Total lines[0].Sku=PEN&lines[0].Qty=3&lines[1].Sku=BOOK&lines[1].Qty=2 200 OK 2 lines, 5 items
Mini quiz#
A form posts Price=abc to a decimal property. What happens?
Want more? Practise all ASP.NET MVC MCQs · ASP.NET MVC interview questions
My notes#
Saved in this browser.