# Controllers and Actions

A controller is a class whose public methods, its actions, answer requests. Learn how the default route picks one from the address, where parameters come from, and what an action can return.

Journeyman · ASP.NET Core · C# · Updated November 3, 2026

## After this lesson you can

- Write a controller with actions that answer requests
- Explain how the default route maps an address to a controller, an action and an id
- Return a view, text, Not Found or a redirect from an action

## Key takeaways

- A controller's name ends in `Controller` and it inherits from `Controller`. Each of its public methods is an action.
- The default route `{controller=Home}/{action=Index}/{id?}` maps `/Products/Details/7` to `ProductsController`, its `Details` action and `id` 7.
- Parameters are filled by name: `id` from the route, other names from the query string. A missing value gets its type's default.
- `View()` returns HTML, `Content()` plain text, `NotFound()` a 404, and `RedirectToAction()` a redirect.
- Look the item up, return `NotFound()` if it isn't there, and otherwise return its view.

A **controller** is the part of an MVC app that answers requests. It's an ordinary C# class, with two rules:

- Its name ends in `Controller`, and it lives in the `Controllers` folder.
- It inherits from ASP.NET Core's `Controller` class, which gives it methods such as `View()` and `NotFound()`.

Each **public method** of a controller is an **action**: one thing the app can do, reached at its own address.

## Your first controller

Add a file `Controllers/ProductsController.cs` to NorthwindWeb (in Visual Studio: right-click **Controllers**, **Add > Controller...**, **MVC Controller - Empty**):

```csharp
using Microsoft.AspNetCore.Mvc;

namespace NorthwindWeb.Controllers;

public class ProductsController : Controller
{
    // GET /Products/Hello
    public IActionResult Hello()
    {
        return Content("Hello from the Products controller!");
    }
}
```

Run the app and go to `/Products/Hello` (for example `http://localhost:5204/Products/Hello`). The browser shows:

```text
Hello from the Products controller!
```

## How the address picks the action

Remember the route in `Program.cs`:

```csharp
app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");
```

The pattern reads the address in up to three parts. The first names the controller (without the word `Controller`), the second the action, and the third, if there is one, is the `id`. `=Home` and `=Index` are defaults for a part that's missing, and the `?` makes `id` optional.

| Address | Controller | Action | id |
|---|---|---|---|
| `/` | `HomeController` | `Index` | |
| `/Products` | `ProductsController` | `Index` | |
| `/Products/Hello` | `ProductsController` | `Hello` | |
| `/Products/Details/7` | `ProductsController` | `Details` | 7 |
| `/Home/Privacy` | `HomeController` | `Privacy` | |

Upper or lower case doesn't matter: `/products/hello` works too. An address that matches no controller or action gets **404 Not Found**.

## Parameters

An action's parameters are filled in from the request, by name. `id` comes from the route; any other name comes from the **query string**, the part after `?`:

```csharp
// GET /Products/Greet?name=Ada
public IActionResult Greet(string name)
{
    return Content($"Hello, {name}!");
}
```

| Address | Shows |
|---|---|
| `/Products/Greet?name=Ada` | `Hello, Ada!` |
| `/Products/Greet?name=Grace` | `Hello, Grace!` |
| `/Products/Greet` | `Hello, !` |

When the value is missing, the parameter gets its type's default: `null` for a `string` (which prints as nothing), 0 for an `int`. The names must match (upper or lower case doesn't matter): `?nam=Ada` fills nothing.

## What an action returns

An action returns an **`IActionResult`**: a description of the response. The `Controller` class has a method for each kind:

| You write | The browser gets |
|---|---|
| `return View(product);` | HTML, built by the view with the same name as the action |
| `return Content("Hello");` | Plain text |
| `return NotFound();` | **404 Not Found** |
| `return RedirectToAction("Index");` | A redirect: the browser goes to that action's address instead |

## A controller that uses the database

Real actions get their data from a DAO. Copy `Product` into `Models/Product.cs` and `ProductDao` into `Data/ProductDao.cs` from *Example: a Product DAO* (with `namespace NorthwindWeb.Models;` and `namespace NorthwindWeb.Data;` at the top), and add a `GetAll` method that returns every product ordered by name.

The DAO needs the connection string. A controller can ask for the app's settings in its constructor, as an `IConfiguration`, and ASP.NET Core passes them in when it creates the controller for a request:

```csharp
using Microsoft.AspNetCore.Mvc;
using NorthwindWeb.Data;
using NorthwindWeb.Models;

namespace NorthwindWeb.Controllers;

public class ProductsController : Controller
{
    private readonly ProductDao productDao;

    public ProductsController(IConfiguration configuration)
    {
        string connectionString = configuration.GetConnectionString("Northwind")!;
        productDao = new ProductDao(connectionString);
    }

    // GET /Products
    public IActionResult Index()
    {
        List<Product> products = productDao.GetAll();
        return View(products);
    }

    // GET /Products/Details/7
    public IActionResult Details(int id)
    {
        Product? product = productDao.GetById(id);
        if (product is null)
        {
            return NotFound();
        }
        return View(product);
    }
}
```

`Details` shows the pattern you'll use again and again: look the item up, return **Not Found** if it isn't there, otherwise return the view. The views themselves come in the next lesson; until you add them, these two actions fail with *The view 'Index' was not found*.

ASP.NET Core makes a **new controller for every request**, so `productDao` lives only as long as one request. (Later, in *ASP.NET Core MVC in Depth*, you'll let ASP.NET Core create the DAO too.)

## Common problems

| What you see | What to do |
|---|---|
| 404 for an action you just wrote | Check the class name ends in `Controller`, the method is `public`, and you restarted the app. |
| `The view 'Index' was not found` | The action works; its view is missing. Add `Views/Products/Index.cshtml`. |
| A parameter is always empty or 0 | Its name doesn't match the query string's name. |

## Check yourself

3 questions to try on the lesson page.

## So what

Controllers are where a request becomes C#: the route picks the action, the request fills its parameters, and what it returns becomes the response. Every page and, later, every API call starts in an action like these.

## Key terms

- Controller
- Action Method
- Route Template
- IActionResult
- [404 Not Found](https://www.ask-codey.com/glossary/404-not-found)

## Cheat sheets

- [Controllers and Actions](https://www.ask-codey.com/cheat-sheets/controllers-and-actions-cheat-sheet)

Source: https://www.ask-codey.com/lessons/controllers-and-actions
