LyraLearn AI Learning Platform
Exams
← Module 2 Β· ASP.NET MVC Architecture
🎧 Listen

Controllers and Model Binding

The controller is where HTTP meets your code, and model binding is the machinery that turns a raw request into typed C# parameters. Understanding exactly where bound values come from β€” and what happens when binding fails β€” eliminates a whole category of "it's always null" afternoons.

Where bound values come from

Given public IActionResult Save(OrderViewModel model, int page = 1), the binder fills parameters by name from, in effect: route values (/orders/edit/5 β†’ id = 5), the query string (?page=2), and form fields (POST bodies where input name attributes match property names). Complex types are bound property-by-property; collections bind from indexed names (Items[0].Qty). When you need to be explicit, the source attributes β€” [FromBody] (JSON), [FromRoute], [FromQuery], [FromForm] β€” pin a parameter to one source; note that JSON payloads on MVC controllers bind only with [FromBody], never implicitly. When a value can't be found, you get the type's default (null, 0) β€” not an error. That's why the mismatched-name bug is silent: the form posts CustNo, the property is CustomerNo, and the property just stays null. First debugging move for "my parameter is empty": open the browser dev tools, look at the actual posted names, and compare them to your property names (matching is case-insensitive, but the names must match).

Validation: annotations + ModelState

Decorate the ViewModel with data annotations β€” [Required], [StringLength(100)], [Range(1, 999)], [EmailAddress] β€” and the binder validates during binding, recording every failure in ModelState. The canonical POST action shape is muscle memory:

[HttpPost, ValidateAntiForgeryToken]
public IActionResult Create(OrderViewModel model)
{
    if (!ModelState.IsValid) return View(model);
    _orderService.Create(model);
    return RedirectToAction(nameof(Index));
}

Binding failures land in ModelState too β€” "abc" posted to an int property is a model error even with no annotations. Redisplaying the same view with the invalid model is what makes the validation summary and field messages appear (Module 3 closes this loop client-side).

Anti-patterns that define the job

Fat controllers. An action that opens a DbContext, runs business rules, sends an email, and builds three ViewModels is untestable and unreviewable. The controller's whole job is: bind, check ModelState, call one service method, choose a result. If an action is 80 lines, the missing service layer is the actual finding.

Binding to entities (overposting). public IActionResult Edit(Employee entity) binds every property a malicious client posts β€” including Salary or IsAdmin, whether or not your form rendered a field for them. This is overposting (mass assignment), and it's a real vulnerability class in government apps. The fix is structural, not [Bind(...)] property lists: bind to a ViewModel that contains only the editable fields, then map to the entity server-side.

Doing HTTP in services. Keep Request, Session, and HttpContext out of your service layer β€” pass values in as parameters, or the layer below MVC becomes untestable too.

🧠 Quiz yourself on this lesson →

Ask the AI Tutor

Grounded in the course lessons β€” it cites its sources and says when it doesn't know.