What Is a Controller in ASP.NET MVC
A controller is a class that derives from the framework's Controller base class and is responsible for handling an incoming HTTP request, executing the appropriate logic, and returning a response such as a rendered view or a JSON payload. In the MVC pattern the controller sits between the router and the model: it receives the parsed request, asks the model layer (services, repositories, Entity Framework contexts) for data, and then hands that data to a view for rendering. Controllers should stay thin, delegating real business rules to separate service classes so the controller's own job is limited to translating HTTP concerns into and out of application logic.
Cricket analogy: A controller acts like the on-field captain who receives the umpire's signal (the request), decides which bowler to bring on (which service to call), and reports the outcome back to the dressing room (the response) without personally bowling every delivery.
Controller Naming and Discovery
By convention every controller class name must end with the suffix Controller, such as HomeController or ProductController, and the framework strips that suffix when matching the {controller} route token, so a request to /Product/Details maps to ProductController. In classic ASP.NET MVC controllers are located through reflection over assemblies implementing IController, while ASP.NET Core discovers them as part of the MVC middleware's controller feature provider during application startup, scanning for public, non-abstract classes that either derive from Controller or are decorated with the [Controller] attribute. This convention-based discovery removes the need for manual registration of each controller.
Cricket analogy: It is like how a scorecard automatically matches a bowler's figures to their surname printed on the back of the jersey, so 'Bumrah' on the sheet reliably resolves to the correct player without a separate lookup table.
public class ProductController : Controller
{
private readonly IProductService _productService;
public ProductController(IProductService productService)
{
_productService = productService;
}
// GET: /Product/Details/5
public IActionResult Details(int id)
{
var product = _productService.GetById(id);
if (product == null)
{
return NotFound();
}
return View(product);
}
}Dependency Injection and Controller Instantiation
Modern ASP.NET Core controllers rarely instantiate their dependencies directly; instead they declare interfaces such as IProductService in the constructor, and the built-in dependency injection container, configured in Program.cs with calls like builder.Services.AddScoped<IProductService, ProductService>(), resolves and injects a concrete instance for every request. This means the controller factory (IControllerActivator) creates a new controller instance per request, resolves its constructor parameters from the service provider, and disposes of it once the request completes, which keeps controllers stateless and makes unit testing straightforward because a test can pass in a mock implementation of the interface.
Cricket analogy: It is like how a franchise in the IPL auction does not manufacture its own all-rounder but drafts one from the player pool each season based on squad needs, swapping in a different player next season without changing the team's strategy.
Keep controllers thin: validate input, call a service or repository, and shape the result. If a controller action grows past a few lines of branching logic, that logic almost always belongs in a dedicated service class instead.
The Controller Base Class and Request Context
Every controller inherits members from the Controller base class (ControllerBase in ASP.NET Core Web API scenarios) that expose the current request context: HttpContext gives access to the raw request and response, ModelState reports validation errors gathered from model binding, User exposes the authenticated ClaimsPrincipal, and helper methods like View(), PartialView(), Json(), and RedirectToAction() build the appropriate IActionResult. Because these members are tied to the current HTTP context, controllers are inherently short-lived and per-request, which is why long-running or shared state must never be stored on controller instance fields.
Cricket analogy: It is like the on-field umpire's kit that comes with the match ball, the light meter, and the wicket-count clicker bundled in for that specific innings, all reset for the next match rather than carried over.
Never store per-request or user-specific data in instance fields on a controller. Because controllers are typically instantiated fresh for each request (and can be reused across requests in some configurations), mutable instance state leads to race conditions and data leaking between unrelated users.
- A controller is a class deriving from Controller (or ControllerBase) that mediates between routing, the model, and the view.
- Controller class names must end with the suffix 'Controller'; the framework strips it when matching the {controller} route token.
- ASP.NET Core discovers controllers automatically via the controller feature provider during startup, requiring no manual registration.
- Constructor dependency injection supplies services to controllers, keeping them decoupled and easy to unit test.
- Controllers should stay thin, delegating business logic to services and focusing on translating HTTP requests into responses.
- Members like HttpContext, ModelState, and User expose the current request's context and are only valid for that single request.
- Never store per-request state in controller instance fields, since controllers are effectively transient per request.
Practice what you learned
1. What naming convention does ASP.NET MVC use to map a URL segment to a controller class?
2. How do ASP.NET Core controllers typically obtain their dependencies?
3. Why should controllers be kept 'thin'?
4. What is a key risk of storing per-user data in a controller's instance field?
5. Which base class member exposes validation errors gathered during model binding?
Was this page helpful?
You May Also Like
Action Methods and Action Results
Understand how public controller methods become callable actions and how the IActionResult family shapes the HTTP response returned to the client.
Routing in MVC
See how convention-based routing maps incoming URLs to controller actions using route templates, defaults, and constraints.
Attribute Routing
Learn how to declare routes directly on controllers and actions with attributes, and how this approach complements or replaces convention-based routing.
Related Reading
Related Study Notes in Microsoft Technologies
Browse all study notesWindows 10 / UWP Development Study Notes
.NET · 30 topics
Microsoft TechnologiesWindows Batch Scripting Study Notes
Batch · 30 topics
Microsoft TechnologiesMFC (Microsoft Foundation Classes) Study Notes
C++ · 30 topics
Microsoft TechnologiesSilverlight Study Notes
.NET · 30 topics
Microsoft TechnologiesXAML Study Notes
.NET · 30 topics
Microsoft TechnologiesWPF (Windows Presentation Foundation) Study Notes
.NET · 30 topics