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

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:

{
  "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:

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:

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

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:

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

Cheat sheets