Controllers and Actions
|
This section documents ASP.NET MVC 5.3.x, ASP.NET Web API 2.2, ASP.NET Web Pages 3, OWIN/Katana, SignalR 2,
and ASP.NET Identity 2 — all running on .NET Framework 4.8.1 — the This is not ASP.NET Core MVC. ASP.NET MVC 5 is the pre-Core, This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
This page documents ASP.NET MVC 5.3.x on .NET Framework 4.8.1 — not ASP.NET Core MVC, whose equivalent types
are Controller/ControllerBase returning IActionResult.
Controller and ControllerBase
System.Web.Mvc.ControllerBase is the abstract root implementing IController; System.Web.Mvc.Controller
extends it with everything action methods actually use — ActionResult-returning helper methods (View,
PartialView, Json, Redirect, …), ViewBag/ViewData/TempData, and access to the current
HttpContextBase.
public class ProductsController : Controller
{
private readonly IProductRepository _repository;
public ProductsController(IProductRepository repository) => _repository = repository;
public ActionResult Index() => View(_repository.GetAll());
}
Public methods on a Controller are actions by default unless marked [NonAction]; the constructor above
takes a dependency that a custom IControllerFactory must supply (see
The MVC Pattern and Request Life Cycle).
Action selectors
[HttpGet]
public ActionResult Edit(int id) => View(_repository.Find(id));
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(Product model)
{
if (!ModelState.IsValid) return View(model);
_repository.Update(model);
return RedirectToAction("Index");
}
[ActionName("Delete")] // exposed as /Products/Delete, method name stays DeleteConfirmed
[HttpPost]
public ActionResult DeleteConfirmed(int id) { _repository.Remove(id); return RedirectToAction("Index"); }
[NonAction]
public Product LoadForEditing(int id) => _repository.Find(id); // never routable
[AcceptVerbs(HttpVerbs.Get | HttpVerbs.Head)]
public ActionResult Details(int id) => View(_repository.Find(id));
[HttpGet]/[HttpPost]/[HttpPut]/[HttpDelete] implement ActionMethodSelectorAttribute; [ActionName]
decouples the routable action name from the CLR method name (commonly used for Edit/Delete GET-vs-POST
overload pairs); [NonAction] removes a public method from consideration entirely; [AcceptVerbs] predates
the per-verb attributes and accepts a bitwise combination of HttpVerbs.
Parameters and defaults
Action parameters are populated by model binding from route data, the query string, and posted form values (in that precedence — see Model Binding and Validation); a C# default parameter value is used when no matching value is found:
public ActionResult Index(int page = 1, string sort = "name") => View(_repository.Page(page, sort));
The ActionResult family
Every built-in result derives from the abstract ActionResult and implements ExecuteResult(ControllerContext):
| Result | Returned by / purpose |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
The default when an action returns |
public ActionResult Summary(int id)
{
var product = _repository.Find(id);
if (product == null) return HttpNotFound();
return Json(new { product.Id, product.Name, product.Price }, JsonRequestBehavior.AllowGet);
}
Custom results are straightforward — subclass ActionResult and override ExecuteResult (a CSV export, for
example); see The MVC Pattern and Request Life Cycle
for where ExecuteResult sits in the pipeline.
ViewBag vs. ViewData vs. TempData
-
ViewData— aViewDataDictionary(string-keyed,objectvalues) passed from controller to view for the current request only; requires casting on read. -
ViewBag— adynamicwrapper over the sameViewDatadictionary; no casting, but no compile-time checking either. -
TempData— aTempDataDictionarythat survives exactly one subsequent request (the classic redirect-then-display-a-message pattern after a POST). It is backed by anITempDataProvider—SessionStateTempDataProviderby default, i.e.Sessionunder the hood — and each key is marked for removal the moment it is read ("one-read" semantics);Keep()/Peek()opt out of that removal when a value must survive an extra hop.
public ActionResult Create(Product model)
{
_repository.Add(model);
TempData["Message"] = "Product created."; // survives the following redirect
return RedirectToAction("Index");
}
public ActionResult Index()
{
ViewBag.Message = TempData["Message"]; // reading here removes it from TempData
return View(_repository.GetAll());
}
Async actions
Action methods can be async Task<ActionResult>; AsyncControllerActionInvoker (the default since MVC 4)
handles both synchronous and asynchronous actions on the same Controller base class — there is no separate
AsyncController requirement in MVC 5:
public async Task<ActionResult> Details(int id)
{
var product = await _repository.FindAsync(id);
return product == null ? (ActionResult)HttpNotFound() : View(product);
}
See Caching and Performance for why "async all the way" matters under IIS’s thread pool.
Request/Response/Server/User via HttpContextBase
ControllerBase exposes the current request through HttpContextBase (the testable abstraction over
System.Web.HttpContext, introduced specifically so controllers can be unit tested without an ASP.NET runtime — see Testing and Diagnostics):
public ActionResult WhoAmI()
{
string ip = Request.UserHostAddress; // HttpRequestBase
bool authenticated = User.Identity.IsAuthenticated; // IPrincipal
string root = Server.MapPath("~/App_Data"); // HttpServerUtilityBase
Response.Cache.SetCacheability(HttpCacheability.NoCache);
return Content(ip);
}