ASP.NET MVC · Controllers & Views

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.

Beginner 6 min Lesson 6 of 37Covers ASP.NET MVC 5.2 on .NET Framework 4.8.
Before you start:Routing
0/37 in ASP.NET MVC
On this page · 6 min · Beginner

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#

C# Illustrative
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#

C#OrdersController.cs Illustrative
// 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"; } }
Expected output
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#

  1. GET /Orders matches the Default route; MVC creates OrdersController and calls Index with status = null.
  2. With ?status=open, model binding copies the query string value into the status parameter — no Request.QueryString code needed.
  3. Request.HttpMethod, Request.Url and RouteData are available from the Controller base class.
  4. The OrderService does the filtering, so the controller stays a few lines long.
  5. [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#

Public helper methods

A public method like public void RecalculateTotals() becomes a URL anyone can call. Make helpers private or mark them [NonAction].

Storing data in controller fields between requests

Each request gets a new controller instance, so a counter in a field always starts again. Use a database, cache or session.

Forgetting the Controller suffix

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?
Accept string customer as a parameter and filter with StartsWith, ignoring case.
Show solution
C# Illustrative
// 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");
        }
    }
}
Expected output
GET /Orders/Search?customer=a
200 OK
#101, #103

GET /Orders/Search?customer=zed
200 OK
No orders

Mini quiz#

Controllers quizMultiple choice Question 1 of 3

Which class must an MVC 5 controller inherit?

Score so far: 0/0

Want more? Practise all ASP.NET MVC MCQs · ASP.NET MVC interview questions

My notes#

Saved in this browser.

Progress is saved in this browser. Sign in to sync.