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 answers400 Bad Requestbefore your action runs, so you don't checkModelState.IsValid.- The controller asks for an
ICategoryDao, andProgram.cssays which class keeps that contract withAddScoped<ICategoryDao>(...). - An API uses
AddControllers()andMapControllers(), 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, notController.Controlleradds everything for views (View(),ViewBag, …), which an API never uses.ControllerBasehas the rest:NotFound(),CreatedAtAction(),ModelStateand more.[ApiController]switches on behavior made for APIs. The most useful: it checks the validation annotations by itself, and answers400 Bad Requestwith the errors, before your action even runs. Noif (!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(), notAddControllersWithViews(): no views.AddScoped<ICategoryDao>(...): one DAO per request, given to anyone who asks for anICategoryDao.MapControllers()connects every controller's attribute routes.AddOpenApi()andMapOpenApi()publish a description of your API at/openapi/v1.jsonwhile 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