Views, ViewModels, and Action Results
An action's return value is a description of the HTTP response. Choosing the right
ActionResult β and disciplining what data travels to the view β is most of what separates a
tidy MVC app from one where every screen change breaks two others.
ViewModel-per-view discipline
The rule: every non-trivial view gets its own ViewModel class, shaped for exactly that screen β the display fields, the dropdown options, the paging info, all of it. Not the EF entity, not a shared "God model" reused across five screens.
Why so strict? Entities expose fields the screen shouldn't (and enable overposting on the way
back in). Shared models accumulate properties that are null on some screens and required on
others, until no one can change anything safely. A per-view model makes the view's contract
explicit: OrderEditViewModel is the documentation for what the Edit screen needs. AutoMapper
(later module) removes most of the copying tedium that makes people cheat.
ViewBag (ViewBag.Title = "Orders") is the tempting shortcut β a dynamic bag with no
compile-time checking. Typo ViewBag.Titel in the view and you get null at runtime, not a
build error; the view's data contract becomes invisible. Acceptable uses are cosmetic one-offs
like the page title. If ViewBag is carrying a dropdown list or business data, that's a ViewModel
property that hasn't been written yet.
The results you actually return
View(model)β render HTML.PartialView(...)β render a fragment with no layout; this is what your jQuery AJAX calls fetch to update part of a page.Json(...)β serialize data for AJAX. Property names are camelCased by default (CustomerNameβcustomerName) β write your jQuery against the wire shape, not the C# property names.File(...)/FileStreamResultβ downloads and exports (the CSV/PDF exports every LOB app grows).RedirectToAction(...)β send the browser somewhere else; the workhorse below.NotFound()β return a real 404 when the id doesn't exist, instead of a view crashing on a null model.
PRG: Post/Redirect/Get
Never return View() from a successful POST. If you do, the browser's "current page" is the
POST β refresh re-submits it ("Confirm form resubmission?"), and the user just created the order
twice. The PRG pattern: on success, RedirectToAction("Index") so the browser lands on a
clean GET; on validation failure, return View(model) so the user's input and error messages
survive. To flash "Order saved" across the redirect, use TempData β it persists for exactly
one subsequent request, which is precisely the PRG hop. Success = redirect, failure = redisplay:
make it a reflex.