# Creating an API Controller

An API controller derives from ControllerBase, is marked \[ApiController\], and gets its address from \[Route\]. It asks for its DAO through an interface registered in Program.cs, and its actions return data that ASP.NET Core sends as JSON.

Journeyman · ASP.NET Core · REST API · Updated November 3, 2026

## After this lesson you can

- Create an API controller with \[ApiController\], \[Route\] and ControllerBase, and register the DAO it asks for
- Explain how an API controller differs from an MVC controller

## Key takeaways

- An API controller derives from `ControllerBase`, is marked `[ApiController]`, and gets its address from an attribute such as `[Route("api/categories")]`.
- `[ApiController]` checks the validation annotations and answers `400 Bad Request` before your action runs, so you don't check `ModelState.IsValid`.
- The controller asks for an `ICategoryDao`, and `Program.cs` says which class keeps that contract with `AddScoped<ICategoryDao>(...)`.
- An API uses `AddControllers()` and `MapControllers()`, and attribute routes instead of the `{controller}/{action}` pattern.
- An MVC controller returns views for a person in a browser; an API controller returns data or a status for a program.

Time to build the Northwind categories API.

## Creating the project

In Visual Studio, create a new project from the **ASP.NET Core Web API** template, named `NorthwindApi`, and this time **keep** the **Use controllers** box checked. (In a terminal: `dotnet new webapi --use-controllers -o NorthwindApi`.)

Delete the sample `WeatherForecast.cs` and `Controllers/WeatherForecastController.cs`, then add the packages you used with Dapper: **Dapper**, **Microsoft.Data.SqlClient** and **System.Security.Cryptography.Pkcs**. Put the connection string in `appsettings.json`, as in your MVC apps:

```json
{
  "ConnectionStrings": {
    "Northwind": "Server=localhost;Database=Northwind;Integrated Security=true;TrustServerCertificate=true;"
  }
}
```

## The model and the DAO

The model is a plain class, with the same validation annotations you used in MVC:

```csharp
using System.ComponentModel.DataAnnotations;

namespace NorthwindApi.Models;

public class Category
{
    public int CategoryId { get; set; }

    [Required]
    [StringLength(15)]
    public string CategoryName { get; set; } = "";

    public string? Description { get; set; }
}
```

The DAO uses Dapper, as in *Data Access with Dapper*. This time it comes with an **interface**, so the controller can depend on the contract, and a test can hand it a fake:

```csharp
using NorthwindApi.Models;

namespace NorthwindApi.Data;

public interface ICategoryDao
{
    List<Category> GetAll();
    Category? GetById(int categoryId);
    Category Add(Category category);
    Category? Update(int categoryId, Category category);
    bool Delete(int categoryId);
}
```

`CategoryDao : ICategoryDao` writes each method with Dapper. You'll see their SQL in the next lessons, as each one is needed.

## The controller

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

namespace NorthwindApi.Controllers;

[ApiController]
[Route("api/categories")]
public class CategoriesController : ControllerBase
{
    private readonly ICategoryDao categoryDao;

    public CategoriesController(ICategoryDao categoryDao)
    {
        this.categoryDao = categoryDao;
    }

    [HttpGet]
    public List<Category> GetAll()
    {
        return categoryDao.GetAll();
    }
}
```

Three things make it an API controller:

- **`ControllerBase`**, not `Controller`. `Controller` adds everything for views (`View()`, `ViewBag`, …), which an API never uses. `ControllerBase` has the rest: `NotFound()`, `CreatedAtAction()`, `ModelState` and more.
- **`[ApiController]`** switches on behavior made for APIs. The most useful: it checks the validation annotations by itself, and answers `400 Bad Request` with the errors, before your action even runs. No `if (!ModelState.IsValid)` needed.
- **`[Route("api/categories")]`** gives every action in the class that address to start from. APIs use **attribute routing**, as in *Routing in ASP.NET Core*; there's no `{controller}/{action}` pattern. (`[Route("api/[controller]")]` does the same, using the class name without *Controller*.)

The constructor asks for an `ICategoryDao`, exactly as MVC controllers asked for their DAOs.

## Registering the DAO

`Program.cs` from the template already registers controllers and maps them. Add one line, telling ASP.NET Core which class keeps the `ICategoryDao` contract:

```csharp
using NorthwindApi.Data;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddOpenApi();

string connectionString = builder.Configuration.GetConnectionString("Northwind")!;
builder.Services.AddScoped<ICategoryDao>(_ => new CategoryDao(connectionString));

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();
```

- **`AddControllers()`**, not `AddControllersWithViews()`: no views.
- **`AddScoped<ICategoryDao>(...)`**: one DAO per request, given to anyone who asks for an `ICategoryDao`.
- **`MapControllers()`** connects every controller's attribute routes.
- **`AddOpenApi()`** and **`MapOpenApi()`** publish a description of your API at `/openapi/v1.json` while you develop, which tools can read to list every endpoint.

Run it, and `GET {{HostAddress}}/api/categories` answers with all eight Northwind categories as JSON.

## MVC controller or API controller?

| | MVC controller | API controller |
|---|---|---|
| Derives from | `Controller` | `ControllerBase` |
| Marked | (nothing) | `[ApiController]` |
| Actions return | Views: `return View(model)` | Data, or a status: `return category`, `return NotFound()` |
| Routes | Usually the `{controller}/{action}/{id?}` pattern | Attribute routes, such as `[Route("api/categories")]` |
| Invalid input | You check `ModelState.IsValid` and show the form again | `[ApiController]` answers `400` by itself |
| Client | A person, in a browser | A program |

## Check yourself

2 questions to try on the lesson page.

## So what

An API controller is an MVC controller with the views taken out: `ControllerBase`, `[ApiController]`, a route, and a DAO asked for through its interface. Everything else you know, dependency injection, model binding, validation, carries straight over.

## Key terms

- Controller
- \[ApiController\]
- [OpenAPI](https://www.ask-codey.com/glossary/openapi-rest-api)

## Cheat sheets

- [Creating an API Controller](https://www.ask-codey.com/cheat-sheets/web-apis-with-controllers-creating-an-api-controller-cheat-sheet)

Source: https://www.ask-codey.com/lessons/web-apis-with-controllers-creating-an-api-controller
