Controllers
Every request in an MVC app lands in a controller. A good controller reads like a short checklist: get the input, ask someone else to do the work, choose the response.
On this page · 6 min · Beginner
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
- Beginner
- Updated
- Oct 29, 2026
Related tutorials
Learning objectives#
- Create controllers that MVC can discover
- Use the properties the Controller base class gives you
- Read query string values through action parameters
- Keep controllers thin by delegating to services
Explanation#
A controller is a public class whose name ends in Controller and which inherits System.Web.Mvc.Controller. MVC finds it by name: /Orders/... looks for OrdersController in any namespace of the app.
Every public, non-static method is an action that can be called from a URL. Make helper methods private (or mark them [NonAction]) so nobody can call them from the browser.
The base class gives you the current request through properties: Request (URL, query string, headers, HttpMethod), Response, RouteData, Session, TempData, User (the signed-in user) and ModelState.
You rarely need Request.QueryString directly: an action parameter with the same name as the query string key is filled automatically by model binding, already converted to the right type.
A new controller instance is created for every request and disposed afterwards. Keep it stateless, and move business rules and data access into services that the controller calls.
Syntax#
public class OrdersController : Controller
{
// GET /Orders?status=open&page=2
public ActionResult Index(string status, int page = 1) { ... }
}
Code example: A thin controller that uses request data#
// Models/OrderService.cs
using System.Collections.Generic;
using System.Linq;
namespace ShopSite.Models
{
public class Order
{
public int Id { get; set; }
public string Customer { get; set; }
public string Status { get; set; }
}
public class OrderService
{
private static readonly List<Order> Orders = new List<Order>
{
new Order { Id = 101, Customer = "Asha", Status = "open" },
new Order { Id = 102, Customer = "Ravi", Status = "shipped" },
new Order { Id = 103, Customer = "Meera", Status = "open" },
};
public List<Order> Find(string status)
{
return string.IsNullOrEmpty(status)
? Orders.ToList()
: Orders.Where(o => o.Status == status).ToList();
}
}
}
// Controllers/OrdersController.cs
using System.Linq;
using System.Web.Mvc;
using ShopSite.Models;
namespace ShopSite.Controllers
{
public class OrdersController : Controller
{
private readonly OrderService _orders = new OrderService();
// GET /Orders or /Orders?status=open
public ActionResult Index(string status)
{
var found = _orders.Find(status);
var lines = found.Select(o => quot;#{o.Id} {o.Customer} ({o.Status})");
var info = quot;{Request.HttpMethod} {Request.Url.PathAndQuery} handled by {RouteData.Values["controller"]}.{RouteData.Values["action"]}";
return Content(info + "\n" + string.Join("\n", lines), "text/plain");
}
// Not reachable from a URL
[NonAction]
public string Secret() => "hidden";
}
}
GET /Orders 200 OK GET /Orders handled by Orders.Index #101 Asha (open) #102 Ravi (shipped) #103 Meera (open) GET /Orders?status=open 200 OK GET /Orders?status=open handled by Orders.Index #101 Asha (open) #103 Meera (open) GET /Orders/Secret 404 Not Found
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#
- GET /Orders matches the Default route; MVC creates OrdersController and calls Index with status = null.
- With ?status=open, model binding copies the query string value into the status parameter — no Request.QueryString code needed.
- Request.HttpMethod, Request.Url and RouteData are available from the Controller base class.
- The OrderService does the filtering, so the controller stays a few lines long.
- [NonAction] hides Secret from routing, so /Orders/Secret returns 404.
Real-world use case#
A support portal’s OrdersController grew to 1,200 lines with SQL inside every action. Moving queries into an OrderService let the team unit-test the filters and cut the controller to 150 lines.
Common mistakes#
A public method like public void RecalculateTotals() becomes a URL anyone can call. Make helpers private or mark them [NonAction].
Each request gets a new controller instance, so a counter in a field always starts again. Use a database, cache or session.
A class named Orders (without the suffix) is never found, so every URL returns 404.
Best practices#
- One controller per resource or feature
- Receive input as action parameters, not Request.QueryString
- Delegate work to services and keep actions under ~20 lines
- Mark non-action public methods with [NonAction]
Remember this#
Name ends in Controller, inherits Controller.
Every public method is a URL — be careful.
New controller per request: no state in fields.
Practice exercise#
Your task: Add a Search action to OrdersController that takes ?customer= and returns the matching order ids, or “No orders” when nothing matches.
Need a hint?
Show solution
// Controllers/OrdersController.cs
using System;
using System.Linq;
using System.Web.Mvc;
namespace ShopSite.Controllers
{
public class OrdersController : Controller
{
private static readonly (int Id, string Customer)[] Orders = { (101, "Asha"), (102, "Ravi"), (103, "Arjun") };
// GET /Orders/Search?customer=a
public ActionResult Search(string customer)
{
var ids = Orders
.Where(o => !string.IsNullOrEmpty(customer) && o.Customer.StartsWith(customer, StringComparison.OrdinalIgnoreCase))
.Select(o => "#" + o.Id)
.ToList();
return Content(ids.Any() ? string.Join(", ", ids) : "No orders", "text/plain");
}
}
}
GET /Orders/Search?customer=a 200 OK #101, #103 GET /Orders/Search?customer=zed 200 OK No orders
Mini quiz#
Which class must an MVC 5 controller inherit?
Want more? Practise all ASP.NET MVC MCQs · ASP.NET MVC interview questions
My notes#
Saved in this browser.